From f09c48d79b69f6359f4b9b7f56ce17f009c62ef4 Mon Sep 17 00:00:00 2001 From: Akhil Mohan Date: Thu, 15 Feb 2024 20:00:37 +0530 Subject: [PATCH 01/29] feat(e2e): fixed seed file issue and added more seeding --- backend/.eslintignore | 1 + backend/.eslintrc.js | 12 ++ backend/e2e-test/vitest-environment-knex.ts | 23 ++-- backend/package-lock.json | 112 +++++++++++-------- backend/package.json | 8 +- backend/scripts/create-seed-file.ts | 9 +- backend/src/db/knexfile.ts | 4 + backend/src/db/seed-data.ts | 115 +++++++++++++++++--- backend/src/db/seeds/1-user.ts | 6 +- backend/src/db/seeds/2-org.ts | 3 +- backend/src/db/seeds/3-project.ts | 46 ++++++-- backend/src/db/seeds/4-machine-identity.ts | 83 ++++++++++++++ backend/src/lib/crypto/encryption.ts | 9 +- backend/vitest.e2e.config.ts | 6 +- 14 files changed, 337 insertions(+), 100 deletions(-) create mode 100644 backend/src/db/seeds/4-machine-identity.ts diff --git a/backend/.eslintignore b/backend/.eslintignore index c767a4a9eb..660e6d10f8 100644 --- a/backend/.eslintignore +++ b/backend/.eslintignore @@ -1,2 +1,3 @@ vitest-environment-infisical.ts vitest.config.ts +vitest.e2e.config.ts diff --git a/backend/.eslintrc.js b/backend/.eslintrc.js index e99c48c097..9c558919b4 100644 --- a/backend/.eslintrc.js +++ b/backend/.eslintrc.js @@ -21,6 +21,18 @@ module.exports = { tsconfigRootDir: __dirname }, root: true, + overrides: [ + { + files: ["./e2e-test/**/*"], + rules: { + "@typescript-eslint/no-unsafe-member-access": "off", + "@typescript-eslint/no-unsafe-assignment": "off", + "@typescript-eslint/no-unsafe-argument": "off", + "@typescript-eslint/no-unsafe-return": "off", + "@typescript-eslint/no-unsafe-call": "off", + } + } + ], rules: { "@typescript-eslint/no-empty-function": "off", "@typescript-eslint/no-unsafe-enum-comparison": "off", diff --git a/backend/e2e-test/vitest-environment-knex.ts b/backend/e2e-test/vitest-environment-knex.ts index 1e424401ed..aad12fd9b4 100644 --- a/backend/e2e-test/vitest-environment-knex.ts +++ b/backend/e2e-test/vitest-environment-knex.ts @@ -1,26 +1,29 @@ -// import { main } from "@app/server/app"; -import { initEnvConfig } from "@app/lib/config/env"; +import "ts-node/register"; + import dotenv from "dotenv"; +import jwt from "jsonwebtoken"; import knex from "knex"; import path from "path"; -import { mockSmtpServer } from "./mocks/smtp"; -import { initLogger } from "@app/lib/logger"; -import jwt from "jsonwebtoken"; -import "ts-node/register"; +import { seedData1 } from "@app/db/seed-data"; +import { initEnvConfig } from "@app/lib/config/env"; +import { initLogger } from "@app/lib/logger"; import { main } from "@app/server/app"; -import { mockQueue } from "./mocks/queue"; import { AuthTokenType } from "@app/services/auth/auth-type"; -import { seedData1 } from "@app/db/seed-data"; + +import { mockQueue } from "./mocks/queue"; +import { mockSmtpServer } from "./mocks/smtp"; dotenv.config({ path: path.join(__dirname, "../.env.test") }); export default { name: "knex-env", transformMode: "ssr", async setup() { + const logger = await initLogger(); + const cfg = initEnvConfig(logger); const db = knex({ client: "pg", - connection: process.env.DB_CONNECTION_URI, + connection: cfg.DB_CONNECTION_URI, migrations: { directory: path.join(__dirname, "../src/db/migrations"), extension: "ts", @@ -37,8 +40,6 @@ export default { await db.seed.run(); const smtp = mockSmtpServer(); const queue = mockQueue(); - const logger = await initLogger(); - const cfg = initEnvConfig(logger); const server = await main({ db, smtp, logger, queue }); // @ts-expect-error type globalThis.testServer = server; diff --git a/backend/package-lock.json b/backend/package-lock.json index 1bce540c65..da81865530 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -101,7 +101,7 @@ "tsx": "^4.4.0", "typescript": "^5.3.2", "vite-tsconfig-paths": "^4.2.2", - "vitest": "^1.0.4" + "vitest": "^1.2.2" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -4207,6 +4207,12 @@ "@types/ms": "*" } }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, "node_modules/@types/express": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", @@ -4927,13 +4933,13 @@ "dev": true }, "node_modules/@vitest/expect": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.0.4.tgz", - "integrity": "sha512-/NRN9N88qjg3dkhmFcCBwhn/Ie4h064pY3iv7WLRsDJW7dXnEgeoa8W9zy7gIPluhz6CkgqiB3HmpIXgmEY5dQ==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.2.2.tgz", + "integrity": "sha512-3jpcdPAD7LwHUUiT2pZTj2U82I2Tcgg2oVPvKxhn6mDI2On6tfvPQTjAI4628GUGDZrCm4Zna9iQHm5cEexOAg==", "dev": true, "dependencies": { - "@vitest/spy": "1.0.4", - "@vitest/utils": "1.0.4", + "@vitest/spy": "1.2.2", + "@vitest/utils": "1.2.2", "chai": "^4.3.10" }, "funding": { @@ -4941,12 +4947,12 @@ } }, "node_modules/@vitest/runner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.0.4.tgz", - "integrity": "sha512-rhOQ9FZTEkV41JWXozFM8YgOqaG9zA7QXbhg5gy6mFOVqh4PcupirIJ+wN7QjeJt8S8nJRYuZH1OjJjsbxAXTQ==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.2.2.tgz", + "integrity": "sha512-JctG7QZ4LSDXr5CsUweFgcpEvrcxOV1Gft7uHrvkQ+fsAVylmWQvnaAr/HDp3LAH1fztGMQZugIheTWjaGzYIg==", "dev": true, "dependencies": { - "@vitest/utils": "1.0.4", + "@vitest/utils": "1.2.2", "p-limit": "^5.0.0", "pathe": "^1.1.1" }, @@ -4982,9 +4988,9 @@ } }, "node_modules/@vitest/snapshot": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.0.4.tgz", - "integrity": "sha512-vkfXUrNyNRA/Gzsp2lpyJxh94vU2OHT1amoD6WuvUAA12n32xeVZQ0KjjQIf8F6u7bcq2A2k969fMVxEsxeKYA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.2.2.tgz", + "integrity": "sha512-SmGY4saEw1+bwE1th6S/cZmPxz/Q4JWsl7LvbQIky2tKE35US4gd0Mjzqfr84/4OD0tikGWaWdMja/nWL5NIPA==", "dev": true, "dependencies": { "magic-string": "^0.30.5", @@ -4996,9 +5002,9 @@ } }, "node_modules/@vitest/spy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.0.4.tgz", - "integrity": "sha512-9ojTFRL1AJVh0hvfzAQpm0QS6xIS+1HFIw94kl/1ucTfGCaj1LV/iuJU4Y6cdR03EzPDygxTHwE1JOm+5RCcvA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.2.2.tgz", + "integrity": "sha512-k9Gcahssw8d7X3pSLq3e3XEu/0L78mUkCjivUqCQeXJm9clfXR/Td8+AP+VC1O6fKPIDLcHDTAmBOINVuv6+7g==", "dev": true, "dependencies": { "tinyspy": "^2.2.0" @@ -5008,12 +5014,13 @@ } }, "node_modules/@vitest/utils": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.0.4.tgz", - "integrity": "sha512-gsswWDXxtt0QvtK/y/LWukN7sGMYmnCcv1qv05CsY6cU/Y1zpGX1QuvLs+GO1inczpE6Owixeel3ShkjhYtGfA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.2.2.tgz", + "integrity": "sha512-WKITBHLsBHlpjnDQahr+XK6RE7MiAsgrIkr0pGhQ9ygoxBfUeG0lUG5iLlzqjmKSlBv3+j5EGsriBzh+C3Tq9g==", "dev": true, "dependencies": { "diff-sequences": "^29.6.3", + "estree-walker": "^3.0.3", "loupe": "^2.3.7", "pretty-format": "^29.7.0" }, @@ -5084,9 +5091,9 @@ } }, "node_modules/acorn-walk": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.0.tgz", - "integrity": "sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==", + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", "dev": true, "engines": { "node": ">=0.4.0" @@ -5874,9 +5881,9 @@ } }, "node_modules/chai": { - "version": "4.3.10", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.10.tgz", - "integrity": "sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", + "integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==", "dev": true, "dependencies": { "assertion-error": "^1.1.0", @@ -7041,6 +7048,15 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -9109,9 +9125,9 @@ } }, "node_modules/magic-string": { - "version": "0.30.5", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", - "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", + "version": "0.30.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz", + "integrity": "sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==", "dev": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" @@ -12286,18 +12302,18 @@ "dev": true }, "node_modules/tinypool": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.1.tgz", - "integrity": "sha512-zBTCK0cCgRROxvs9c0CGK838sPkeokNGdQVUUwHAbynHFlmyJYj825f/oRs528HaIJ97lo0pLIlDUzwN+IorWg==", + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.2.tgz", + "integrity": "sha512-SUszKYe5wgsxnNOVlBYO6IC+8VGWdVGZWAqUxp3UErNBtptZvWbwyUOyzNL59zigz2rCA92QiL3wvG+JDSdJdQ==", "dev": true, "engines": { "node": ">=14.0.0" } }, "node_modules/tinyspy": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.0.tgz", - "integrity": "sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz", + "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==", "dev": true, "engines": { "node": ">=14.0.0" @@ -13430,9 +13446,9 @@ } }, "node_modules/vite-node": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.0.4.tgz", - "integrity": "sha512-9xQQtHdsz5Qn8hqbV7UKqkm8YkJhzT/zr41Dmt5N7AlD8hJXw/Z7y0QiD5I8lnTthV9Rvcvi0QW7PI0Fq83ZPg==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.2.2.tgz", + "integrity": "sha512-1as4rDTgVWJO3n1uHmUYqq7nsFgINQ9u+mRcXpjeOMJUmviqNKjcZB7UfRZrlM7MjYXMKpuWp5oGkjaFLnjawg==", "dev": true, "dependencies": { "cac": "^6.7.14", @@ -13906,17 +13922,17 @@ } }, "node_modules/vitest": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.0.4.tgz", - "integrity": "sha512-s1GQHp/UOeWEo4+aXDOeFBJwFzL6mjycbQwwKWX2QcYfh/7tIerS59hWQ20mxzupTJluA2SdwiBuWwQHH67ckg==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.2.2.tgz", + "integrity": "sha512-d5Ouvrnms3GD9USIK36KG8OZ5bEvKEkITFtnGv56HFaSlbItJuYr7hv2Lkn903+AvRAgSixiamozUVfORUekjw==", "dev": true, "dependencies": { - "@vitest/expect": "1.0.4", - "@vitest/runner": "1.0.4", - "@vitest/snapshot": "1.0.4", - "@vitest/spy": "1.0.4", - "@vitest/utils": "1.0.4", - "acorn-walk": "^8.3.0", + "@vitest/expect": "1.2.2", + "@vitest/runner": "1.2.2", + "@vitest/snapshot": "1.2.2", + "@vitest/spy": "1.2.2", + "@vitest/utils": "1.2.2", + "acorn-walk": "^8.3.2", "cac": "^6.7.14", "chai": "^4.3.10", "debug": "^4.3.4", @@ -13928,9 +13944,9 @@ "std-env": "^3.5.0", "strip-literal": "^1.3.0", "tinybench": "^2.5.1", - "tinypool": "^0.8.1", + "tinypool": "^0.8.2", "vite": "^5.0.0", - "vite-node": "1.0.4", + "vite-node": "1.2.2", "why-is-node-running": "^2.2.2" }, "bin": { diff --git a/backend/package.json b/backend/package.json index 6b4d32317e..252d5cb471 100644 --- a/backend/package.json +++ b/backend/package.json @@ -24,8 +24,8 @@ "migration:latest": "knex --knexfile ./src/db/knexfile.ts --client pg migrate:latest", "migration:rollback": "knex --knexfile ./src/db/knexfile.ts migrate:rollback", "seed:new": "tsx ./scripts/create-seed-file.ts", - "seed:run": "knex --knexfile ./src/db/knexfile.ts --client pg seed:run", - "db:reset": "npm run migration:rollback -- --all && npm run migration:latest && npm run seed:run" + "seed": "knex --knexfile ./src/db/knexfile.ts --client pg seed:run", + "db:reset": "npm run migration:rollback -- --all && npm run migration:latest" }, "keywords": [], "author": "", @@ -67,7 +67,7 @@ "tsx": "^4.4.0", "typescript": "^5.3.2", "vite-tsconfig-paths": "^4.2.2", - "vitest": "^1.0.4" + "vitest": "^1.2.2" }, "dependencies": { "@aws-sdk/client-secrets-manager": "^3.485.0", @@ -125,4 +125,4 @@ "zod": "^3.22.4", "zod-to-json-schema": "^3.22.0" } -} \ No newline at end of file +} diff --git a/backend/scripts/create-seed-file.ts b/backend/scripts/create-seed-file.ts index 25faf94c30..c79ea0846f 100644 --- a/backend/scripts/create-seed-file.ts +++ b/backend/scripts/create-seed-file.ts @@ -7,11 +7,10 @@ import promptSync from "prompt-sync"; const prompt = promptSync({ sigint: true }); const migrationName = prompt("Enter name for seedfile: "); -const fileCounter = readdirSync(path.join(__dirname, "../src/db/seed")).length || 1; +const fileCounter = readdirSync(path.join(__dirname, "../src/db/seeds")).length || 1; execSync( - `npx knex seed:make --knexfile ${path.join( - __dirname, - "../src/db/knexfile.ts" - )} -x ts ${fileCounter}-${migrationName}`, + `npx knex seed:make --knexfile ${path.join(__dirname, "../src/db/knexfile.ts")} -x ts ${ + fileCounter + 1 + }-${migrationName}`, { stdio: "inherit" } ); diff --git a/backend/src/db/knexfile.ts b/backend/src/db/knexfile.ts index 73eb507f40..b81285d222 100644 --- a/backend/src/db/knexfile.ts +++ b/backend/src/db/knexfile.ts @@ -10,6 +10,10 @@ dotenv.config({ path: path.join(__dirname, "../../../.env.migration"), debug: true }); +dotenv.config({ + path: path.join(__dirname, "../../../.env"), + debug: true +}); export default { development: { client: "postgres", diff --git a/backend/src/db/seed-data.ts b/backend/src/db/seed-data.ts index bb57d5bb4b..60038bdf88 100644 --- a/backend/src/db/seed-data.ts +++ b/backend/src/db/seed-data.ts @@ -6,13 +6,14 @@ import nacl from "tweetnacl"; import { encodeBase64 } from "tweetnacl-util"; import { + decryptAsymmetric, // decryptAsymmetric, - decryptSymmetric, + decryptSymmetric128BitHexKeyUTF8, encryptAsymmetric, - encryptSymmetric + encryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto"; -import { TUserEncryptionKeys } from "./schemas"; +import { TSecrets, TUserEncryptionKeys } from "./schemas"; export const seedData1 = { id: "3dafd81d-4388-432b-a4c5-f735616868c1", @@ -31,6 +32,14 @@ export const seedData1 = { name: "Development", slug: "dev" }, + machineIdentity: { + id: "88fa7aed-9288-401e-a4c9-fa9430be62a0", + name: "mac1", + clientCred: { + id: "3f6135db-f237-421d-af66-a8f4e80d443b", + secret: "da35a5a5a7b57f977a9a73394506e878a7175d06606df43dc93e1472b10cf339" + } + }, token: { id: "a9dfafba-a3b7-42e3-8618-91abb702fd36" } @@ -73,7 +82,7 @@ export const generateUserSrpKeys = async (password: string) => { ciphertext: encryptedPrivateKey, iv: encryptedPrivateKeyIV, tag: encryptedPrivateKeyTag - } = encryptSymmetric(privateKey, key.toString("base64")); + } = encryptSymmetric128BitHexKeyUTF8(privateKey, key); // create the protected key by encrypting the symmetric key // [key] with the derived key @@ -81,7 +90,7 @@ export const generateUserSrpKeys = async (password: string) => { ciphertext: protectedKey, iv: protectedKeyIV, tag: protectedKeyTag - } = encryptSymmetric(key.toString("hex"), derivedKey.toString("base64")); + } = encryptSymmetric128BitHexKeyUTF8(key.toString("hex"), derivedKey); return { protectedKey, @@ -107,32 +116,102 @@ export const getUserPrivateKey = async (password: string, user: TUserEncryptionK raw: true }); if (!derivedKey) throw new Error("Failed to derive key from password"); - const key = decryptSymmetric({ + + const key = decryptSymmetric128BitHexKeyUTF8({ ciphertext: user.protectedKey as string, iv: user.protectedKeyIV as string, tag: user.protectedKeyTag as string, - key: derivedKey.toString("base64") + key: derivedKey }); - const privateKey = decryptSymmetric({ + + const privateKey = decryptSymmetric128BitHexKeyUTF8({ ciphertext: user.encryptedPrivateKey, iv: user.iv, tag: user.tag, - key + key: Buffer.from(key, "hex") }); return privateKey; }; -export const buildUserProjectKey = async (privateKey: string, publickey: string) => { +export const buildUserProjectKey = (privateKey: string, publickey: string) => { const randomBytes = crypto.randomBytes(16).toString("hex"); const { nonce, ciphertext } = encryptAsymmetric(randomBytes, publickey, privateKey); return { nonce, ciphertext }; }; -// export const getUserProjectKey = async (privateKey: string) => { -// const key = decryptAsymmetric({ -// ciphertext: decryptFileKey.encryptedKey, -// nonce: decryptFileKey.nonce, -// publicKey: decryptFileKey.sender.publicKey, -// privateKey: PRIVATE_KEY -// }); -// }; +export const getUserProjectKey = async (privateKey: string, ciphertext: string, nonce: string, publicKey: string) => { + return decryptAsymmetric({ + ciphertext, + nonce, + publicKey, + privateKey + }); +}; + +export const encryptSecret = (encKey: string, key: string, value?: string, comment?: string) => { + // encrypt key + const { + ciphertext: secretKeyCiphertext, + iv: secretKeyIV, + tag: secretKeyTag + } = encryptSymmetric128BitHexKeyUTF8(key, encKey); + + // encrypt value + const { + ciphertext: secretValueCiphertext, + iv: secretValueIV, + tag: secretValueTag + } = encryptSymmetric128BitHexKeyUTF8(value ?? "", encKey); + + // encrypt comment + const { + ciphertext: secretCommentCiphertext, + iv: secretCommentIV, + tag: secretCommentTag + } = encryptSymmetric128BitHexKeyUTF8(comment ?? "", encKey); + + return { + secretKeyCiphertext, + secretKeyIV, + secretKeyTag, + secretValueCiphertext, + secretValueIV, + secretValueTag, + secretCommentCiphertext, + secretCommentIV, + secretCommentTag + }; +}; + +export const decryptSecret = (decryptKey: string, encSecret: TSecrets) => { + const secretKey = decryptSymmetric128BitHexKeyUTF8({ + key: decryptKey, + ciphertext: encSecret.secretKeyCiphertext, + tag: encSecret.secretKeyTag, + iv: encSecret.secretKeyIV + }); + + const secretValue = decryptSymmetric128BitHexKeyUTF8({ + key: decryptKey, + ciphertext: encSecret.secretValueCiphertext, + tag: encSecret.secretValueTag, + iv: encSecret.secretValueIV + }); + + const secretComment = + encSecret.secretCommentIV && encSecret.secretCommentTag && encSecret.secretCommentCiphertext + ? decryptSymmetric128BitHexKeyUTF8({ + key: decryptKey, + ciphertext: encSecret.secretCommentCiphertext, + tag: encSecret.secretCommentTag, + iv: encSecret.secretCommentIV + }) + : null; + + return { + key: secretKey, + value: secretValue, + comment: secretComment, + version: encSecret.version + }; +}; diff --git a/backend/src/db/seeds/1-user.ts b/backend/src/db/seeds/1-user.ts index ca0042a98f..8e2b00b90a 100644 --- a/backend/src/db/seeds/1-user.ts +++ b/backend/src/db/seeds/1-user.ts @@ -14,7 +14,8 @@ export async function seed(knex: Knex): Promise { const [user] = await knex(TableName.Users) .insert([ { - // @ts-expect-error exluded type id needs to be inserted here to keep it testable + // eslint-disable-next-line + // @ts-ignore id: seedData1.id, email: seedData1.email, superAdmin: true, @@ -48,7 +49,8 @@ export async function seed(knex: Knex): Promise { ]); await knex(TableName.AuthTokenSession).insert({ - // @ts-expect-error exluded type id needs to be inserted here to keep it testable + // eslint-disable-next-line + // @ts-ignore id: seedData1.token.id, userId: seedData1.id, ip: "151.196.220.213", diff --git a/backend/src/db/seeds/2-org.ts b/backend/src/db/seeds/2-org.ts index a9c3ec3c77..ba2f65a36f 100644 --- a/backend/src/db/seeds/2-org.ts +++ b/backend/src/db/seeds/2-org.ts @@ -14,7 +14,8 @@ export async function seed(knex: Knex): Promise { const [org] = await knex(TableName.Organization) .insert([ { - // @ts-expect-error exluded type id needs to be inserted here to keep it testable + // eslint-disable-next-line + // @ts-ignore id: seedData1.organization.id, name: "infisical", slug: "infisical", diff --git a/backend/src/db/seeds/3-project.ts b/backend/src/db/seeds/3-project.ts index 7818d58311..fea71b557f 100644 --- a/backend/src/db/seeds/3-project.ts +++ b/backend/src/db/seeds/3-project.ts @@ -1,7 +1,11 @@ +import crypto from "node:crypto"; + import { Knex } from "knex"; -import { OrgMembershipRole, TableName } from "../schemas"; -import { seedData1 } from "../seed-data"; +import { encryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto"; + +import { OrgMembershipRole, SecretEncryptionAlgo, SecretKeyEncoding, TableName } from "../schemas"; +import { buildUserProjectKey, getUserPrivateKey, seedData1 } from "../seed-data"; export const DEFAULT_PROJECT_ENVS = [ { name: "Development", slug: "dev" }, @@ -20,21 +24,32 @@ export async function seed(knex: Knex): Promise { name: seedData1.project.name, orgId: seedData1.organization.id, slug: "first-project", - // @ts-expect-error exluded type id needs to be inserted here to keep it testable + // eslint-disable-next-line + // @ts-ignore id: seedData1.project.id }) .returning("*"); - // await knex(TableName.ProjectKeys).insert({ - // projectId: project.id, - // senderId: seedData1.id - // }); - await knex(TableName.ProjectMembership).insert({ projectId: project.id, role: OrgMembershipRole.Admin, userId: seedData1.id }); + + const user = await knex(TableName.UserEncryptionKey).where({ userId: seedData1.id }).first(); + if (!user) throw new Error("User not found"); + + const userPrivateKey = await getUserPrivateKey(seedData1.password, user); + const projectKey = buildUserProjectKey(userPrivateKey, user.publicKey); + await knex(TableName.ProjectKeys).insert({ + projectId: project.id, + nonce: projectKey.nonce, + encryptedKey: projectKey.ciphertext, + receiverId: seedData1.id, + senderId: seedData1.id + }); + + // create default environments and default folders const envs = await knex(TableName.Environment) .insert( DEFAULT_PROJECT_ENVS.map(({ name, slug }, index) => ({ @@ -46,4 +61,19 @@ export async function seed(knex: Knex): Promise { ) .returning("*"); await knex(TableName.SecretFolder).insert(envs.map(({ id }) => ({ name: "root", envId: id, parentId: null }))); + + // save secret secret blind index + const encKey = process.env.ENCRYPTION_KEY; + if (!encKey) throw new Error("Missing ENCRYPTION_KEY"); + const salt = crypto.randomBytes(16).toString("base64"); + const secretBlindIndex = encryptSymmetric128BitHexKeyUTF8(salt, encKey); + // insert secret blind index for project + await knex(TableName.SecretBlindIndex).insert({ + projectId: project.id, + encryptedSaltCipherText: secretBlindIndex.ciphertext, + saltIV: secretBlindIndex.iv, + saltTag: secretBlindIndex.tag, + algorithm: SecretEncryptionAlgo.AES_256_GCM, + keyEncoding: SecretKeyEncoding.UTF8 + }); } diff --git a/backend/src/db/seeds/4-machine-identity.ts b/backend/src/db/seeds/4-machine-identity.ts new file mode 100644 index 0000000000..e51a81b3b8 --- /dev/null +++ b/backend/src/db/seeds/4-machine-identity.ts @@ -0,0 +1,83 @@ +import bcrypt from "bcrypt"; +import { Knex } from "knex"; + +import { IdentityAuthMethod, OrgMembershipRole, ProjectMembershipRole, TableName } from "../schemas"; +import { seedData1 } from "../seed-data"; + +export async function seed(knex: Knex): Promise { + // Deletes ALL existing entries + await knex(TableName.Identity).del(); + await knex(TableName.IdentityOrgMembership).del(); + + // Inserts seed entries + await knex(TableName.Identity).insert([ + { + // eslint-disable-next-line + // @ts-ignore + id: seedData1.machineIdentity.id, + name: seedData1.machineIdentity.name, + authMethod: IdentityAuthMethod.Univeral + } + ]); + const identityUa = await knex(TableName.IdentityUniversalAuth) + .insert([ + { + identityId: seedData1.machineIdentity.id, + clientId: seedData1.machineIdentity.clientCred.id, + clientSecretTrustedIps: JSON.stringify([ + { + type: "ipv4", + prefix: 0, + ipAddress: "0.0.0.0" + }, + { + type: "ipv6", + prefix: 0, + ipAddress: "::" + } + ]), + accessTokenTrustedIps: JSON.stringify([ + { + type: "ipv4", + prefix: 0, + ipAddress: "0.0.0.0" + }, + { + type: "ipv6", + prefix: 0, + ipAddress: "::" + } + ]), + accessTokenTTL: 2592000, + accessTokenMaxTTL: 2592000, + accessTokenNumUsesLimit: 0 + } + ]) + .returning("*"); + const clientSecretHash = await bcrypt.hash(seedData1.machineIdentity.clientCred.secret, 10); + await knex(TableName.IdentityUaClientSecret).insert([ + { + identityUAId: identityUa[0].id, + description: "", + clientSecretTTL: 0, + clientSecretNumUses: 0, + clientSecretNumUsesLimit: 0, + clientSecretPrefix: seedData1.machineIdentity.clientCred.secret.slice(0, 4), + clientSecretHash, + isClientSecretRevoked: false + } + ]); + await knex(TableName.IdentityOrgMembership).insert([ + { + identityId: seedData1.machineIdentity.id, + orgId: seedData1.organization.id, + role: OrgMembershipRole.Admin + } + ]); + + await knex(TableName.IdentityProjectMembership).insert({ + identityId: seedData1.machineIdentity.id, + role: ProjectMembershipRole.Admin, + projectId: seedData1.project.id + }); +} diff --git a/backend/src/lib/crypto/encryption.ts b/backend/src/lib/crypto/encryption.ts index 74febccec8..2c947b75bd 100644 --- a/backend/src/lib/crypto/encryption.ts +++ b/backend/src/lib/crypto/encryption.ts @@ -44,7 +44,7 @@ export const encryptSymmetric = (plaintext: string, key: string) => { }; }; -export const encryptSymmetric128BitHexKeyUTF8 = (plaintext: string, key: string) => { +export const encryptSymmetric128BitHexKeyUTF8 = (plaintext: string, key: string | Buffer) => { const iv = crypto.randomBytes(BLOCK_SIZE_BYTES_16); const cipher = crypto.createCipheriv(SecretEncryptionAlgo.AES_256_GCM, key, iv); @@ -58,7 +58,12 @@ export const encryptSymmetric128BitHexKeyUTF8 = (plaintext: string, key: string) }; }; -export const decryptSymmetric128BitHexKeyUTF8 = ({ ciphertext, iv, tag, key }: TDecryptSymmetricInput): string => { +export const decryptSymmetric128BitHexKeyUTF8 = ({ + ciphertext, + iv, + tag, + key +}: Omit & { key: string | Buffer }): string => { const decipher = crypto.createDecipheriv(SecretEncryptionAlgo.AES_256_GCM, key, Buffer.from(iv, "base64")); decipher.setAuthTag(Buffer.from(tag, "base64")); diff --git a/backend/vitest.e2e.config.ts b/backend/vitest.e2e.config.ts index e8636d4051..c660fed14e 100644 --- a/backend/vitest.e2e.config.ts +++ b/backend/vitest.e2e.config.ts @@ -4,12 +4,16 @@ import { defineConfig } from "vitest/config"; export default defineConfig({ test: { globals: true, + env: { + NODE_ENV: "test" + }, environment: "./e2e-test/vitest-environment-knex.ts", include: ["./e2e-test/**/*.spec.ts"], poolOptions: { threads: { singleThread: true, - useAtomics: true + useAtomics: true, + isolate: false } } }, From 00876f788c2b35a2629b59bd66bc26c228f05cb5 Mon Sep 17 00:00:00 2001 From: Akhil Mohan Date: Thu, 15 Feb 2024 20:01:32 +0530 Subject: [PATCH 02/29] feat(e2e): added secrets integration tests --- .../e2e-test/routes/v1/secret-folder.spec.ts | 6 +- backend/e2e-test/routes/v3/secrets.spec.ts | 777 ++++++++++++++++++ backend/src/server/routes/v3/secret-router.ts | 3 +- backend/src/services/secret/secret-dal.ts | 6 + 4 files changed, 787 insertions(+), 5 deletions(-) diff --git a/backend/e2e-test/routes/v1/secret-folder.spec.ts b/backend/e2e-test/routes/v1/secret-folder.spec.ts index bc290fed35..727c41bbb1 100644 --- a/backend/e2e-test/routes/v1/secret-folder.spec.ts +++ b/backend/e2e-test/routes/v1/secret-folder.spec.ts @@ -59,8 +59,9 @@ describe("Secret Folder Router", async () => { expect(res.statusCode).toBe(200); const payload = JSON.parse(res.payload); expect(payload).toHaveProperty("folders"); - expect(payload.folders.length).toBe(expected.length); - expect(payload).toEqual({ folders: expected.folders.map((el) => expect.objectContaining(el)) }); + expect(payload).toEqual({ + folders: expect.arrayContaining(expected.folders.map((el) => expect.objectContaining(el))) + }); }); let toBeDeleteFolderId = ""; @@ -106,7 +107,6 @@ describe("Secret Folder Router", async () => { expect(resUpdatedFolders.statusCode).toBe(200); const updatedFolderList = JSON.parse(resUpdatedFolders.payload); expect(updatedFolderList).toHaveProperty("folders"); - expect(updatedFolderList.folders.length).toEqual(1); expect(updatedFolderList.folders[0].name).toEqual("folder-updated"); }); diff --git a/backend/e2e-test/routes/v3/secrets.spec.ts b/backend/e2e-test/routes/v3/secrets.spec.ts index e69de29bb2..89935d432d 100644 --- a/backend/e2e-test/routes/v3/secrets.spec.ts +++ b/backend/e2e-test/routes/v3/secrets.spec.ts @@ -0,0 +1,777 @@ +import { SecretType, TSecrets } from "@app/db/schemas"; +import { decryptSecret, encryptSecret, getUserPrivateKey, seedData1 } from "@app/db/seed-data"; +import { decryptAsymmetric, encryptAsymmetric } from "@app/lib/crypto"; + +describe("Secret V3 Router", async () => { + const testSecrets = [ + { + path: "/", + secret: { + key: "SEC1", + value: "something-secret", + comment: "some comment" + } + }, + { + path: "/nested1/nested2/folder", + secret: { + key: "NESTED-SEC1", + value: "something-secret", + comment: "some comment" + } + } + ]; + + let projectKey = ""; + beforeAll(async () => { + const projectKeyRes = await testServer.inject({ + method: "GET", + url: `/api/v2/workspace/${seedData1.project.id}/encrypted-key`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + } + }); + const projectKeyEnc = JSON.parse(projectKeyRes.payload); + + const userInfoRes = await testServer.inject({ + method: "GET", + url: "/api/v2/users/me", + headers: { + authorization: `Bearer ${jwtAuthToken}` + } + }); + const { user: userInfo } = JSON.parse(userInfoRes.payload); + const privateKey = await getUserPrivateKey(seedData1.password, userInfo); + projectKey = decryptAsymmetric({ + ciphertext: projectKeyEnc.encryptedKey, + nonce: projectKeyEnc.nonce, + publicKey: projectKeyEnc.sender.publicKey, + privateKey + }); + + // create a deep folder + const folderCreate = await testServer.inject({ + method: "POST", + url: `/api/v1/folders`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + name: "folder", + path: "/nested1/nested2" + } + }); + expect(folderCreate.statusCode).toBe(200); + }); + + const getSecrets = async (environment: string, secretPath = "/") => { + const res = await testServer.inject({ + method: "GET", + url: `/api/v3/secrets`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + query: { + secretPath, + environment, + workspaceId: seedData1.project.id + } + }); + const secrets: TSecrets[] = JSON.parse(res.payload).secrets || []; + return secrets.map((el) => ({ ...decryptSecret(projectKey, el), type: el.type })); + }; + + test.each(testSecrets)("Create secret in path $path", async ({ secret, path }) => { + const createSecretReqBody = { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + type: SecretType.Shared, + secretPath: path, + ...encryptSecret(projectKey, secret.key, secret.value, secret.comment) + }; + const createSecRes = await testServer.inject({ + method: "POST", + url: `/api/v3/secrets/${secret.key}`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: createSecretReqBody + }); + expect(createSecRes.statusCode).toBe(200); + const createdSecretPayload = JSON.parse(createSecRes.payload); + expect(createdSecretPayload).toHaveProperty("secret"); + const decryptedSecret = decryptSecret(projectKey, createdSecretPayload.secret); + expect(decryptedSecret.key).toEqual(secret.key); + expect(decryptedSecret.value).toEqual(secret.value); + expect(decryptedSecret.comment).toEqual(secret.comment); + expect(decryptedSecret.version).toEqual(1); + + const secrets = await getSecrets(seedData1.environment.slug, path); + expect(secrets).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + key: secret.key, + value: secret.value, + type: SecretType.Shared + }) + ]) + ); + }); + + test.each(testSecrets)("Get secret by name in path $path", async ({ secret, path }) => { + const getSecByNameRes = await testServer.inject({ + method: "GET", + url: `/api/v3/secrets/${secret.key}`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + query: { + secretPath: path, + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug + } + }); + expect(getSecByNameRes.statusCode).toBe(200); + const getSecretByNamePayload = JSON.parse(getSecByNameRes.payload); + expect(getSecretByNamePayload).toHaveProperty("secret"); + const decryptedSecret = decryptSecret(projectKey, getSecretByNamePayload.secret); + expect(decryptedSecret.key).toEqual(secret.key); + expect(decryptedSecret.value).toEqual(secret.value); + expect(decryptedSecret.comment).toEqual(secret.comment); + }); + + test.each(testSecrets)("Creating personal secret without shared throw error in path $path", async ({ secret }) => { + const createSecretReqBody = { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + type: SecretType.Personal, + ...encryptSecret(projectKey, "SEC2", secret.value, secret.comment) + }; + const createSecRes = await testServer.inject({ + method: "POST", + url: `/api/v3/secrets/SEC2`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: createSecretReqBody + }); + const payload = JSON.parse(createSecRes.payload); + expect(createSecRes.statusCode).toBe(400); + expect(payload.error).toEqual("BadRequest"); + expect(payload.message).toEqual("Failed to create personal secret override for no corresponding shared secret"); + }); + + test.each(testSecrets)("Creating personal secret in path $path", async ({ secret, path }) => { + const createSecretReqBody = { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + type: SecretType.Personal, + secretPath: path, + ...encryptSecret(projectKey, secret.key, "personal-value", secret.comment) + }; + const createSecRes = await testServer.inject({ + method: "POST", + url: `/api/v3/secrets/${secret.key}`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: createSecretReqBody + }); + expect(createSecRes.statusCode).toBe(200); + + // list secrets should contain personal one and shared one + const secrets = await getSecrets(seedData1.environment.slug, path); + expect(secrets).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + key: secret.key, + value: secret.value, + type: SecretType.Shared + }), + expect.objectContaining({ + key: secret.key, + value: "personal-value", + type: SecretType.Personal + }) + ]) + ); + }); + + test.each(testSecrets)("Update secret in path $path", async ({ path, secret }) => { + const updateSecretReqBody = { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + type: SecretType.Shared, + secretPath: path, + ...encryptSecret(projectKey, secret.key, "new-value", secret.comment) + }; + const updateSecRes = await testServer.inject({ + method: "PATCH", + url: `/api/v3/secrets/${secret.key}`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: updateSecretReqBody + }); + expect(updateSecRes.statusCode).toBe(200); + const updatedSecretPayload = JSON.parse(updateSecRes.payload); + expect(updatedSecretPayload).toHaveProperty("secret"); + const decryptedSecret = decryptSecret(projectKey, updatedSecretPayload.secret); + expect(decryptedSecret.key).toEqual(secret.key); + expect(decryptedSecret.value).toEqual("new-value"); + expect(decryptedSecret.comment).toEqual(secret.comment); + + // list secret should have updated value + const secrets = await getSecrets(seedData1.environment.slug, path); + expect(secrets).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + key: secret.key, + value: "new-value", + type: SecretType.Shared + }) + ]) + ); + }); + + test.each(testSecrets)("Delete secret in path $path", async ({ secret, path }) => { + const deleteSecRes = await testServer.inject({ + method: "DELETE", + url: `/api/v3/secrets/${secret.key}`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + secretPath: path + } + }); + expect(deleteSecRes.statusCode).toBe(200); + const updatedSecretPayload = JSON.parse(deleteSecRes.payload); + expect(updatedSecretPayload).toHaveProperty("secret"); + const decryptedSecret = decryptSecret(projectKey, updatedSecretPayload.secret); + expect(decryptedSecret.key).toEqual(secret.key); + + // shared secret deletion should delete personal ones also + const secrets = await getSecrets(seedData1.environment.slug, path); + expect(secrets).toEqual( + expect.not.arrayContaining([ + expect.objectContaining({ + key: secret.key, + type: SecretType.Shared + }), + expect.objectContaining({ + key: secret.key, + type: SecretType.Personal + }) + ]) + ); + }); + + test.each(testSecrets)( + "Deleting personal one should not delete shared secret in path $path", + async ({ secret, path }) => { + const createSharedSecRes = await testServer.inject({ + method: "POST", + url: `/api/v3/secrets/${secret.key}`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + type: SecretType.Shared, + secretPath: path, + ...encryptSecret(projectKey, secret.key, secret.value, secret.comment) + } + }); + expect(createSharedSecRes.statusCode).toBe(200); + + const createPersonalSecRes = await testServer.inject({ + method: "POST", + url: `/api/v3/secrets/${secret.key}`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + secretPath: path, + type: SecretType.Personal, + ...encryptSecret(projectKey, secret.key, secret.value, secret.comment) + } + }); + expect(createPersonalSecRes.statusCode).toBe(200); + + // shared secret deletion should delete personal ones also + const secrets = await getSecrets(seedData1.environment.slug, path); + expect(secrets).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + key: secret.key, + type: SecretType.Shared + }), + expect.not.objectContaining({ + key: secret.key, + type: SecretType.Personal + }) + ]) + ); + } + ); + + test.each(testSecrets)("Bulk create secrets in path $path", async ({ secret, path }) => { + const createSharedSecRes = await testServer.inject({ + method: "POST", + url: `/api/v3/secrets/batch`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + secretPath: path, + secrets: Array.from(Array(10)).map((_e, i) => ({ + secretName: `BULK-${secret.key}-${i + 1}`, + ...encryptSecret(projectKey, `BULK-${secret.key}-${i + 1}`, secret.value, secret.comment) + })) + } + }); + expect(createSharedSecRes.statusCode).toBe(200); + const createSharedSecPayload = JSON.parse(createSharedSecRes.payload); + expect(createSharedSecPayload).toHaveProperty("secrets"); + + // bulk ones should exist + const secrets = await getSecrets(seedData1.environment.slug, path); + expect(secrets).toEqual( + expect.arrayContaining( + Array.from(Array(10)).map((_e, i) => + expect.objectContaining({ + key: `BULK-${secret.key}-${i + 1}`, + type: SecretType.Shared + }) + ) + ) + ); + }); + + test.each(testSecrets)("Bulk create fail on existing secret in path $path", async ({ secret, path }) => { + const createSharedSecRes = await testServer.inject({ + method: "POST", + url: `/api/v3/secrets/batch`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + secretPath: path, + secrets: Array.from(Array(10)).map((_e, i) => ({ + secretName: `BULK-${secret.key}-${i + 1}`, + ...encryptSecret(projectKey, `BULK-${secret.key}-${i + 1}`, secret.value, secret.comment) + })) + } + }); + expect(createSharedSecRes.statusCode).toBe(400); + }); + + test.each(testSecrets)("Bulk update secrets in path $path", async ({ secret, path }) => { + const updateSharedSecRes = await testServer.inject({ + method: "PATCH", + url: `/api/v3/secrets/batch`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + secretPath: path, + secrets: Array.from(Array(10)).map((_e, i) => ({ + secretName: `BULK-${secret.key}-${i + 1}`, + ...encryptSecret(projectKey, `BULK-${secret.key}-${i + 1}`, "update-value", secret.comment) + })) + } + }); + expect(updateSharedSecRes.statusCode).toBe(200); + const updateSharedSecPayload = JSON.parse(updateSharedSecRes.payload); + expect(updateSharedSecPayload).toHaveProperty("secrets"); + + // bulk ones should exist + const secrets = await getSecrets(seedData1.environment.slug, path); + expect(secrets).toEqual( + expect.arrayContaining( + Array.from(Array(10)).map((_e, i) => + expect.objectContaining({ + key: `BULK-${secret.key}-${i + 1}`, + value: "update-value", + type: SecretType.Shared + }) + ) + ) + ); + }); + + test.each(testSecrets)("Bulk delete secrets in path $path", async ({ secret, path }) => { + const updateSharedSecRes = await testServer.inject({ + method: "DELETE", + url: `/api/v3/secrets/batch`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + secretPath: path, + secrets: Array.from(Array(10)).map((_e, i) => ({ + secretName: `BULK-${secret.key}-${i + 1}` + })) + } + }); + expect(updateSharedSecRes.statusCode).toBe(200); + const updateSharedSecPayload = JSON.parse(updateSharedSecRes.payload); + expect(updateSharedSecPayload).toHaveProperty("secrets"); + + // bulk ones should exist + const secrets = await getSecrets(seedData1.environment.slug, path); + expect(secrets).toEqual( + expect.not.arrayContaining( + Array.from(Array(10)).map((_e, i) => + expect.objectContaining({ + key: `BULK-${secret.value}-${i + 1}`, + type: SecretType.Shared + }) + ) + ) + ); + }); +}); + +// raw secret endpoints +describe("Secret V3 Raw Router", async () => { + const testRawSecrets = [ + { + path: "/", + secret: { + key: "RAW-SEC1", + value: "something-secret", + comment: "some comment" + } + }, + { + path: "/nested1/nested2/folder", + secret: { + key: "NESTED-RAW-SEC1", + value: "something-secret", + comment: "some comment" + } + } + ]; + + beforeAll(async () => { + const res = await testServer.inject({ + method: "GET", + url: `/api/v2/workspace/${seedData1.project.id}/encrypted-key`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + } + }); + expect(res.statusCode).toEqual(200); + const projectKeyEnc = JSON.parse(res.payload); + + const userInfoRes = await testServer.inject({ + method: "GET", + url: "/api/v2/users/me", + headers: { + authorization: `Bearer ${jwtAuthToken}` + } + }); + expect(userInfoRes.statusCode).toEqual(200); + const { user: userInfo } = JSON.parse(userInfoRes.payload); + + const privateKey = await getUserPrivateKey(seedData1.password, userInfo); + const projectKey = decryptAsymmetric({ + ciphertext: projectKeyEnc.encryptedKey, + nonce: projectKeyEnc.nonce, + publicKey: projectKeyEnc.sender.publicKey, + privateKey + }); + + const projectBotRes = await testServer.inject({ + method: "GET", + url: `/api/v1/bot/${seedData1.project.id}`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + } + }); + expect(projectBotRes.statusCode).toEqual(200); + const projectBot = JSON.parse(projectBotRes.payload).bot; + const botKey = encryptAsymmetric(projectKey, projectBot.publicKey, privateKey); + + // set bot as active + const setBotActive = await testServer.inject({ + method: "PATCH", + url: `/api/v1/bot/${projectBot.id}/active`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: { + isActive: true, + workspaceId: seedData1.project.id, + botKey: { + encryptedKey: botKey.ciphertext, + nonce: botKey.nonce + } + } + }); + expect(setBotActive.statusCode).toEqual(200); + + // create a deep folder + const folderCreate = await testServer.inject({ + method: "POST", + url: `/api/v1/folders`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + name: "folder", + path: "/nested1/nested2" + } + }); + expect(folderCreate.statusCode).toBe(200); + }); + + afterAll(async () => { + const projectBotRes = await testServer.inject({ + method: "GET", + url: `/api/v1/bot/${seedData1.project.id}`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + } + }); + expect(projectBotRes.statusCode).toEqual(200); + const projectBot = JSON.parse(projectBotRes.payload).bot; + + // set bot as inactive + const setBotInActive = await testServer.inject({ + method: "PATCH", + url: `/api/v1/bot/${projectBot.id}/active`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: { + isActive: false, + workspaceId: seedData1.project.id + } + }); + expect(setBotInActive.statusCode).toEqual(200); + }); + + const getSecrets = async (environment: string, secretPath = "/") => { + const res = await testServer.inject({ + method: "GET", + url: `/api/v3/secrets/raw`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + query: { + secretPath, + environment, + workspaceId: seedData1.project.id + } + }); + const secrets: { secretKey: string; secretValue: string; type: SecretType; version: number }[] = + JSON.parse(res.payload).secrets || []; + return secrets.map((el) => ({ key: el.secretKey, value: el.secretValue, type: el.type, version: el.version })); + }; + + test.each(testRawSecrets)("Create secret raw in path $path", async ({ secret, path }) => { + const createSecretReqBody = { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + type: SecretType.Shared, + secretValue: secret.value, + secretComment: secret.comment, + secretPath: path + }; + const createSecRes = await testServer.inject({ + method: "POST", + url: `/api/v3/secrets/raw/${secret.key}`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: createSecretReqBody + }); + expect(createSecRes.statusCode).toBe(200); + const createdSecretPayload = JSON.parse(createSecRes.payload); + expect(createdSecretPayload).toHaveProperty("secret"); + + // fetch secrets + const secrets = await getSecrets(seedData1.environment.slug, path); + expect(secrets).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + key: secret.key, + value: secret.value, + type: SecretType.Shared + }) + ]) + ); + }); + + test.each(testRawSecrets)("Get secret by name raw in path $path", async ({ secret, path }) => { + const getSecByNameRes = await testServer.inject({ + method: "GET", + url: `/api/v3/secrets/raw/${secret.key}`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + query: { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + secretPath: path + } + }); + expect(getSecByNameRes.statusCode).toBe(200); + const secretPayload = JSON.parse(getSecByNameRes.payload); + expect(secretPayload).toHaveProperty("secret"); + expect(secretPayload.secret).toEqual( + expect.objectContaining({ + secretKey: secret.key, + secretValue: secret.value + }) + ); + }); + + test.each(testRawSecrets)("Update secret raw in path $path", async ({ secret, path }) => { + const updateSecretReqBody = { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + type: SecretType.Shared, + secretValue: "new-value", + secretPath: path + }; + + const updateSecRes = await testServer.inject({ + method: "PATCH", + url: `/api/v3/secrets/raw/${secret.key}`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: updateSecretReqBody + }); + expect(updateSecRes.statusCode).toBe(200); + const updatedSecretPayload = JSON.parse(updateSecRes.payload); + expect(updatedSecretPayload).toHaveProperty("secret"); + + // fetch secrets + const secrets = await getSecrets(seedData1.environment.slug, path); + expect(secrets).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + key: secret.key, + value: "new-value", + version: 2, + type: SecretType.Shared + }) + ]) + ); + }); + + test.each(testRawSecrets)("Delete secret raw in path $path", async ({ path, secret }) => { + const deletedSecretReqBody = { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + type: SecretType.Shared, + secretPath: path + }; + const deletedSecRes = await testServer.inject({ + method: "DELETE", + url: `/api/v3/secrets/raw/${secret.key}`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: deletedSecretReqBody + }); + expect(deletedSecRes.statusCode).toBe(200); + const deletedSecretPayload = JSON.parse(deletedSecRes.payload); + expect(deletedSecretPayload).toHaveProperty("secret"); + + // fetch secrets + const secrets = await getSecrets(seedData1.environment.slug, path); + expect(secrets).toEqual( + expect.arrayContaining([ + expect.not.objectContaining({ + key: secret.key, + type: SecretType.Shared + }) + ]) + ); + }); +}); + +describe("Secret V3 Raw Router Without E2EE enabled", async () => { + const secret = { + key: "RAW-SEC-1", + value: "something-secret", + comment: "some comment" + }; + + test("Create secret raw", async () => { + const createSecretReqBody = { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + type: SecretType.Shared, + secretValue: secret.value, + secretComment: secret.comment + }; + const createSecRes = await testServer.inject({ + method: "POST", + url: `/api/v3/secrets/raw/${secret.key}`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: createSecretReqBody + }); + expect(createSecRes.statusCode).toBe(400); + }); + + test("Update secret raw", async () => { + const updateSecretReqBody = { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + type: SecretType.Shared, + secretValue: "new-value" + }; + const updateSecRes = await testServer.inject({ + method: "PATCH", + url: `/api/v3/secrets/raw/${secret.key}`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: updateSecretReqBody + }); + expect(updateSecRes.statusCode).toBe(400); + }); + + test("Delete secret raw", async () => { + const deletedSecretReqBody = { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + type: SecretType.Shared + }; + const deletedSecRes = await testServer.inject({ + method: "DELETE", + url: `/api/v3/secrets/raw/${secret.key}`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: deletedSecretReqBody + }); + expect(deletedSecRes.statusCode).toBe(400); + }); +}); diff --git a/backend/src/server/routes/v3/secret-router.ts b/backend/src/server/routes/v3/secret-router.ts index 5f47ae3c5f..1aac862050 100644 --- a/backend/src/server/routes/v3/secret-router.ts +++ b/backend/src/server/routes/v3/secret-router.ts @@ -1092,7 +1092,6 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { secrets: z .object({ secretName: z.string().trim(), - type: z.nativeEnum(SecretType).default(SecretType.Shared), secretKeyCiphertext: z.string().trim(), secretKeyIV: z.string().trim(), secretKeyTag: z.string().trim(), @@ -1139,7 +1138,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { projectId, policy, data: { - [CommitType.Create]: inputSecrets.filter(({ type }) => type === "shared") + [CommitType.Create]: inputSecrets } }); diff --git a/backend/src/services/secret/secret-dal.ts b/backend/src/services/secret/secret-dal.ts index ba65033cbe..c4b74eef88 100644 --- a/backend/src/services/secret/secret-dal.ts +++ b/backend/src/services/secret/secret-dal.ts @@ -57,6 +57,12 @@ export const secretDALFactory = (db: TDbClient) => { type: el.type, ...(el.type === SecretType.Personal ? { userId } : {}) }); + if (el.type === SecretType.Shared) { + void bd.orWhere({ + secretBlindIndex: el.blindIndex, + type: SecretType.Personal + }); + } }); }) .delete() From 90f09c7a787e2f55503b4a00569bbe75a7626da8 Mon Sep 17 00:00:00 2001 From: Akhil Mohan Date: Thu, 15 Feb 2024 20:02:13 +0530 Subject: [PATCH 03/29] feat(e2e): added service token integration tests --- backend/e2e-test/routes/v1/login.spec.ts | 3 +- .../e2e-test/routes/v2/service-token.spec.ts | 507 ++++++++++++++++++ 2 files changed, 509 insertions(+), 1 deletion(-) create mode 100644 backend/e2e-test/routes/v2/service-token.spec.ts diff --git a/backend/e2e-test/routes/v1/login.spec.ts b/backend/e2e-test/routes/v1/login.spec.ts index c95e1e0168..cd6ec31947 100644 --- a/backend/e2e-test/routes/v1/login.spec.ts +++ b/backend/e2e-test/routes/v1/login.spec.ts @@ -1,6 +1,7 @@ -import { seedData1 } from "@app/db/seed-data"; import jsrp from "jsrp"; +import { seedData1 } from "@app/db/seed-data"; + describe("Login V1 Router", async () => { // eslint-disable-next-line const client = new jsrp.client(); diff --git a/backend/e2e-test/routes/v2/service-token.spec.ts b/backend/e2e-test/routes/v2/service-token.spec.ts new file mode 100644 index 0000000000..e9843a2e7b --- /dev/null +++ b/backend/e2e-test/routes/v2/service-token.spec.ts @@ -0,0 +1,507 @@ +import crypto from "node:crypto"; + +import { SecretType, TSecrets } from "@app/db/schemas"; +import { decryptSecret, encryptSecret, getUserPrivateKey, seedData1 } from "@app/db/seed-data"; +import { decryptAsymmetric, decryptSymmetric128BitHexKeyUTF8, encryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto"; + +const createServiceToken = async ( + scopes: { environment: string; secretPath: string }[], + permissions: ("read" | "write")[] +) => { + const projectKeyRes = await testServer.inject({ + method: "GET", + url: `/api/v2/workspace/${seedData1.project.id}/encrypted-key`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + } + }); + const projectKeyEnc = JSON.parse(projectKeyRes.payload); + + const userInfoRes = await testServer.inject({ + method: "GET", + url: "/api/v2/users/me", + headers: { + authorization: `Bearer ${jwtAuthToken}` + } + }); + const { user: userInfo } = JSON.parse(userInfoRes.payload); + const privateKey = await getUserPrivateKey(seedData1.password, userInfo); + const projectKey = decryptAsymmetric({ + ciphertext: projectKeyEnc.encryptedKey, + nonce: projectKeyEnc.nonce, + publicKey: projectKeyEnc.sender.publicKey, + privateKey + }); + + const randomBytes = crypto.randomBytes(16).toString("hex"); + const { ciphertext, iv, tag } = encryptSymmetric128BitHexKeyUTF8(projectKey, randomBytes); + const serviceTokenRes = await testServer.inject({ + method: "POST", + url: "/api/v2/service-token", + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: { + name: "test-token", + workspaceId: seedData1.project.id, + scopes, + encryptedKey: ciphertext, + iv, + tag, + permissions, + expiresIn: null + } + }); + expect(serviceTokenRes.statusCode).toBe(200); + const serviceTokenInfo = serviceTokenRes.json(); + expect(serviceTokenInfo).toHaveProperty("serviceToken"); + expect(serviceTokenInfo).toHaveProperty("serviceTokenData"); + return `${serviceTokenInfo.serviceToken}.${randomBytes}`; +}; + +const deleteServiceToken = async () => { + const serviceTokenListRes = await testServer.inject({ + method: "GET", + url: `/api/v1/workspace/${seedData1.project.id}/service-token-data`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + } + }); + expect(serviceTokenListRes.statusCode).toBe(200); + const serviceTokens = JSON.parse(serviceTokenListRes.payload).serviceTokenData as { name: string; id: string }[]; + expect(serviceTokens.length).toBeGreaterThan(0); + const serviceTokenInfo = serviceTokens.find(({ name }) => name === "test-token"); + expect(serviceTokenInfo).toBeDefined(); + + const deleteTokenRes = await testServer.inject({ + method: "DELETE", + url: `/api/v2/service-token/${serviceTokenInfo?.id}`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + } + }); + expect(deleteTokenRes.statusCode).toBe(200); +}; + +describe("Service token secret ops", async () => { + let serviceToken = ""; + let projectKey = ""; + beforeAll(async () => { + serviceToken = await createServiceToken( + [{ secretPath: "/**", environment: seedData1.environment.slug }], + ["read", "write"] + ); + + // this is ensure cli service token decryptiong working fine + const serviceTokenInfoRes = await testServer.inject({ + method: "GET", + url: "/api/v2/service-token", + headers: { + authorization: `Bearer ${serviceToken}` + } + }); + expect(serviceTokenInfoRes.statusCode).toBe(200); + const serviceTokenInfo = serviceTokenInfoRes.json(); + const serviceTokenParts = serviceToken.split("."); + projectKey = decryptSymmetric128BitHexKeyUTF8({ + key: serviceTokenParts[3], + tag: serviceTokenInfo.tag, + ciphertext: serviceTokenInfo.encryptedKey, + iv: serviceTokenInfo.iv + }); + + // create a deep folder + const folderCreate = await testServer.inject({ + method: "POST", + url: `/api/v1/folders`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + name: "folder", + path: "/nested1/nested2" + } + }); + expect(folderCreate.statusCode).toBe(200); + }); + + afterAll(async () => { + await deleteServiceToken(); + }); + + const testSecrets = [ + { + path: "/", + secret: { + key: "ST-SEC", + value: "something-secret", + comment: "some comment" + } + }, + { + path: "/nested1/nested2/folder", + secret: { + key: "NESTED-ST-SEC", + value: "something-secret", + comment: "some comment" + } + } + ]; + + const getSecrets = async (environment: string, secretPath = "/") => { + const res = await testServer.inject({ + method: "GET", + url: `/api/v3/secrets`, + headers: { + authorization: `Bearer ${serviceToken}` + }, + query: { + secretPath, + environment, + workspaceId: seedData1.project.id + } + }); + const secrets: TSecrets[] = JSON.parse(res.payload).secrets || []; + return secrets.map((el) => ({ ...decryptSecret(projectKey, el), type: el.type })); + }; + + test.each(testSecrets)("Create secret in path $path", async ({ secret, path }) => { + const createSecretReqBody = { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + type: SecretType.Shared, + secretPath: path, + ...encryptSecret(projectKey, secret.key, secret.value, secret.comment) + }; + const createSecRes = await testServer.inject({ + method: "POST", + url: `/api/v3/secrets/${secret.key}`, + headers: { + authorization: `Bearer ${serviceToken}` + }, + body: createSecretReqBody + }); + expect(createSecRes.statusCode).toBe(200); + const createdSecretPayload = JSON.parse(createSecRes.payload); + expect(createdSecretPayload).toHaveProperty("secret"); + const decryptedSecret = decryptSecret(projectKey, createdSecretPayload.secret); + expect(decryptedSecret.key).toEqual(secret.key); + expect(decryptedSecret.value).toEqual(secret.value); + expect(decryptedSecret.comment).toEqual(secret.comment); + expect(decryptedSecret.version).toEqual(1); + + const secrets = await getSecrets(seedData1.environment.slug, path); + expect(secrets).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + key: secret.key, + value: secret.value, + type: SecretType.Shared + }) + ]) + ); + }); + + test.each(testSecrets)("Get secret by name in path $path", async ({ secret, path }) => { + const getSecByNameRes = await testServer.inject({ + method: "GET", + url: `/api/v3/secrets/${secret.key}`, + headers: { + authorization: `Bearer ${serviceToken}` + }, + query: { + secretPath: path, + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug + } + }); + expect(getSecByNameRes.statusCode).toBe(200); + const getSecretByNamePayload = JSON.parse(getSecByNameRes.payload); + expect(getSecretByNamePayload).toHaveProperty("secret"); + const decryptedSecret = decryptSecret(projectKey, getSecretByNamePayload.secret); + expect(decryptedSecret.key).toEqual(secret.key); + expect(decryptedSecret.value).toEqual(secret.value); + expect(decryptedSecret.comment).toEqual(secret.comment); + }); + + test.each(testSecrets)("Update secret in path $path", async ({ path, secret }) => { + const updateSecretReqBody = { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + type: SecretType.Shared, + secretPath: path, + ...encryptSecret(projectKey, secret.key, "new-value", secret.comment) + }; + const updateSecRes = await testServer.inject({ + method: "PATCH", + url: `/api/v3/secrets/${secret.key}`, + headers: { + authorization: `Bearer ${serviceToken}` + }, + body: updateSecretReqBody + }); + expect(updateSecRes.statusCode).toBe(200); + const updatedSecretPayload = JSON.parse(updateSecRes.payload); + expect(updatedSecretPayload).toHaveProperty("secret"); + const decryptedSecret = decryptSecret(projectKey, updatedSecretPayload.secret); + expect(decryptedSecret.key).toEqual(secret.key); + expect(decryptedSecret.value).toEqual("new-value"); + expect(decryptedSecret.comment).toEqual(secret.comment); + + // list secret should have updated value + const secrets = await getSecrets(seedData1.environment.slug, path); + expect(secrets).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + key: secret.key, + value: "new-value", + type: SecretType.Shared + }) + ]) + ); + }); + + test.each(testSecrets)("Delete secret in path $path", async ({ secret, path }) => { + const deleteSecRes = await testServer.inject({ + method: "DELETE", + url: `/api/v3/secrets/${secret.key}`, + headers: { + authorization: `Bearer ${serviceToken}` + }, + body: { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + secretPath: path + } + }); + expect(deleteSecRes.statusCode).toBe(200); + const updatedSecretPayload = JSON.parse(deleteSecRes.payload); + expect(updatedSecretPayload).toHaveProperty("secret"); + const decryptedSecret = decryptSecret(projectKey, updatedSecretPayload.secret); + expect(decryptedSecret.key).toEqual(secret.key); + + // shared secret deletion should delete personal ones also + const secrets = await getSecrets(seedData1.environment.slug, path); + expect(secrets).toEqual( + expect.not.arrayContaining([ + expect.objectContaining({ + key: secret.key, + type: SecretType.Shared + }) + ]) + ); + }); + + test.each(testSecrets)("Bulk create secrets in path $path", async ({ secret, path }) => { + const createSharedSecRes = await testServer.inject({ + method: "POST", + url: `/api/v3/secrets/batch`, + headers: { + authorization: `Bearer ${serviceToken}` + }, + body: { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + secretPath: path, + secrets: Array.from(Array(10)).map((_e, i) => ({ + secretName: `BULK-${secret.key}-${i + 1}`, + ...encryptSecret(projectKey, `BULK-${secret.key}-${i + 1}`, secret.value, secret.comment) + })) + } + }); + expect(createSharedSecRes.statusCode).toBe(200); + const createSharedSecPayload = JSON.parse(createSharedSecRes.payload); + expect(createSharedSecPayload).toHaveProperty("secrets"); + + // bulk ones should exist + const secrets = await getSecrets(seedData1.environment.slug, path); + expect(secrets).toEqual( + expect.arrayContaining( + Array.from(Array(10)).map((_e, i) => + expect.objectContaining({ + key: `BULK-${secret.key}-${i + 1}`, + type: SecretType.Shared + }) + ) + ) + ); + }); + + test.each(testSecrets)("Bulk create fail on existing secret in path $path", async ({ secret, path }) => { + const createSharedSecRes = await testServer.inject({ + method: "POST", + url: `/api/v3/secrets/batch`, + headers: { + authorization: `Bearer ${serviceToken}` + }, + body: { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + secretPath: path, + secrets: Array.from(Array(10)).map((_e, i) => ({ + secretName: `BULK-${secret.key}-${i + 1}`, + ...encryptSecret(projectKey, `BULK-${secret.key}-${i + 1}`, secret.value, secret.comment) + })) + } + }); + expect(createSharedSecRes.statusCode).toBe(400); + }); + + test.each(testSecrets)("Bulk update secrets in path $path", async ({ secret, path }) => { + const updateSharedSecRes = await testServer.inject({ + method: "PATCH", + url: `/api/v3/secrets/batch`, + headers: { + authorization: `Bearer ${serviceToken}` + }, + body: { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + secretPath: path, + secrets: Array.from(Array(10)).map((_e, i) => ({ + secretName: `BULK-${secret.key}-${i + 1}`, + ...encryptSecret(projectKey, `BULK-${secret.key}-${i + 1}`, "update-value", secret.comment) + })) + } + }); + expect(updateSharedSecRes.statusCode).toBe(200); + const updateSharedSecPayload = JSON.parse(updateSharedSecRes.payload); + expect(updateSharedSecPayload).toHaveProperty("secrets"); + + // bulk ones should exist + const secrets = await getSecrets(seedData1.environment.slug, path); + expect(secrets).toEqual( + expect.arrayContaining( + Array.from(Array(10)).map((_e, i) => + expect.objectContaining({ + key: `BULK-${secret.key}-${i + 1}`, + value: "update-value", + type: SecretType.Shared + }) + ) + ) + ); + }); + + test.each(testSecrets)("Bulk delete secrets in path $path", async ({ secret, path }) => { + const updateSharedSecRes = await testServer.inject({ + method: "DELETE", + url: `/api/v3/secrets/batch`, + headers: { + authorization: `Bearer ${serviceToken}` + }, + body: { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + secretPath: path, + secrets: Array.from(Array(10)).map((_e, i) => ({ + secretName: `BULK-${secret.key}-${i + 1}` + })) + } + }); + expect(updateSharedSecRes.statusCode).toBe(200); + const updateSharedSecPayload = JSON.parse(updateSharedSecRes.payload); + expect(updateSharedSecPayload).toHaveProperty("secrets"); + + // bulk ones should exist + const secrets = await getSecrets(seedData1.environment.slug, path); + expect(secrets).toEqual( + expect.not.arrayContaining( + Array.from(Array(10)).map((_e, i) => + expect.objectContaining({ + key: `BULK-${secret.value}-${i + 1}`, + type: SecretType.Shared + }) + ) + ) + ); + }); +}); + +describe("Service token fail cases", async () => { + test("Unauthorized secret path access", async () => { + const serviceToken = await createServiceToken( + [{ secretPath: "/", environment: seedData1.environment.slug }], + ["read", "write"] + ); + const fetchSecrets = await testServer.inject({ + method: "GET", + url: "/api/v3/secrets", + query: { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + secretPath: "/nested/deep" + }, + headers: { + authorization: `Bearer ${serviceToken}` + } + }); + expect(fetchSecrets.statusCode).toBe(401); + expect(fetchSecrets.json().error).toBe("PermissionDenied"); + await deleteServiceToken(); + }); + + test("Unauthorized secret environment access", async () => { + const serviceToken = await createServiceToken( + [{ secretPath: "/", environment: seedData1.environment.slug }], + ["read", "write"] + ); + const fetchSecrets = await testServer.inject({ + method: "GET", + url: "/api/v3/secrets", + query: { + workspaceId: seedData1.project.id, + environment: "prod", + secretPath: "/" + }, + headers: { + authorization: `Bearer ${serviceToken}` + } + }); + expect(fetchSecrets.statusCode).toBe(401); + expect(fetchSecrets.json().error).toBe("PermissionDenied"); + await deleteServiceToken(); + }); + + test("Unauthorized write operation", async () => { + const serviceToken = await createServiceToken( + [{ secretPath: "/", environment: seedData1.environment.slug }], + ["read"] + ); + const writeSecrets = await testServer.inject({ + method: "POST", + url: `/api/v3/secrets/NEW`, + body: { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + type: SecretType.Shared, + secretPath: "/", + // doesn't matter project key because this will fail before that due to read only access + ...encryptSecret(crypto.randomBytes(16).toString("hex"), "NEW", "value", "") + }, + headers: { + authorization: `Bearer ${serviceToken}` + } + }); + expect(writeSecrets.statusCode).toBe(401); + expect(writeSecrets.json().error).toBe("PermissionDenied"); + + // but read access should still work fine + const fetchSecrets = await testServer.inject({ + method: "GET", + url: "/api/v3/secrets", + query: { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + secretPath: "/" + }, + headers: { + authorization: `Bearer ${serviceToken}` + } + }); + expect(fetchSecrets.statusCode).toBe(200); + await deleteServiceToken(); + }); +}); From 4c49119ac55839dfec0af0be7f086766a86c2e83 Mon Sep 17 00:00:00 2001 From: Akhil Mohan Date: Thu, 15 Feb 2024 20:02:38 +0530 Subject: [PATCH 04/29] feat(e2e): added identity token secret access integration tests --- backend/e2e-test/routes/v1/identity.spec.ts | 281 ++++++++++++++++++++ backend/e2e-test/vitest-environment-knex.ts | 1 + 2 files changed, 282 insertions(+) create mode 100644 backend/e2e-test/routes/v1/identity.spec.ts diff --git a/backend/e2e-test/routes/v1/identity.spec.ts b/backend/e2e-test/routes/v1/identity.spec.ts new file mode 100644 index 0000000000..f30ba7b1e2 --- /dev/null +++ b/backend/e2e-test/routes/v1/identity.spec.ts @@ -0,0 +1,281 @@ +import { SecretType } from "@app/db/schemas"; +import { getUserPrivateKey, seedData1 } from "@app/db/seed-data"; +import { decryptAsymmetric, encryptAsymmetric } from "@app/lib/crypto"; + +describe("Identity token secret ops", async () => { + let identityToken = ""; + beforeAll(async () => { + // enable bot + const res = await testServer.inject({ + method: "GET", + url: `/api/v2/workspace/${seedData1.project.id}/encrypted-key`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + } + }); + expect(res.statusCode).toEqual(200); + const projectKeyEnc = JSON.parse(res.payload); + + const userInfoRes = await testServer.inject({ + method: "GET", + url: "/api/v2/users/me", + headers: { + authorization: `Bearer ${jwtAuthToken}` + } + }); + expect(userInfoRes.statusCode).toEqual(200); + const { user: userInfo } = JSON.parse(userInfoRes.payload); + + const privateKey = await getUserPrivateKey(seedData1.password, userInfo); + const projectKey = decryptAsymmetric({ + ciphertext: projectKeyEnc.encryptedKey, + nonce: projectKeyEnc.nonce, + publicKey: projectKeyEnc.sender.publicKey, + privateKey + }); + + const projectBotRes = await testServer.inject({ + method: "GET", + url: `/api/v1/bot/${seedData1.project.id}`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + } + }); + expect(projectBotRes.statusCode).toEqual(200); + const projectBot = JSON.parse(projectBotRes.payload).bot; + const botKey = encryptAsymmetric(projectKey, projectBot.publicKey, privateKey); + + // set bot as active + const setBotActive = await testServer.inject({ + method: "PATCH", + url: `/api/v1/bot/${projectBot.id}/active`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: { + isActive: true, + workspaceId: seedData1.project.id, + botKey: { + encryptedKey: botKey.ciphertext, + nonce: botKey.nonce + } + } + }); + expect(setBotActive.statusCode).toEqual(200); + + // set identity token + const identityLogin = await testServer.inject({ + method: "POST", + url: "/api/v1/auth/universal-auth/login", + body: { + clientSecret: seedData1.machineIdentity.clientCred.secret, + clientId: seedData1.machineIdentity.clientCred.id + } + }); + expect(identityLogin.statusCode).toBe(200); + identityToken = identityLogin.json().accessToken; + + // create a deep folder + const folderCreate = await testServer.inject({ + method: "POST", + url: `/api/v1/folders`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + name: "folder", + path: "/nested1/nested2" + } + }); + expect(folderCreate.statusCode).toBe(200); + }); + + afterAll(async () => { + const projectBotRes = await testServer.inject({ + method: "GET", + url: `/api/v1/bot/${seedData1.project.id}`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + } + }); + expect(projectBotRes.statusCode).toEqual(200); + const projectBot = JSON.parse(projectBotRes.payload).bot; + + // set bot as inactive + const setBotInActive = await testServer.inject({ + method: "PATCH", + url: `/api/v1/bot/${projectBot.id}/active`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: { + isActive: false, + workspaceId: seedData1.project.id + } + }); + expect(setBotInActive.statusCode).toEqual(200); + }); + + const testRawSecrets = [ + { + path: "/", + secret: { + key: "ID-SEC", + value: "something-secret", + comment: "some comment" + } + }, + { + path: "/nested1/nested2/folder", + secret: { + key: "NESTED-ID-SEC", + value: "something-secret", + comment: "some comment" + } + } + ]; + + const getSecrets = async (environment: string, secretPath = "/") => { + const res = await testServer.inject({ + method: "GET", + url: `/api/v3/secrets/raw`, + headers: { + authorization: `Bearer ${identityToken}` + }, + query: { + secretPath, + environment, + workspaceId: seedData1.project.id + } + }); + const secrets: { secretKey: string; secretValue: string; type: SecretType; version: number }[] = + JSON.parse(res.payload).secrets || []; + return secrets.map((el) => ({ key: el.secretKey, value: el.secretValue, type: el.type, version: el.version })); + }; + + test.each(testRawSecrets)("Create secret raw in path $path", async ({ secret, path }) => { + const createSecretReqBody = { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + type: SecretType.Shared, + secretValue: secret.value, + secretComment: secret.comment, + secretPath: path + }; + const createSecRes = await testServer.inject({ + method: "POST", + url: `/api/v3/secrets/raw/${secret.key}`, + headers: { + authorization: `Bearer ${identityToken}` + }, + body: createSecretReqBody + }); + expect(createSecRes.statusCode).toBe(200); + const createdSecretPayload = JSON.parse(createSecRes.payload); + expect(createdSecretPayload).toHaveProperty("secret"); + + // fetch secrets + const secrets = await getSecrets(seedData1.environment.slug, path); + expect(secrets).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + key: secret.key, + value: secret.value, + type: SecretType.Shared + }) + ]) + ); + }); + + test.each(testRawSecrets)("Get secret by name raw in path $path", async ({ secret, path }) => { + const getSecByNameRes = await testServer.inject({ + method: "GET", + url: `/api/v3/secrets/raw/${secret.key}`, + headers: { + authorization: `Bearer ${identityToken}` + }, + query: { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + secretPath: path + } + }); + expect(getSecByNameRes.statusCode).toBe(200); + const secretPayload = JSON.parse(getSecByNameRes.payload); + expect(secretPayload).toHaveProperty("secret"); + expect(secretPayload.secret).toEqual( + expect.objectContaining({ + secretKey: secret.key, + secretValue: secret.value + }) + ); + }); + + test.each(testRawSecrets)("Update secret raw in path $path", async ({ secret, path }) => { + const updateSecretReqBody = { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + type: SecretType.Shared, + secretValue: "new-value", + secretPath: path + }; + + const updateSecRes = await testServer.inject({ + method: "PATCH", + url: `/api/v3/secrets/raw/${secret.key}`, + headers: { + authorization: `Bearer ${identityToken}` + }, + body: updateSecretReqBody + }); + expect(updateSecRes.statusCode).toBe(200); + const updatedSecretPayload = JSON.parse(updateSecRes.payload); + expect(updatedSecretPayload).toHaveProperty("secret"); + + // fetch secrets + const secrets = await getSecrets(seedData1.environment.slug, path); + expect(secrets).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + key: secret.key, + value: "new-value", + version: 2, + type: SecretType.Shared + }) + ]) + ); + }); + + test.each(testRawSecrets)("Delete secret raw in path $path", async ({ path, secret }) => { + const deletedSecretReqBody = { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + type: SecretType.Shared, + secretPath: path + }; + const deletedSecRes = await testServer.inject({ + method: "DELETE", + url: `/api/v3/secrets/raw/${secret.key}`, + headers: { + authorization: `Bearer ${identityToken}` + }, + body: deletedSecretReqBody + }); + expect(deletedSecRes.statusCode).toBe(200); + const deletedSecretPayload = JSON.parse(deletedSecRes.payload); + expect(deletedSecretPayload).toHaveProperty("secret"); + + // fetch secrets + const secrets = await getSecrets(seedData1.environment.slug, path); + expect(secrets).toEqual( + expect.arrayContaining([ + expect.not.objectContaining({ + key: secret.key, + type: SecretType.Shared + }) + ]) + ); + }); +}); diff --git a/backend/e2e-test/vitest-environment-knex.ts b/backend/e2e-test/vitest-environment-knex.ts index aad12fd9b4..7125ab0fed 100644 --- a/backend/e2e-test/vitest-environment-knex.ts +++ b/backend/e2e-test/vitest-environment-knex.ts @@ -1,3 +1,4 @@ +// eslint-disable-next-line import "ts-node/register"; import dotenv from "dotenv"; From b24d748462e5394641af8a468e9af9d050b3bdfa Mon Sep 17 00:00:00 2001 From: Akhil Mohan Date: Thu, 15 Feb 2024 20:03:38 +0530 Subject: [PATCH 05/29] feat(e2e): added gh action for execution of integration tests on PR and release --- .github/workflows/run-backend-tests.yml | 47 +++++++++++++++++++++++++ backend/src/server/app.ts | 2 +- 2 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/run-backend-tests.yml diff --git a/.github/workflows/run-backend-tests.yml b/.github/workflows/run-backend-tests.yml new file mode 100644 index 0000000000..41870efb53 --- /dev/null +++ b/.github/workflows/run-backend-tests.yml @@ -0,0 +1,47 @@ +name: "Run backend tests" + +on: + pull_request: + types: [opened, synchronize] + paths: + - "backend/**" + - "!backend/README.md" + - "!backend/.*" + - "backend/.eslintrc.js" + push: + tags: + - "infisical/v*.*.*-postgres" +jobs: + check-be-pr: + name: Run integration test + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: ☁️ Checkout source + uses: actions/checkout@v3 + - uses: KengoTODA/actions-setup-docker-compose@v1 + if: ${{ env.ACT }} + name: Install `docker-compose` for local simulations + with: + version: "2.14.2" + - 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: Start postgres and redis + run: touch .env && docker-compose -f docker-compose.pg.yml up -d db redis + - name: Start integration test + run: npm run test:e2e + working-directory: backend + env: + REDIS_URL: redis://172.17.0.1:6379 + DB_CONNECTION_URI: postgres://infisical:infisical@172.17.0.1:5432/infisical?sslmode=disable + AUTH_SECRET: something-random + - name: cleanup + run: | + docker-compose -f "docker-compose.pg.yml" down diff --git a/backend/src/server/app.ts b/backend/src/server/app.ts index ca6b9003a1..969d2fe43d 100644 --- a/backend/src/server/app.ts +++ b/backend/src/server/app.ts @@ -37,7 +37,7 @@ type TMain = { export const main = async ({ db, smtp, logger, queue }: TMain) => { const appCfg = getConfig(); const server = fasitfy({ - logger, + logger: appCfg.NODE_ENV === "test" ? false : logger, trustProxy: true, connectionTimeout: 30 * 1000, ignoreTrailingSlash: true From 793440feb60fd37f918c4b293dc238e441c96230 Mon Sep 17 00:00:00 2001 From: Akhil Mohan Date: Thu, 15 Feb 2024 20:30:12 +0530 Subject: [PATCH 06/29] feat(e2e): made rebased changes --- .github/workflows/run-backend-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-backend-tests.yml b/.github/workflows/run-backend-tests.yml index 41870efb53..beb9a6c2bc 100644 --- a/.github/workflows/run-backend-tests.yml +++ b/.github/workflows/run-backend-tests.yml @@ -34,7 +34,7 @@ jobs: run: npm install working-directory: backend - name: Start postgres and redis - run: touch .env && docker-compose -f docker-compose.pg.yml up -d db redis + run: touch .env && docker-compose -f docker-compose.dev.yml up -d db redis - name: Start integration test run: npm run test:e2e working-directory: backend From c71af00146bf2512c856145b9112c324d33b8c70 Mon Sep 17 00:00:00 2001 From: Akhil Mohan Date: Thu, 15 Feb 2024 20:38:44 +0530 Subject: [PATCH 07/29] feat(e2e): added enc key on gh action for be test file --- .github/workflows/run-backend-tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/run-backend-tests.yml b/.github/workflows/run-backend-tests.yml index beb9a6c2bc..74e595049e 100644 --- a/.github/workflows/run-backend-tests.yml +++ b/.github/workflows/run-backend-tests.yml @@ -42,6 +42,7 @@ jobs: REDIS_URL: redis://172.17.0.1:6379 DB_CONNECTION_URI: postgres://infisical:infisical@172.17.0.1:5432/infisical?sslmode=disable AUTH_SECRET: something-random + ENCRYPTION_KEY: 4bnfe4e407b8921c104518903515b218 - name: cleanup run: | docker-compose -f "docker-compose.pg.yml" down From 4f3cf046fa05668adef818b8d6ed27c1199fa615 Mon Sep 17 00:00:00 2001 From: Akhil Mohan Date: Thu, 15 Feb 2024 20:43:40 +0530 Subject: [PATCH 08/29] feat(e2e): fixed post clean up error on gh be integration action --- .github/workflows/run-backend-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-backend-tests.yml b/.github/workflows/run-backend-tests.yml index 74e595049e..2a805eab79 100644 --- a/.github/workflows/run-backend-tests.yml +++ b/.github/workflows/run-backend-tests.yml @@ -45,4 +45,4 @@ jobs: ENCRYPTION_KEY: 4bnfe4e407b8921c104518903515b218 - name: cleanup run: | - docker-compose -f "docker-compose.pg.yml" down + docker-compose -f "docker-compose.dev.yml" down From b84579b8665990c9a0307794524962c4e038c5d4 Mon Sep 17 00:00:00 2001 From: Akhil Mohan Date: Thu, 15 Feb 2024 21:06:18 +0530 Subject: [PATCH 09/29] feat(e2e): changed to root .env.test and added .env.test.example --- .env.test.example | 7 +++++++ backend/e2e-test/vitest-environment-knex.ts | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 .env.test.example diff --git a/.env.test.example b/.env.test.example new file mode 100644 index 0000000000..2bbeccb9c6 --- /dev/null +++ b/.env.test.example @@ -0,0 +1,7 @@ +DB_CONNECTION_URI=postgres://infisical:infisical@localhost:5430/infisical-test +REDIS_URL=redis://redis:6379 +# Keys +# Required key for platform encryption/decryption ops +# THIS IS A SAMPLE ENCRYPTION KEY AND SHOULD NOT BE USED FOR PRODUCTION +ENCRYPTION_KEY=6c1fe4e407b8911c104518103505b218 +AUTH_SECRET=something-random diff --git a/backend/e2e-test/vitest-environment-knex.ts b/backend/e2e-test/vitest-environment-knex.ts index 7125ab0fed..7c58fb0af9 100644 --- a/backend/e2e-test/vitest-environment-knex.ts +++ b/backend/e2e-test/vitest-environment-knex.ts @@ -15,7 +15,7 @@ import { AuthTokenType } from "@app/services/auth/auth-type"; import { mockQueue } from "./mocks/queue"; import { mockSmtpServer } from "./mocks/smtp"; -dotenv.config({ path: path.join(__dirname, "../.env.test") }); +dotenv.config({ path: path.join(__dirname, "../../.env.test"), debug: true }); export default { name: "knex-env", transformMode: "ssr", From 7b5410916864f3933058427dd92ebe50878ffa73 Mon Sep 17 00:00:00 2001 From: snyk-bot Date: Sat, 17 Feb 2024 04:00:04 +0000 Subject: [PATCH 10/29] fix: upgrade sharp from 0.32.6 to 0.33.2 Snyk has created this PR to upgrade sharp from 0.32.6 to 0.33.2. See this package in npm: https://www.npmjs.com/package/sharp See this project in Snyk: https://app.snyk.io/org/maidul98/project/53d4ecb6-6cc1-4918-aa73-bf9cae4ffd13?utm_source=github&utm_medium=referral&page=upgrade-pr --- frontend/package-lock.json | 636 ++++++++++++++++++++++++++++++++++--- frontend/package.json | 2 +- 2 files changed, 599 insertions(+), 39 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index f28920f064..e63b1ffbf2 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,5 +1,5 @@ { - "name": "frontend", + "name": "npm-proj-1708142380787-0.9952765718063858vJAsWg", "lockfileVersion": 3, "requires": true, "packages": { @@ -84,7 +84,7 @@ "react-table": "^7.8.0", "sanitize-html": "^2.11.0", "set-cookie-parser": "^2.5.1", - "sharp": "^0.32.6", + "sharp": "^0.33.2", "styled-components": "^5.3.7", "tailwind-merge": "^1.8.1", "tweetnacl": "^1.0.3", @@ -2487,6 +2487,15 @@ "react": ">=16.8.0" } }, + "node_modules/@emnapi/runtime": { + "version": "0.45.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-0.45.0.tgz", + "integrity": "sha512-Txumi3td7J4A/xTTwlssKieHKTGl3j4A1tglBx72auZ49YK7ePY6XZricgIg9mnZT4xPfA+UPCUdnhRuEFDL+w==", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@emotion/babel-plugin": { "version": "11.11.0", "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz", @@ -3243,6 +3252,437 @@ "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", "dev": true }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.2.tgz", + "integrity": "sha512-itHBs1rPmsmGF9p4qRe++CzCgd+kFYktnsoR1sbIAfsRMrJZau0Tt1AH9KVnufc2/tU02Gf6Ibujx+15qRE03w==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "glibc": ">=2.26", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.0.1" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.2.tgz", + "integrity": "sha512-/rK/69Rrp9x5kaWBjVN07KixZanRr+W1OiyKdXcbjQD6KbW+obaTeBBtLUAtbBsnlTTmWthw99xqoOS7SsySDg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "glibc": ">=2.26", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.0.1" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.1.tgz", + "integrity": "sha512-kQyrSNd6lmBV7O0BUiyu/OEw9yeNGFbQhbxswS1i6rMDwBBSX+e+rPzu3S+MwAiGU3HdLze3PanQ4Xkfemgzcw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "macos": ">=11", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.1.tgz", + "integrity": "sha512-eVU/JYLPVjhhrd8Tk6gosl5pVlvsqiFlt50wotCvdkFGf+mDNBJxMh+bvav+Wt3EBnNZWq8Sp2I7XfSjm8siog==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "macos": ">=10.13", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.1.tgz", + "integrity": "sha512-FtdMvR4R99FTsD53IA3LxYGghQ82t3yt0ZQ93WMZ2xV3dqrb0E8zq4VHaTOuLEAuA83oDawHV3fd+BsAPadHIQ==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.28", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.1.tgz", + "integrity": "sha512-bnGG+MJjdX70mAQcSLxgeJco11G+MxTz+ebxlz8Y3dxyeb3Nkl7LgLI0mXupoO+u1wRNx/iRj5yHtzA4sde1yA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.26", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.1.tgz", + "integrity": "sha512-3+rzfAR1YpMOeA2zZNp+aYEzGNWK4zF3+sdMxuCS3ey9HhDbJ66w6hDSHDMoap32DueFwhhs3vwooAB2MaK4XQ==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.28", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.1.tgz", + "integrity": "sha512-3NR1mxFsaSgMMzz1bAnnKbSAI+lHXVTqAHgc1bgzjHuXjo4hlscpUxc0vFSAPKI3yuzdzcZOkq7nDPrP2F8Jgw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.26", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.1.tgz", + "integrity": "sha512-5aBRcjHDG/T6jwC3Edl3lP8nl9U2Yo8+oTl5drd1dh9Z1EBfzUKAJFUDTDisDjUwc7N4AjnPGfCA3jl3hY8uDg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "musl": ">=1.2.2", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.1.tgz", + "integrity": "sha512-dcT7inI9DBFK6ovfeWRe3hG30h51cBAP5JXlZfx6pzc/Mnf9HFCQDLtYf4MCBjxaaTfjCCjkBxcy3XzOAo5txw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "musl": ">=1.2.2", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.2.tgz", + "integrity": "sha512-Fndk/4Zq3vAc4G/qyfXASbS3HBZbKrlnKZLEJzPLrXoJuipFNNwTes71+Ki1hwYW5lch26niRYoZFAtZVf3EGA==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.28", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.1" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.2.tgz", + "integrity": "sha512-pz0NNo882vVfqJ0yNInuG9YH71smP4gRSdeL09ukC2YLE6ZyZePAlWKEHgAzJGTiOh8Qkaov6mMIMlEhmLdKew==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.26", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.1" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.2.tgz", + "integrity": "sha512-MBoInDXDppMfhSzbMmOQtGfloVAflS2rP1qPcUIiITMi36Mm5YR7r0ASND99razjQUpHTzjrU1flO76hKvP5RA==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.28", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.0.1" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.2.tgz", + "integrity": "sha512-xUT82H5IbXewKkeF5aiooajoO1tQV4PnKfS/OZtb5DDdxS/FCI/uXTVZ35GQ97RZXsycojz/AJ0asoz6p2/H/A==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.26", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.1" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.2.tgz", + "integrity": "sha512-F+0z8JCu/UnMzg8IYW1TMeiViIWBVg7IWP6nE0p5S5EPQxlLd76c8jYemG21X99UzFwgkRo5yz2DS+zbrnxZeA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "musl": ">=1.2.2", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.1" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.2.tgz", + "integrity": "sha512-+ZLE3SQmSL+Fn1gmSaM8uFusW5Y3J9VOf+wMGNnTtJUMUxFhv+P4UPaYEYT8tqnyYVaOVGgMN/zsOxn9pSsO2A==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "musl": ">=1.2.2", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.1" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.2.tgz", + "integrity": "sha512-fLbTaESVKuQcpm8ffgBD7jLb/CQLcATju/jxtTXR1XCLwbOQt+OL5zPHSDMmp2JZIeq82e18yE0Vv7zh6+6BfQ==", + "cpu": [ + "wasm32" + ], + "optional": true, + "dependencies": { + "@emnapi/runtime": "^0.45.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.2.tgz", + "integrity": "sha512-okBpql96hIGuZ4lN3+nsAjGeggxKm7hIRu9zyec0lnfB8E7Z6p95BuRZzDDXZOl2e8UmR4RhYt631i7mfmKU8g==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.2.tgz", + "integrity": "sha512-E4magOks77DK47FwHUIGH0RYWSgRBfGdK56kIHSVeB9uIS4pPFr4N2kIVsXdQQo4LzOsENKV5KAhRlRL7eMAdg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -6574,6 +7014,29 @@ "node": ">=10" } }, + "node_modules/@storybook/nextjs/node_modules/sharp": { + "version": "0.32.6", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.6.tgz", + "integrity": "sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.2", + "node-addon-api": "^6.1.0", + "prebuild-install": "^7.1.1", + "semver": "^7.5.4", + "simple-get": "^4.0.1", + "tar-fs": "^3.0.4", + "tunnel-agent": "^0.6.0" + }, + "engines": { + "node": ">=14.15.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@storybook/nextjs/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -8939,9 +9402,10 @@ } }, "node_modules/b4a": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.4.tgz", - "integrity": "sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==" + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz", + "integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==", + "dev": true }, "node_modules/babel-core": { "version": "7.0.0-bridge.0", @@ -9240,6 +9704,43 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/bare-events": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.2.0.tgz", + "integrity": "sha512-Yyyqff4PIFfSuthCZqLlPISTWHmnQxoPuAvkmgzsJEmG3CesdIv6Xweayl0JkCZJSB2yYIdJyEz97tpxNhgjbg==", + "dev": true, + "optional": true + }, + "node_modules/bare-fs": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.1.5.tgz", + "integrity": "sha512-5t0nlecX+N2uJqdxe9d18A98cp2u9BETelbjKpiVgQqzzmVNFYWEAjQHqS+2Khgto1vcwhik9cXucaj5ve2WWA==", + "dev": true, + "optional": true, + "dependencies": { + "bare-events": "^2.0.0", + "bare-os": "^2.0.0", + "bare-path": "^2.0.0", + "streamx": "^2.13.0" + } + }, + "node_modules/bare-os": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.2.0.tgz", + "integrity": "sha512-hD0rOPfYWOMpVirTACt4/nK8mC55La12K5fY1ij8HAdfQakD62M+H4o4tpfKzVGLgRDTuk3vjA4GqGXXCeFbag==", + "dev": true, + "optional": true + }, + "node_modules/bare-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-2.1.0.tgz", + "integrity": "sha512-DIIg7ts8bdRKwJRJrUMy/PICEaQZaPGZ26lsSx9MJSwIhSrcdHn7/C8W+XmnG/rKi6BaRcz+JO00CjZteybDtw==", + "dev": true, + "optional": true, + "dependencies": { + "bare-os": "^2.1.0" + } + }, "node_modules/base64-arraybuffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", @@ -9253,6 +9754,7 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, "funding": [ { "type": "github", @@ -9336,6 +9838,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -9346,6 +9849,7 @@ "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -9624,6 +10128,7 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, "funding": [ { "type": "github", @@ -10941,6 +11446,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, "dependencies": { "mimic-response": "^3.1.0" }, @@ -10993,6 +11499,7 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, "engines": { "node": ">=4.0.0" } @@ -11634,6 +12141,7 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, "dependencies": { "once": "^1.4.0" } @@ -12708,6 +13216,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "dev": true, "engines": { "node": ">=6" } @@ -12849,7 +13358,8 @@ "node_modules/fast-fifo": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", - "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==" + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true }, "node_modules/fast-glob": { "version": "3.3.2", @@ -13435,7 +13945,8 @@ "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true }, "node_modules/fs-extra": { "version": "11.2.0", @@ -13686,7 +14197,8 @@ "node_modules/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "dev": true }, "node_modules/github-slugger": { "version": "1.5.0", @@ -14412,6 +14924,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, "funding": [ { "type": "github", @@ -16856,6 +17369,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, "engines": { "node": ">=10" }, @@ -16959,7 +17473,8 @@ "node_modules/mkdirp-classic": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true }, "node_modules/mri": { "version": "1.2.0", @@ -17008,7 +17523,8 @@ "node_modules/napi-build-utils": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", - "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==", + "dev": true }, "node_modules/natural-compare": { "version": "1.4.0", @@ -17185,6 +17701,7 @@ "version": "3.54.0", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.54.0.tgz", "integrity": "sha512-p7eGEiQil0YUV3ItH4/tBb781L5impVmmx2E9FRKF7d18XXzp4PGT2tdYMFY6wQqgxD0IwNZOiSJ0/K0fSi/OA==", + "dev": true, "dependencies": { "semver": "^7.3.5" }, @@ -17196,6 +17713,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -17204,9 +17722,10 @@ } }, "node_modules/node-abi/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, "dependencies": { "lru-cache": "^6.0.0" }, @@ -17220,7 +17739,8 @@ "node_modules/node-abi/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true }, "node_modules/node-abort-controller": { "version": "3.1.1", @@ -17231,7 +17751,8 @@ "node_modules/node-addon-api": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", - "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==" + "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", + "dev": true }, "node_modules/node-dir": { "version": "0.1.17", @@ -18555,6 +19076,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", "integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==", + "dev": true, "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", @@ -18579,12 +19101,14 @@ "node_modules/prebuild-install/node_modules/chownr": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true }, "node_modules/prebuild-install/node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -18598,6 +19122,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dev": true, "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", @@ -18609,6 +19134,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", @@ -18911,6 +19437,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -19149,7 +19676,8 @@ "node_modules/queue-tick": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", - "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==" + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", + "dev": true }, "node_modules/quick-lru": { "version": "5.1.1", @@ -19233,6 +19761,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", @@ -19246,12 +19775,14 @@ "node_modules/rc/node_modules/ini": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true }, "node_modules/rc/node_modules/strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -20820,25 +21351,42 @@ "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" }, "node_modules/sharp": { - "version": "0.32.6", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.6.tgz", - "integrity": "sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==", + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.2.tgz", + "integrity": "sha512-WlYOPyyPDiiM07j/UO+E720ju6gtNtHjEGg5vovUk1Lgxyjm2LFO+37Nt/UI3MMh2l6hxTWQWi7qk3cXJTutcQ==", "hasInstallScript": true, "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.2", - "node-addon-api": "^6.1.0", - "prebuild-install": "^7.1.1", - "semver": "^7.5.4", - "simple-get": "^4.0.1", - "tar-fs": "^3.0.4", - "tunnel-agent": "^0.6.0" + "semver": "^7.5.4" }, "engines": { - "node": ">=14.15.0" + "libvips": ">=8.15.1", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.33.2", + "@img/sharp-darwin-x64": "0.33.2", + "@img/sharp-libvips-darwin-arm64": "1.0.1", + "@img/sharp-libvips-darwin-x64": "1.0.1", + "@img/sharp-libvips-linux-arm": "1.0.1", + "@img/sharp-libvips-linux-arm64": "1.0.1", + "@img/sharp-libvips-linux-s390x": "1.0.1", + "@img/sharp-libvips-linux-x64": "1.0.1", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.1", + "@img/sharp-libvips-linuxmusl-x64": "1.0.1", + "@img/sharp-linux-arm": "0.33.2", + "@img/sharp-linux-arm64": "0.33.2", + "@img/sharp-linux-s390x": "0.33.2", + "@img/sharp-linux-x64": "0.33.2", + "@img/sharp-linuxmusl-arm64": "0.33.2", + "@img/sharp-linuxmusl-x64": "0.33.2", + "@img/sharp-wasm32": "0.33.2", + "@img/sharp-win32-ia32": "0.33.2", + "@img/sharp-win32-x64": "0.33.2" } }, "node_modules/sharp/node_modules/lru-cache": { @@ -20916,6 +21464,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "dev": true, "funding": [ { "type": "github", @@ -20935,6 +21484,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "dev": true, "funding": [ { "type": "github", @@ -21318,12 +21868,16 @@ "dev": true }, "node_modules/streamx": { - "version": "2.15.6", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.15.6.tgz", - "integrity": "sha512-q+vQL4AAz+FdfT137VF69Cc/APqUbxy+MDOImRrMvchJpigHj9GksgDU2LYbO9rx7RX6osWgxJB2WxhYv4SZAw==", + "version": "2.15.8", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.15.8.tgz", + "integrity": "sha512-6pwMeMY/SuISiRsuS8TeIrAzyFbG5gGPHFQsYjUr/pbBadaL1PCWmzKw+CHZSwainfvcF6Si6cVLq4XTEwswFQ==", + "dev": true, "dependencies": { "fast-fifo": "^1.1.0", "queue-tick": "^1.0.1" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" } }, "node_modules/strict-uri-encode": { @@ -21786,19 +22340,24 @@ } }, "node_modules/tar-fs": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", - "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.5.tgz", + "integrity": "sha512-JOgGAmZyMgbqpLwct7ZV8VzkEB6pxXFBVErLtb+XCOqzc6w1xiWKI9GVd6bwk68EX7eJ4DWmfXVmq8K2ziZTGg==", + "dev": true, "dependencies": { - "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0" } }, "node_modules/tar-stream": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.6.tgz", - "integrity": "sha512-B/UyjYwPpMBv+PaFSWAmtYjwdrlEaZQEhMIBFNC5oEG8lpiW8XjcSdmEaClj28ArfKScKHs2nshz3k2le6crsg==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dev": true, "dependencies": { "b4a": "^1.6.4", "fast-fifo": "^1.2.0", @@ -22322,6 +22881,7 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dev": true, "dependencies": { "safe-buffer": "^5.0.1" }, diff --git a/frontend/package.json b/frontend/package.json index 53e383f426..7cfad3ad36 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -92,7 +92,7 @@ "react-table": "^7.8.0", "sanitize-html": "^2.11.0", "set-cookie-parser": "^2.5.1", - "sharp": "^0.32.6", + "sharp": "^0.33.2", "styled-components": "^5.3.7", "tailwind-merge": "^1.8.1", "tweetnacl": "^1.0.3", From 0ab811194da0d17c6a9a70e5ade49aff86d7aa38 Mon Sep 17 00:00:00 2001 From: snyk-bot Date: Sat, 17 Feb 2024 04:00:08 +0000 Subject: [PATCH 11/29] fix: upgrade posthog-js from 1.100.0 to 1.103.0 Snyk has created this PR to upgrade posthog-js from 1.100.0 to 1.103.0. See this package in npm: https://www.npmjs.com/package/posthog-js See this project in Snyk: https://app.snyk.io/org/maidul98/project/53d4ecb6-6cc1-4918-aa73-bf9cae4ffd13?utm_source=github&utm_medium=referral&page=upgrade-pr --- frontend/package-lock.json | 22 ++++++++++++++++------ frontend/package.json | 2 +- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index f28920f064..0a39bad251 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,5 +1,5 @@ { - "name": "frontend", + "name": "npm-proj-1708142388158-0.4275179729522043tF0bTg", "lockfileVersion": 3, "requires": true, "packages": { @@ -68,7 +68,7 @@ "next": "^12.3.4", "nprogress": "^0.2.0", "picomatch": "^2.3.1", - "posthog-js": "^1.58.0", + "posthog-js": "^1.103.0", "query-string": "^7.1.3", "react": "^17.0.2", "react-beautiful-dnd": "^13.1.1", @@ -18544,11 +18544,21 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, "node_modules/posthog-js": { - "version": "1.100.0", - "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.100.0.tgz", - "integrity": "sha512-r2XZEiHQ9mBK7D1G9k57I8uYZ2kZTAJ0OCX6K/OOdCWN8jKPhw3h5F9No5weilP6eVAn+hrsy7NvPV7SCX7gMg==", + "version": "1.103.0", + "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.103.0.tgz", + "integrity": "sha512-NldabkbCB9a/2JLszoB7vk5XZr93iBoGEgEEKn201oDD3QkRn1nC+c+e1HQ3S9oMs5oZIhKmPNBCje1J9prYRg==", "dependencies": { - "fflate": "^0.4.1" + "fflate": "^0.4.8", + "preact": "^10.19.3" + } + }, + "node_modules/preact": { + "version": "10.19.5", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.19.5.tgz", + "integrity": "sha512-OPELkDmSVbKjbFqF9tgvOowiiQ9TmsJljIzXRyNE8nGiis94pwv1siF78rQkAP1Q1738Ce6pellRg/Ns/CtHqQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" } }, "node_modules/prebuild-install": { diff --git a/frontend/package.json b/frontend/package.json index 53e383f426..1e5298c79b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -76,7 +76,7 @@ "next": "^12.3.4", "nprogress": "^0.2.0", "picomatch": "^2.3.1", - "posthog-js": "^1.58.0", + "posthog-js": "^1.103.0", "query-string": "^7.1.3", "react": "^17.0.2", "react-beautiful-dnd": "^13.1.1", From bccbedfc31c40c7d5bbecd8f191a85b61e5f0330 Mon Sep 17 00:00:00 2001 From: snyk-bot Date: Sat, 17 Feb 2024 04:00:12 +0000 Subject: [PATCH 12/29] fix: upgrade cookies from 0.8.0 to 0.9.1 Snyk has created this PR to upgrade cookies from 0.8.0 to 0.9.1. See this package in npm: https://www.npmjs.com/package/cookies See this project in Snyk: https://app.snyk.io/org/maidul98/project/53d4ecb6-6cc1-4918-aa73-bf9cae4ffd13?utm_source=github&utm_medium=referral&page=upgrade-pr --- frontend/package-lock.json | 10 +++++----- frontend/package.json | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index f28920f064..a1140f18e4 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,5 +1,5 @@ { - "name": "frontend", + "name": "npm-proj-1708142392925-0.33189569014007294yCesf", "lockfileVersion": 3, "requires": true, "packages": { @@ -48,7 +48,7 @@ "axios-auth-refresh": "^3.3.6", "base64-loader": "^1.0.0", "classnames": "^2.3.1", - "cookies": "^0.8.0", + "cookies": "^0.9.1", "cva": "npm:class-variance-authority@^0.4.0", "date-fns": "^2.30.0", "file-saver": "^2.0.5", @@ -10351,9 +10351,9 @@ "dev": true }, "node_modules/cookies": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.8.0.tgz", - "integrity": "sha512-8aPsApQfebXnuI+537McwYsDtjVxGm8gTIzQI3FDW6t5t/DAhERxtnbEPN/8RX+uZthoz4eCOgloXaE5cYyNow==", + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.9.1.tgz", + "integrity": "sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==", "dependencies": { "depd": "~2.0.0", "keygrip": "~1.1.0" diff --git a/frontend/package.json b/frontend/package.json index 53e383f426..31962291b4 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -56,7 +56,7 @@ "axios-auth-refresh": "^3.3.6", "base64-loader": "^1.0.0", "classnames": "^2.3.1", - "cookies": "^0.8.0", + "cookies": "^0.9.1", "cva": "npm:class-variance-authority@^0.4.0", "date-fns": "^2.30.0", "file-saver": "^2.0.5", From c6c64b5499ab7147e175ab74a24ef82150884984 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Sat, 17 Feb 2024 12:12:12 -0500 Subject: [PATCH 13/29] trigger workflow --- backend/src/server/routes/v3/secret-router.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/src/server/routes/v3/secret-router.ts b/backend/src/server/routes/v3/secret-router.ts index 1aac862050..ab5da0f36b 100644 --- a/backend/src/server/routes/v3/secret-router.ts +++ b/backend/src/server/routes/v3/secret-router.ts @@ -20,6 +20,8 @@ import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types"; import { secretRawSchema } from "../sanitizedSchemas"; +// trigger workflow + const getDistinctId = (req: FastifyRequest) => { if (req.auth.actor === ActorType.USER) { return req.auth.user.email; From abd28d92693a1d1fda01d313943d279bfa0f0093 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Sat, 17 Feb 2024 12:14:37 -0500 Subject: [PATCH 14/29] remove comment --- backend/src/server/routes/v3/secret-router.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/backend/src/server/routes/v3/secret-router.ts b/backend/src/server/routes/v3/secret-router.ts index ab5da0f36b..1aac862050 100644 --- a/backend/src/server/routes/v3/secret-router.ts +++ b/backend/src/server/routes/v3/secret-router.ts @@ -20,8 +20,6 @@ import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types"; import { secretRawSchema } from "../sanitizedSchemas"; -// trigger workflow - const getDistinctId = (req: FastifyRequest) => { if (req.auth.actor === ActorType.USER) { return req.auth.user.email; From 8864c811fe3b562a9c1d8e6af403643aa339fb39 Mon Sep 17 00:00:00 2001 From: Akhil Mohan Date: Sun, 18 Feb 2024 22:47:13 +0530 Subject: [PATCH 15/29] feat: updated to reuse and run integration api test before release --- .../release-standalone-docker-img-postgres-offical.yml | 8 ++++++++ .github/workflows/run-backend-tests.yml | 5 ++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release-standalone-docker-img-postgres-offical.yml b/.github/workflows/release-standalone-docker-img-postgres-offical.yml index 54f4f4fbe8..2e9044a9fa 100644 --- a/.github/workflows/release-standalone-docker-img-postgres-offical.yml +++ b/.github/workflows/release-standalone-docker-img-postgres-offical.yml @@ -5,9 +5,17 @@ on: - "infisical/v*.*.*-postgres" jobs: + infisical-tests: + name: Run tests before deployment + runs-on: ubuntu-latest + steps: + - name: Run backend api integration tests + # https://docs.github.com/en/actions/using-workflows/reusing-workflows#overview + uses: ./.github/workflows/run-backend-tests.yml infisical-standalone: name: Build infisical standalone image postgres runs-on: ubuntu-latest + needs: [infisical-tests] steps: - name: Extract version from tag id: extract_version diff --git a/.github/workflows/run-backend-tests.yml b/.github/workflows/run-backend-tests.yml index 2a805eab79..a283a538da 100644 --- a/.github/workflows/run-backend-tests.yml +++ b/.github/workflows/run-backend-tests.yml @@ -8,9 +8,8 @@ on: - "!backend/README.md" - "!backend/.*" - "backend/.eslintrc.js" - push: - tags: - - "infisical/v*.*.*-postgres" + workflow_call: + jobs: check-be-pr: name: Run integration test From 678306b35071064d7ba85cdcf43f8557355a1638 Mon Sep 17 00:00:00 2001 From: Akhil Mohan Date: Sun, 18 Feb 2024 22:51:10 +0530 Subject: [PATCH 16/29] feat: some name changes for better understanding on testing --- backend/e2e-test/routes/v1/identity.spec.ts | 4 ++-- backend/e2e-test/routes/v3/secrets.spec.ts | 8 ++++---- backend/src/db/seed-data.ts | 2 +- backend/src/db/seeds/4-machine-identity.ts | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/backend/e2e-test/routes/v1/identity.spec.ts b/backend/e2e-test/routes/v1/identity.spec.ts index f30ba7b1e2..16ebb444e2 100644 --- a/backend/e2e-test/routes/v1/identity.spec.ts +++ b/backend/e2e-test/routes/v1/identity.spec.ts @@ -68,8 +68,8 @@ describe("Identity token secret ops", async () => { method: "POST", url: "/api/v1/auth/universal-auth/login", body: { - clientSecret: seedData1.machineIdentity.clientCred.secret, - clientId: seedData1.machineIdentity.clientCred.id + clientSecret: seedData1.machineIdentity.clientCredentials.secret, + clientId: seedData1.machineIdentity.clientCredentials.id } }); expect(identityLogin.statusCode).toBe(200); diff --git a/backend/e2e-test/routes/v3/secrets.spec.ts b/backend/e2e-test/routes/v3/secrets.spec.ts index 89935d432d..02d239825c 100644 --- a/backend/e2e-test/routes/v3/secrets.spec.ts +++ b/backend/e2e-test/routes/v3/secrets.spec.ts @@ -31,7 +31,7 @@ describe("Secret V3 Router", async () => { authorization: `Bearer ${jwtAuthToken}` } }); - const projectKeyEnc = JSON.parse(projectKeyRes.payload); + const projectKeyEncryptionDetails = JSON.parse(projectKeyRes.payload); const userInfoRes = await testServer.inject({ method: "GET", @@ -43,9 +43,9 @@ describe("Secret V3 Router", async () => { const { user: userInfo } = JSON.parse(userInfoRes.payload); const privateKey = await getUserPrivateKey(seedData1.password, userInfo); projectKey = decryptAsymmetric({ - ciphertext: projectKeyEnc.encryptedKey, - nonce: projectKeyEnc.nonce, - publicKey: projectKeyEnc.sender.publicKey, + ciphertext: projectKeyEncryptionDetails.encryptedKey, + nonce: projectKeyEncryptionDetails.nonce, + publicKey: projectKeyEncryptionDetails.sender.publicKey, privateKey }); diff --git a/backend/src/db/seed-data.ts b/backend/src/db/seed-data.ts index 60038bdf88..8745d7f02e 100644 --- a/backend/src/db/seed-data.ts +++ b/backend/src/db/seed-data.ts @@ -35,7 +35,7 @@ export const seedData1 = { machineIdentity: { id: "88fa7aed-9288-401e-a4c9-fa9430be62a0", name: "mac1", - clientCred: { + clientCredentials: { id: "3f6135db-f237-421d-af66-a8f4e80d443b", secret: "da35a5a5a7b57f977a9a73394506e878a7175d06606df43dc93e1472b10cf339" } diff --git a/backend/src/db/seeds/4-machine-identity.ts b/backend/src/db/seeds/4-machine-identity.ts index e51a81b3b8..149fba40a9 100644 --- a/backend/src/db/seeds/4-machine-identity.ts +++ b/backend/src/db/seeds/4-machine-identity.ts @@ -23,7 +23,7 @@ export async function seed(knex: Knex): Promise { .insert([ { identityId: seedData1.machineIdentity.id, - clientId: seedData1.machineIdentity.clientCred.id, + clientId: seedData1.machineIdentity.clientCredentials.id, clientSecretTrustedIps: JSON.stringify([ { type: "ipv4", @@ -54,7 +54,7 @@ export async function seed(knex: Knex): Promise { } ]) .returning("*"); - const clientSecretHash = await bcrypt.hash(seedData1.machineIdentity.clientCred.secret, 10); + const clientSecretHash = await bcrypt.hash(seedData1.machineIdentity.clientCredentials.secret, 10); await knex(TableName.IdentityUaClientSecret).insert([ { identityUAId: identityUa[0].id, @@ -62,7 +62,7 @@ export async function seed(knex: Knex): Promise { clientSecretTTL: 0, clientSecretNumUses: 0, clientSecretNumUsesLimit: 0, - clientSecretPrefix: seedData1.machineIdentity.clientCred.secret.slice(0, 4), + clientSecretPrefix: seedData1.machineIdentity.clientCredentials.secret.slice(0, 4), clientSecretHash, isClientSecretRevoked: false } From 7070a69711a021071e883451b23c83861be69033 Mon Sep 17 00:00:00 2001 From: Akhil Mohan Date: Mon, 19 Feb 2024 20:56:29 +0530 Subject: [PATCH 17/29] feat: made e2ee api test indepdent or stateless --- backend/e2e-test/routes/v1/identity.spec.ts | 111 ++++++- .../e2e-test/routes/v1/project-env.spec.ts | 113 +++---- .../e2e-test/routes/v1/secret-folder.spec.ts | 92 +++--- .../e2e-test/routes/v1/secret-import.spec.ts | 116 ++++--- .../e2e-test/routes/v2/service-token.spec.ts | 164 +++++++--- backend/e2e-test/routes/v3/secrets.spec.ts | 299 +++++++++++++----- 6 files changed, 601 insertions(+), 294 deletions(-) diff --git a/backend/e2e-test/routes/v1/identity.spec.ts b/backend/e2e-test/routes/v1/identity.spec.ts index 16ebb444e2..9497655cab 100644 --- a/backend/e2e-test/routes/v1/identity.spec.ts +++ b/backend/e2e-test/routes/v1/identity.spec.ts @@ -2,8 +2,58 @@ import { SecretType } from "@app/db/schemas"; import { getUserPrivateKey, seedData1 } from "@app/db/seed-data"; import { decryptAsymmetric, encryptAsymmetric } from "@app/lib/crypto"; +const createRawSecret = async (dto: { + path: string; + key: string; + value: string; + comment: string; + type?: SecretType; + token: string; +}) => { + const createSecretReqBody = { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + type: dto.type || SecretType.Shared, + secretValue: dto.value, + secretComment: dto.comment, + secretPath: dto.path + }; + const createSecRes = await testServer.inject({ + method: "POST", + url: `/api/v3/secrets/raw/${dto.key}`, + headers: { + authorization: `Bearer ${dto.token}` + }, + body: createSecretReqBody + }); + expect(createSecRes.statusCode).toBe(200); + const createdSecretPayload = JSON.parse(createSecRes.payload); + expect(createdSecretPayload).toHaveProperty("secret"); + return createdSecretPayload.secret; +}; + +const deleteRawSecret = async (dto: { path: string; key: string; token: string }) => { + const deleteSecRes = await testServer.inject({ + method: "DELETE", + url: `/api/v3/secrets/raw/${dto.key}`, + headers: { + authorization: `Bearer ${dto.token}` + }, + body: { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + secretPath: dto.path + } + }); + expect(deleteSecRes.statusCode).toBe(200); + const updatedSecretPayload = JSON.parse(deleteSecRes.payload); + expect(updatedSecretPayload).toHaveProperty("secret"); + return updatedSecretPayload.secret; +}; + describe("Identity token secret ops", async () => { let identityToken = ""; + let folderId = ""; beforeAll(async () => { // enable bot const res = await testServer.inject({ @@ -90,6 +140,7 @@ describe("Identity token secret ops", async () => { } }); expect(folderCreate.statusCode).toBe(200); + folderId = folderCreate.json().folder.id; }); afterAll(async () => { @@ -116,6 +167,20 @@ describe("Identity token secret ops", async () => { } }); expect(setBotInActive.statusCode).toEqual(200); + + const deleteFolder = await testServer.inject({ + method: "DELETE", + url: `/api/v1/folders/${folderId}`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + path: "/nested1/nested2" + } + }); + expect(deleteFolder.statusCode).toBe(200); }); const testRawSecrets = [ @@ -187,9 +252,13 @@ describe("Identity token secret ops", async () => { }) ]) ); + + await deleteRawSecret({ path, key: secret.key, token: identityToken }); }); test.each(testRawSecrets)("Get secret by name raw in path $path", async ({ secret, path }) => { + await createRawSecret({ path, ...secret, token: identityToken }); + const getSecByNameRes = await testServer.inject({ method: "GET", url: `/api/v3/secrets/raw/${secret.key}`, @@ -211,9 +280,37 @@ describe("Identity token secret ops", async () => { secretValue: secret.value }) ); + + await deleteRawSecret({ path, key: secret.key, token: identityToken }); + }); + + test.each(testRawSecrets)("List secret raw in path $path", async ({ secret, path }) => { + await Promise.all( + Array.from(Array(5)).map((_e, i) => + createRawSecret({ path, token: identityToken, ...secret, key: `BULK-${secret.key}-${i + 1}` }) + ) + ); + + const secrets = await getSecrets(seedData1.environment.slug, path); + expect(secrets.length).toEqual(5); + expect(secrets).toEqual( + expect.arrayContaining( + Array.from(Array(5)).map((_e, i) => + expect.objectContaining({ value: expect.any(String), key: `BULK-${secret.key}-${i + 1}` }) + ) + ) + ); + + await Promise.all( + Array.from(Array(5)).map((_e, i) => + deleteRawSecret({ path, token: identityToken, key: `BULK-${secret.key}-${i + 1}` }) + ) + ); }); test.each(testRawSecrets)("Update secret raw in path $path", async ({ secret, path }) => { + await createRawSecret({ path, ...secret, token: identityToken }); + const updateSecretReqBody = { workspaceId: seedData1.project.id, environment: seedData1.environment.slug, @@ -221,7 +318,6 @@ describe("Identity token secret ops", async () => { secretValue: "new-value", secretPath: path }; - const updateSecRes = await testServer.inject({ method: "PATCH", url: `/api/v3/secrets/raw/${secret.key}`, @@ -246,9 +342,13 @@ describe("Identity token secret ops", async () => { }) ]) ); + + await deleteRawSecret({ path, key: secret.key, token: identityToken }); }); test.each(testRawSecrets)("Delete secret raw in path $path", async ({ path, secret }) => { + await createRawSecret({ path, ...secret, token: identityToken }); + const deletedSecretReqBody = { workspaceId: seedData1.project.id, environment: seedData1.environment.slug, @@ -269,13 +369,6 @@ describe("Identity token secret ops", async () => { // fetch secrets const secrets = await getSecrets(seedData1.environment.slug, path); - expect(secrets).toEqual( - expect.arrayContaining([ - expect.not.objectContaining({ - key: secret.key, - type: SecretType.Shared - }) - ]) - ); + expect(secrets).toEqual([]); }); }); diff --git a/backend/e2e-test/routes/v1/project-env.spec.ts b/backend/e2e-test/routes/v1/project-env.spec.ts index 936cfa8590..ec06d64748 100644 --- a/backend/e2e-test/routes/v1/project-env.spec.ts +++ b/backend/e2e-test/routes/v1/project-env.spec.ts @@ -1,6 +1,40 @@ import { seedData1 } from "@app/db/seed-data"; import { DEFAULT_PROJECT_ENVS } from "@app/db/seeds/3-project"; +const createProjectEnvironment = async (name: string, slug: string) => { + const res = await testServer.inject({ + method: "POST", + url: `/api/v1/workspace/${seedData1.project.id}/environments`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: { + name, + slug + } + }); + + expect(res.statusCode).toBe(200); + const payload = JSON.parse(res.payload); + expect(payload).toHaveProperty("environment"); + return payload.environment; +}; + +const deleteProjectEnvironment = async (envId: string) => { + const res = await testServer.inject({ + method: "DELETE", + url: `/api/v1/workspace/${seedData1.project.id}/environments/${envId}`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + } + }); + + expect(res.statusCode).toBe(200); + const payload = JSON.parse(res.payload); + expect(payload).toHaveProperty("environment"); + return payload.environment; +}; + describe("Project Environment Router", async () => { test("Get default environments", async () => { const res = await testServer.inject({ @@ -31,24 +65,10 @@ describe("Project Environment Router", async () => { expect(payload.workspace.environments.length).toBe(3); }); - const mockProjectEnv = { name: "temp", slug: "temp", id: "" }; // id will be filled in create op + const mockProjectEnv = { name: "temp", slug: "temp" }; // id will be filled in create op test("Create environment", async () => { - const res = await testServer.inject({ - method: "POST", - url: `/api/v1/workspace/${seedData1.project.id}/environments`, - headers: { - authorization: `Bearer ${jwtAuthToken}` - }, - body: { - name: mockProjectEnv.name, - slug: mockProjectEnv.slug - } - }); - - expect(res.statusCode).toBe(200); - const payload = JSON.parse(res.payload); - expect(payload).toHaveProperty("environment"); - expect(payload.environment).toEqual( + const newEnvironment = await createProjectEnvironment(mockProjectEnv.name, mockProjectEnv.slug); + expect(newEnvironment).toEqual( expect.objectContaining({ id: expect.any(String), name: mockProjectEnv.name, @@ -59,14 +79,15 @@ describe("Project Environment Router", async () => { updatedAt: expect.any(String) }) ); - mockProjectEnv.id = payload.environment.id; + await deleteProjectEnvironment(newEnvironment.id); }); test("Update environment", async () => { + const newEnvironment = await createProjectEnvironment(mockProjectEnv.name, mockProjectEnv.slug); const updatedName = { name: "temp#2", slug: "temp2" }; const res = await testServer.inject({ method: "PATCH", - url: `/api/v1/workspace/${seedData1.project.id}/environments/${mockProjectEnv.id}`, + url: `/api/v1/workspace/${seedData1.project.id}/environments/${newEnvironment.id}`, headers: { authorization: `Bearer ${jwtAuthToken}` }, @@ -82,7 +103,7 @@ describe("Project Environment Router", async () => { expect(payload).toHaveProperty("environment"); expect(payload.environment).toEqual( expect.objectContaining({ - id: expect.any(String), + id: newEnvironment.id, name: updatedName.name, slug: updatedName.slug, projectId: seedData1.project.id, @@ -91,61 +112,21 @@ describe("Project Environment Router", async () => { updatedAt: expect.any(String) }) ); - mockProjectEnv.name = updatedName.name; - mockProjectEnv.slug = updatedName.slug; + await deleteProjectEnvironment(newEnvironment.id); }); test("Delete environment", async () => { - const res = await testServer.inject({ - method: "DELETE", - url: `/api/v1/workspace/${seedData1.project.id}/environments/${mockProjectEnv.id}`, - headers: { - authorization: `Bearer ${jwtAuthToken}` - } - }); - - expect(res.statusCode).toBe(200); - const payload = JSON.parse(res.payload); - expect(payload).toHaveProperty("environment"); - expect(payload.environment).toEqual( + const newEnvironment = await createProjectEnvironment(mockProjectEnv.name, mockProjectEnv.slug); + const deletedProjectEnvironment = await deleteProjectEnvironment(newEnvironment.id); + expect(deletedProjectEnvironment).toEqual( expect.objectContaining({ - id: expect.any(String), + id: deletedProjectEnvironment.id, name: mockProjectEnv.name, slug: mockProjectEnv.slug, - position: 1, + position: 4, createdAt: expect.any(String), updatedAt: expect.any(String) }) ); }); - - // after all these opreations the list of environment should be still same - test("Default list of environment", async () => { - const res = await testServer.inject({ - method: "GET", - url: `/api/v1/workspace/${seedData1.project.id}`, - headers: { - authorization: `Bearer ${jwtAuthToken}` - } - }); - - expect(res.statusCode).toBe(200); - const payload = JSON.parse(res.payload); - expect(payload).toHaveProperty("workspace"); - // check for default environments - expect(payload).toEqual({ - workspace: expect.objectContaining({ - name: seedData1.project.name, - id: seedData1.project.id, - slug: seedData1.project.slug, - environments: expect.arrayContaining([ - expect.objectContaining(DEFAULT_PROJECT_ENVS[0]), - expect.objectContaining(DEFAULT_PROJECT_ENVS[1]), - expect.objectContaining(DEFAULT_PROJECT_ENVS[2]) - ]) - }) - }); - // ensure only two default environments exist - expect(payload.workspace.environments.length).toBe(3); - }); }); diff --git a/backend/e2e-test/routes/v1/secret-folder.spec.ts b/backend/e2e-test/routes/v1/secret-folder.spec.ts index 727c41bbb1..4d4bd7ab4c 100644 --- a/backend/e2e-test/routes/v1/secret-folder.spec.ts +++ b/backend/e2e-test/routes/v1/secret-folder.spec.ts @@ -1,5 +1,40 @@ import { seedData1 } from "@app/db/seed-data"; +const createFolder = async (dto: { path: string; name: string }) => { + const res = await testServer.inject({ + method: "POST", + url: `/api/v1/folders`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + name: dto.name, + path: dto.path + } + }); + expect(res.statusCode).toBe(200); + return res.json().folder; +}; + +const deleteFolder = async (dto: { path: string; id: string }) => { + const res = await testServer.inject({ + method: "DELETE", + url: `/api/v1/folders/${dto.id}`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + path: dto.path + } + }); + expect(res.statusCode).toBe(200); + return res.json().folder; +}; + describe("Secret Folder Router", async () => { test.each([ { name: "folder1", path: "/" }, // one in root @@ -7,30 +42,15 @@ describe("Secret Folder Router", async () => { { name: "folder2", path: "/" }, { name: "folder1", path: "/level1/level2" } // this should not create folder return same thing ])("Create folder $name in $path", async ({ name, path }) => { - const res = await testServer.inject({ - method: "POST", - url: `/api/v1/folders`, - headers: { - authorization: `Bearer ${jwtAuthToken}` - }, - body: { - workspaceId: seedData1.project.id, - environment: seedData1.environment.slug, - name, - path - } - }); - - expect(res.statusCode).toBe(200); - const payload = JSON.parse(res.payload); - expect(payload).toHaveProperty("folder"); + const createdFolder = await createFolder({ path, name }); // check for default environments - expect(payload).toEqual({ - folder: expect.objectContaining({ + expect(createdFolder).toEqual( + expect.objectContaining({ name, id: expect.any(String) }) - }); + ); + await deleteFolder({ path, id: createdFolder.id }); }); test.each([ @@ -43,6 +63,8 @@ describe("Secret Folder Router", async () => { }, { path: "/level1/level2", expected: { folders: [{ name: "folder1" }], length: 1 } } ])("Get folders $path", async ({ path, expected }) => { + const newFolders = await Promise.all(expected.folders.map(({ name }) => createFolder({ name, path }))); + const res = await testServer.inject({ method: "GET", url: `/api/v1/folders`, @@ -59,37 +81,22 @@ describe("Secret Folder Router", async () => { expect(res.statusCode).toBe(200); const payload = JSON.parse(res.payload); expect(payload).toHaveProperty("folders"); + expect(payload.folders.length >= expected.folders.length).toBeTruthy(); expect(payload).toEqual({ folders: expect.arrayContaining(expected.folders.map((el) => expect.objectContaining(el))) }); + + await Promise.all(newFolders.map(({ id }) => deleteFolder({ path, id }))); }); - let toBeDeleteFolderId = ""; test("Update a deep folder", async () => { - const res = await testServer.inject({ - method: "PATCH", - url: `/api/v1/folders/folder1`, - headers: { - authorization: `Bearer ${jwtAuthToken}` - }, - body: { - workspaceId: seedData1.project.id, - environment: seedData1.environment.slug, - name: "folder-updated", - path: "/level1/level2" - } - }); - - expect(res.statusCode).toBe(200); - const payload = JSON.parse(res.payload); - expect(payload).toHaveProperty("folder"); - expect(payload.folder).toEqual( + const newFolder = await createFolder({ name: "folder-updated", path: "/level1/level2" }); + expect(newFolder).toEqual( expect.objectContaining({ id: expect.any(String), name: "folder-updated" }) ); - toBeDeleteFolderId = payload.folder.id; const resUpdatedFolders = await testServer.inject({ method: "GET", @@ -108,12 +115,15 @@ describe("Secret Folder Router", async () => { const updatedFolderList = JSON.parse(resUpdatedFolders.payload); expect(updatedFolderList).toHaveProperty("folders"); expect(updatedFolderList.folders[0].name).toEqual("folder-updated"); + + await deleteFolder({ path: "/level1/level2", id: newFolder.id }); }); test("Delete a deep folder", async () => { + const newFolder = await createFolder({ name: "folder-updated", path: "/level1/level2" }); const res = await testServer.inject({ method: "DELETE", - url: `/api/v1/folders/${toBeDeleteFolderId}`, + url: `/api/v1/folders/${newFolder.id}`, headers: { authorization: `Bearer ${jwtAuthToken}` }, diff --git a/backend/e2e-test/routes/v1/secret-import.spec.ts b/backend/e2e-test/routes/v1/secret-import.spec.ts index f42c033c2d..56a8aad51a 100644 --- a/backend/e2e-test/routes/v1/secret-import.spec.ts +++ b/backend/e2e-test/routes/v1/secret-import.spec.ts @@ -1,32 +1,57 @@ import { seedData1 } from "@app/db/seed-data"; -describe("Secret Folder Router", async () => { +const createSecretImport = async (importPath: string, importEnv: string) => { + const res = await testServer.inject({ + method: "POST", + url: `/api/v1/secret-imports`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + path: "/", + import: { + environment: importEnv, + path: importPath + } + } + }); + + expect(res.statusCode).toBe(200); + const payload = JSON.parse(res.payload); + expect(payload).toHaveProperty("secretImport"); + return payload.secretImport; +}; + +const deleteSecretImport = async (id: string) => { + const res = await testServer.inject({ + method: "DELETE", + url: `/api/v1/secret-imports/${id}`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + path: "/" + } + }); + + expect(res.statusCode).toBe(200); + const payload = JSON.parse(res.payload); + expect(payload).toHaveProperty("secretImport"); + return payload.secretImport; +}; + +describe("Secret Import Router", async () => { test.each([ { importEnv: "dev", importPath: "/" }, // one in root { importEnv: "staging", importPath: "/" } // then create a deep one creating intermediate ones ])("Create secret import $importEnv with path $importPath", async ({ importPath, importEnv }) => { - const res = await testServer.inject({ - method: "POST", - url: `/api/v1/secret-imports`, - headers: { - authorization: `Bearer ${jwtAuthToken}` - }, - body: { - workspaceId: seedData1.project.id, - environment: seedData1.environment.slug, - path: "/", - import: { - environment: importEnv, - path: importPath - } - } - }); - - expect(res.statusCode).toBe(200); - const payload = JSON.parse(res.payload); - expect(payload).toHaveProperty("secretImport"); // check for default environments - expect(payload.secretImport).toEqual( + const payload = await createSecretImport(importPath, importEnv); + expect(payload).toEqual( expect.objectContaining({ id: expect.any(String), importPath: expect.any(String), @@ -37,10 +62,12 @@ describe("Secret Folder Router", async () => { }) }) ); + await deleteSecretImport(payload.id); }); - let testSecretImportId = ""; test("Get secret imports", async () => { + const createdImport1 = await createSecretImport("/", "dev"); + const createdImport2 = await createSecretImport("/", "staging"); const res = await testServer.inject({ method: "GET", url: `/api/v1/secret-imports`, @@ -58,7 +85,6 @@ describe("Secret Folder Router", async () => { const payload = JSON.parse(res.payload); expect(payload).toHaveProperty("secretImports"); expect(payload.secretImports.length).toBe(2); - testSecretImportId = payload.secretImports[0].id; expect(payload.secretImports).toEqual( expect.arrayContaining([ expect.objectContaining({ @@ -72,12 +98,17 @@ describe("Secret Folder Router", async () => { }) ]) ); + await deleteSecretImport(createdImport1.id); + await deleteSecretImport(createdImport2.id); }); test("Update secret import position", async () => { - const res = await testServer.inject({ + const createdImport1 = await createSecretImport("/", "dev"); + const createdImport2 = await createSecretImport("/", "staging"); + + const updateImportRes = await testServer.inject({ method: "PATCH", - url: `/api/v1/secret-imports/${testSecretImportId}`, + url: `/api/v1/secret-imports/${createdImport1.id}`, headers: { authorization: `Bearer ${jwtAuthToken}` }, @@ -91,8 +122,8 @@ describe("Secret Folder Router", async () => { } }); - expect(res.statusCode).toBe(200); - const payload = JSON.parse(res.payload); + expect(updateImportRes.statusCode).toBe(200); + const payload = JSON.parse(updateImportRes.payload); expect(payload).toHaveProperty("secretImport"); // check for default environments expect(payload.secretImport).toEqual( @@ -124,28 +155,19 @@ describe("Secret Folder Router", async () => { expect(secretImportsListRes.statusCode).toBe(200); const secretImportList = JSON.parse(secretImportsListRes.payload); expect(secretImportList).toHaveProperty("secretImports"); - expect(secretImportList.secretImports[1].id).toEqual(testSecretImportId); + expect(secretImportList.secretImports[1].id).toEqual(createdImport1.id); + expect(secretImportList.secretImports[0].id).toEqual(createdImport2.id); + + await deleteSecretImport(createdImport1.id); + await deleteSecretImport(createdImport2.id); }); test("Delete secret import position", async () => { - const res = await testServer.inject({ - method: "DELETE", - url: `/api/v1/secret-imports/${testSecretImportId}`, - headers: { - authorization: `Bearer ${jwtAuthToken}` - }, - body: { - workspaceId: seedData1.project.id, - environment: seedData1.environment.slug, - path: "/" - } - }); - - expect(res.statusCode).toBe(200); - const payload = JSON.parse(res.payload); - expect(payload).toHaveProperty("secretImport"); + const createdImport1 = await createSecretImport("/", "dev"); + const createdImport2 = await createSecretImport("/", "staging"); + const deletedImport = await deleteSecretImport(createdImport1.id); // check for default environments - expect(payload.secretImport).toEqual( + expect(deletedImport).toEqual( expect.objectContaining({ id: expect.any(String), importPath: expect.any(String), @@ -175,5 +197,7 @@ describe("Secret Folder Router", async () => { expect(secretImportList).toHaveProperty("secretImports"); expect(secretImportList.secretImports.length).toEqual(1); expect(secretImportList.secretImports[0].position).toEqual(1); + + await deleteSecretImport(createdImport2.id); }); }); diff --git a/backend/e2e-test/routes/v2/service-token.spec.ts b/backend/e2e-test/routes/v2/service-token.spec.ts index e9843a2e7b..a07eda4b9b 100644 --- a/backend/e2e-test/routes/v2/service-token.spec.ts +++ b/backend/e2e-test/routes/v2/service-token.spec.ts @@ -83,9 +83,59 @@ const deleteServiceToken = async () => { expect(deleteTokenRes.statusCode).toBe(200); }; +const createSecret = async (dto: { + projectKey: string; + path: string; + key: string; + value: string; + comment: string; + type?: SecretType; + token: string; +}) => { + const createSecretReqBody = { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + type: dto.type || SecretType.Shared, + secretPath: dto.path, + ...encryptSecret(dto.projectKey, dto.key, dto.value, dto.comment) + }; + const createSecRes = await testServer.inject({ + method: "POST", + url: `/api/v3/secrets/${dto.key}`, + headers: { + authorization: `Bearer ${dto.token}` + }, + body: createSecretReqBody + }); + expect(createSecRes.statusCode).toBe(200); + const createdSecretPayload = JSON.parse(createSecRes.payload); + expect(createdSecretPayload).toHaveProperty("secret"); + return createdSecretPayload.secret; +}; + +const deleteSecret = async (dto: { path: string; key: string; token: string }) => { + const deleteSecRes = await testServer.inject({ + method: "DELETE", + url: `/api/v3/secrets/${dto.key}`, + headers: { + authorization: `Bearer ${dto.token}` + }, + body: { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + secretPath: dto.path + } + }); + expect(deleteSecRes.statusCode).toBe(200); + const updatedSecretPayload = JSON.parse(deleteSecRes.payload); + expect(updatedSecretPayload).toHaveProperty("secret"); + return updatedSecretPayload.secret; +}; + describe("Service token secret ops", async () => { let serviceToken = ""; let projectKey = ""; + let folderId = ""; beforeAll(async () => { serviceToken = await createServiceToken( [{ secretPath: "/**", environment: seedData1.environment.slug }], @@ -125,10 +175,26 @@ describe("Service token secret ops", async () => { } }); expect(folderCreate.statusCode).toBe(200); + folderId = folderCreate.json().folder.id; }); afterAll(async () => { await deleteServiceToken(); + + // create a deep folder + const deleteFolder = await testServer.inject({ + method: "DELETE", + url: `/api/v1/folders/${folderId}`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + path: "/nested1/nested2" + } + }); + expect(deleteFolder.statusCode).toBe(200); }); const testSecrets = [ @@ -168,25 +234,8 @@ describe("Service token secret ops", async () => { }; test.each(testSecrets)("Create secret in path $path", async ({ secret, path }) => { - const createSecretReqBody = { - workspaceId: seedData1.project.id, - environment: seedData1.environment.slug, - type: SecretType.Shared, - secretPath: path, - ...encryptSecret(projectKey, secret.key, secret.value, secret.comment) - }; - const createSecRes = await testServer.inject({ - method: "POST", - url: `/api/v3/secrets/${secret.key}`, - headers: { - authorization: `Bearer ${serviceToken}` - }, - body: createSecretReqBody - }); - expect(createSecRes.statusCode).toBe(200); - const createdSecretPayload = JSON.parse(createSecRes.payload); - expect(createdSecretPayload).toHaveProperty("secret"); - const decryptedSecret = decryptSecret(projectKey, createdSecretPayload.secret); + const createdSecret = await createSecret({ projectKey, path, ...secret, token: serviceToken }); + const decryptedSecret = decryptSecret(projectKey, createdSecret); expect(decryptedSecret.key).toEqual(secret.key); expect(decryptedSecret.value).toEqual(secret.value); expect(decryptedSecret.comment).toEqual(secret.comment); @@ -202,9 +251,12 @@ describe("Service token secret ops", async () => { }) ]) ); + await deleteSecret({ path, key: secret.key, token: serviceToken }); }); test.each(testSecrets)("Get secret by name in path $path", async ({ secret, path }) => { + await createSecret({ projectKey, path, ...secret, token: serviceToken }); + const getSecByNameRes = await testServer.inject({ method: "GET", url: `/api/v3/secrets/${secret.key}`, @@ -224,9 +276,12 @@ describe("Service token secret ops", async () => { expect(decryptedSecret.key).toEqual(secret.key); expect(decryptedSecret.value).toEqual(secret.value); expect(decryptedSecret.comment).toEqual(secret.comment); + + await deleteSecret({ path, key: secret.key, token: serviceToken }); }); test.each(testSecrets)("Update secret in path $path", async ({ path, secret }) => { + await createSecret({ projectKey, path, ...secret, token: serviceToken }); const updateSecretReqBody = { workspaceId: seedData1.project.id, environment: seedData1.environment.slug, @@ -261,25 +316,14 @@ describe("Service token secret ops", async () => { }) ]) ); + + await deleteSecret({ path, key: secret.key, token: serviceToken }); }); test.each(testSecrets)("Delete secret in path $path", async ({ secret, path }) => { - const deleteSecRes = await testServer.inject({ - method: "DELETE", - url: `/api/v3/secrets/${secret.key}`, - headers: { - authorization: `Bearer ${serviceToken}` - }, - body: { - workspaceId: seedData1.project.id, - environment: seedData1.environment.slug, - secretPath: path - } - }); - expect(deleteSecRes.statusCode).toBe(200); - const updatedSecretPayload = JSON.parse(deleteSecRes.payload); - expect(updatedSecretPayload).toHaveProperty("secret"); - const decryptedSecret = decryptSecret(projectKey, updatedSecretPayload.secret); + await createSecret({ projectKey, path, ...secret, token: serviceToken }); + const deletedSecret = await deleteSecret({ path, key: secret.key, token: serviceToken }); + const decryptedSecret = decryptSecret(projectKey, deletedSecret); expect(decryptedSecret.key).toEqual(secret.key); // shared secret deletion should delete personal ones also @@ -305,7 +349,7 @@ describe("Service token secret ops", async () => { workspaceId: seedData1.project.id, environment: seedData1.environment.slug, secretPath: path, - secrets: Array.from(Array(10)).map((_e, i) => ({ + secrets: Array.from(Array(5)).map((_e, i) => ({ secretName: `BULK-${secret.key}-${i + 1}`, ...encryptSecret(projectKey, `BULK-${secret.key}-${i + 1}`, secret.value, secret.comment) })) @@ -319,7 +363,7 @@ describe("Service token secret ops", async () => { const secrets = await getSecrets(seedData1.environment.slug, path); expect(secrets).toEqual( expect.arrayContaining( - Array.from(Array(10)).map((_e, i) => + Array.from(Array(5)).map((_e, i) => expect.objectContaining({ key: `BULK-${secret.key}-${i + 1}`, type: SecretType.Shared @@ -327,9 +371,17 @@ describe("Service token secret ops", async () => { ) ) ); + + await Promise.all( + Array.from(Array(5)).map((_e, i) => + deleteSecret({ path, token: serviceToken, key: `BULK-${secret.key}-${i + 1}` }) + ) + ); }); test.each(testSecrets)("Bulk create fail on existing secret in path $path", async ({ secret, path }) => { + await createSecret({ projectKey, ...secret, key: `BULK-${secret.key}-1`, path, token: serviceToken }); + const createSharedSecRes = await testServer.inject({ method: "POST", url: `/api/v3/secrets/batch`, @@ -340,16 +392,24 @@ describe("Service token secret ops", async () => { workspaceId: seedData1.project.id, environment: seedData1.environment.slug, secretPath: path, - secrets: Array.from(Array(10)).map((_e, i) => ({ + secrets: Array.from(Array(5)).map((_e, i) => ({ secretName: `BULK-${secret.key}-${i + 1}`, ...encryptSecret(projectKey, `BULK-${secret.key}-${i + 1}`, secret.value, secret.comment) })) } }); expect(createSharedSecRes.statusCode).toBe(400); + + await deleteSecret({ path, key: `BULK-${secret.key}-1`, token: serviceToken }); }); test.each(testSecrets)("Bulk update secrets in path $path", async ({ secret, path }) => { + await Promise.all( + Array.from(Array(5)).map((_e, i) => + createSecret({ projectKey, token: serviceToken, ...secret, key: `BULK-${secret.key}-${i + 1}`, path }) + ) + ); + const updateSharedSecRes = await testServer.inject({ method: "PATCH", url: `/api/v3/secrets/batch`, @@ -360,7 +420,7 @@ describe("Service token secret ops", async () => { workspaceId: seedData1.project.id, environment: seedData1.environment.slug, secretPath: path, - secrets: Array.from(Array(10)).map((_e, i) => ({ + secrets: Array.from(Array(5)).map((_e, i) => ({ secretName: `BULK-${secret.key}-${i + 1}`, ...encryptSecret(projectKey, `BULK-${secret.key}-${i + 1}`, "update-value", secret.comment) })) @@ -374,7 +434,7 @@ describe("Service token secret ops", async () => { const secrets = await getSecrets(seedData1.environment.slug, path); expect(secrets).toEqual( expect.arrayContaining( - Array.from(Array(10)).map((_e, i) => + Array.from(Array(5)).map((_e, i) => expect.objectContaining({ key: `BULK-${secret.key}-${i + 1}`, value: "update-value", @@ -383,10 +443,21 @@ describe("Service token secret ops", async () => { ) ) ); + await Promise.all( + Array.from(Array(5)).map((_e, i) => + deleteSecret({ path, key: `BULK-${secret.key}-${i + 1}`, token: serviceToken }) + ) + ); }); test.each(testSecrets)("Bulk delete secrets in path $path", async ({ secret, path }) => { - const updateSharedSecRes = await testServer.inject({ + await Promise.all( + Array.from(Array(5)).map((_e, i) => + createSecret({ projectKey, token: serviceToken, ...secret, key: `BULK-${secret.key}-${i + 1}`, path }) + ) + ); + + const deletedSharedSecRes = await testServer.inject({ method: "DELETE", url: `/api/v3/secrets/batch`, headers: { @@ -396,20 +467,21 @@ describe("Service token secret ops", async () => { workspaceId: seedData1.project.id, environment: seedData1.environment.slug, secretPath: path, - secrets: Array.from(Array(10)).map((_e, i) => ({ + secrets: Array.from(Array(5)).map((_e, i) => ({ secretName: `BULK-${secret.key}-${i + 1}` })) } }); - expect(updateSharedSecRes.statusCode).toBe(200); - const updateSharedSecPayload = JSON.parse(updateSharedSecRes.payload); - expect(updateSharedSecPayload).toHaveProperty("secrets"); + + expect(deletedSharedSecRes.statusCode).toBe(200); + const deletedSecretPayload = JSON.parse(deletedSharedSecRes.payload); + expect(deletedSecretPayload).toHaveProperty("secrets"); // bulk ones should exist const secrets = await getSecrets(seedData1.environment.slug, path); expect(secrets).toEqual( expect.not.arrayContaining( - Array.from(Array(10)).map((_e, i) => + Array.from(Array(5)).map((_e, i) => expect.objectContaining({ key: `BULK-${secret.value}-${i + 1}`, type: SecretType.Shared diff --git a/backend/e2e-test/routes/v3/secrets.spec.ts b/backend/e2e-test/routes/v3/secrets.spec.ts index 02d239825c..b0a8ea04cd 100644 --- a/backend/e2e-test/routes/v3/secrets.spec.ts +++ b/backend/e2e-test/routes/v3/secrets.spec.ts @@ -2,6 +2,54 @@ import { SecretType, TSecrets } from "@app/db/schemas"; import { decryptSecret, encryptSecret, getUserPrivateKey, seedData1 } from "@app/db/seed-data"; import { decryptAsymmetric, encryptAsymmetric } from "@app/lib/crypto"; +const createSecret = async (dto: { + projectKey: string; + path: string; + key: string; + value: string; + comment: string; + type?: SecretType; +}) => { + const createSecretReqBody = { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + type: dto.type || SecretType.Shared, + secretPath: dto.path, + ...encryptSecret(dto.projectKey, dto.key, dto.value, dto.comment) + }; + const createSecRes = await testServer.inject({ + method: "POST", + url: `/api/v3/secrets/${dto.key}`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: createSecretReqBody + }); + expect(createSecRes.statusCode).toBe(200); + const createdSecretPayload = JSON.parse(createSecRes.payload); + expect(createdSecretPayload).toHaveProperty("secret"); + return createdSecretPayload.secret; +}; + +const deleteSecret = async (dto: { path: string; key: string }) => { + const deleteSecRes = await testServer.inject({ + method: "DELETE", + url: `/api/v3/secrets/${dto.key}`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + secretPath: dto.path + } + }); + expect(deleteSecRes.statusCode).toBe(200); + const updatedSecretPayload = JSON.parse(deleteSecRes.payload); + expect(updatedSecretPayload).toHaveProperty("secret"); + return updatedSecretPayload.secret; +}; + describe("Secret V3 Router", async () => { const testSecrets = [ { @@ -23,6 +71,7 @@ describe("Secret V3 Router", async () => { ]; let projectKey = ""; + let folderId = ""; beforeAll(async () => { const projectKeyRes = await testServer.inject({ method: "GET", @@ -64,6 +113,23 @@ describe("Secret V3 Router", async () => { } }); expect(folderCreate.statusCode).toBe(200); + folderId = folderCreate.json().folder.id; + }); + + afterAll(async () => { + const deleteFolder = await testServer.inject({ + method: "DELETE", + url: `/api/v1/folders/${folderId}`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + path: "/nested1/nested2" + } + }); + expect(deleteFolder.statusCode).toBe(200); }); const getSecrets = async (environment: string, secretPath = "/") => { @@ -84,25 +150,8 @@ describe("Secret V3 Router", async () => { }; test.each(testSecrets)("Create secret in path $path", async ({ secret, path }) => { - const createSecretReqBody = { - workspaceId: seedData1.project.id, - environment: seedData1.environment.slug, - type: SecretType.Shared, - secretPath: path, - ...encryptSecret(projectKey, secret.key, secret.value, secret.comment) - }; - const createSecRes = await testServer.inject({ - method: "POST", - url: `/api/v3/secrets/${secret.key}`, - headers: { - authorization: `Bearer ${jwtAuthToken}` - }, - body: createSecretReqBody - }); - expect(createSecRes.statusCode).toBe(200); - const createdSecretPayload = JSON.parse(createSecRes.payload); - expect(createdSecretPayload).toHaveProperty("secret"); - const decryptedSecret = decryptSecret(projectKey, createdSecretPayload.secret); + const createdSecret = await createSecret({ projectKey, path, ...secret }); + const decryptedSecret = decryptSecret(projectKey, createdSecret); expect(decryptedSecret.key).toEqual(secret.key); expect(decryptedSecret.value).toEqual(secret.value); expect(decryptedSecret.comment).toEqual(secret.comment); @@ -118,9 +167,12 @@ describe("Secret V3 Router", async () => { }) ]) ); + await deleteSecret({ path, key: secret.key }); }); test.each(testSecrets)("Get secret by name in path $path", async ({ secret, path }) => { + await createSecret({ projectKey, path, ...secret }); + const getSecByNameRes = await testServer.inject({ method: "GET", url: `/api/v3/secrets/${secret.key}`, @@ -140,6 +192,8 @@ describe("Secret V3 Router", async () => { expect(decryptedSecret.key).toEqual(secret.key); expect(decryptedSecret.value).toEqual(secret.value); expect(decryptedSecret.comment).toEqual(secret.comment); + + await deleteSecret({ path, key: secret.key }); }); test.each(testSecrets)("Creating personal secret without shared throw error in path $path", async ({ secret }) => { @@ -164,6 +218,8 @@ describe("Secret V3 Router", async () => { }); test.each(testSecrets)("Creating personal secret in path $path", async ({ secret, path }) => { + await createSecret({ projectKey, path, ...secret }); + const createSecretReqBody = { workspaceId: seedData1.project.id, environment: seedData1.environment.slug, @@ -197,9 +253,12 @@ describe("Secret V3 Router", async () => { }) ]) ); + + await deleteSecret({ path, key: secret.key }); }); test.each(testSecrets)("Update secret in path $path", async ({ path, secret }) => { + await createSecret({ projectKey, path, ...secret }); const updateSecretReqBody = { workspaceId: seedData1.project.id, environment: seedData1.environment.slug, @@ -234,25 +293,14 @@ describe("Secret V3 Router", async () => { }) ]) ); + + await deleteSecret({ path, key: secret.key }); }); test.each(testSecrets)("Delete secret in path $path", async ({ secret, path }) => { - const deleteSecRes = await testServer.inject({ - method: "DELETE", - url: `/api/v3/secrets/${secret.key}`, - headers: { - authorization: `Bearer ${jwtAuthToken}` - }, - body: { - workspaceId: seedData1.project.id, - environment: seedData1.environment.slug, - secretPath: path - } - }); - expect(deleteSecRes.statusCode).toBe(200); - const updatedSecretPayload = JSON.parse(deleteSecRes.payload); - expect(updatedSecretPayload).toHaveProperty("secret"); - const decryptedSecret = decryptSecret(projectKey, updatedSecretPayload.secret); + await createSecret({ projectKey, path, ...secret }); + const deletedSecret = await deleteSecret({ path, key: secret.key }); + const decryptedSecret = decryptSecret(projectKey, deletedSecret); expect(decryptedSecret.key).toEqual(secret.key); // shared secret deletion should delete personal ones also @@ -274,37 +322,8 @@ describe("Secret V3 Router", async () => { test.each(testSecrets)( "Deleting personal one should not delete shared secret in path $path", async ({ secret, path }) => { - const createSharedSecRes = await testServer.inject({ - method: "POST", - url: `/api/v3/secrets/${secret.key}`, - headers: { - authorization: `Bearer ${jwtAuthToken}` - }, - body: { - workspaceId: seedData1.project.id, - environment: seedData1.environment.slug, - type: SecretType.Shared, - secretPath: path, - ...encryptSecret(projectKey, secret.key, secret.value, secret.comment) - } - }); - expect(createSharedSecRes.statusCode).toBe(200); - - const createPersonalSecRes = await testServer.inject({ - method: "POST", - url: `/api/v3/secrets/${secret.key}`, - headers: { - authorization: `Bearer ${jwtAuthToken}` - }, - body: { - workspaceId: seedData1.project.id, - environment: seedData1.environment.slug, - secretPath: path, - type: SecretType.Personal, - ...encryptSecret(projectKey, secret.key, secret.value, secret.comment) - } - }); - expect(createPersonalSecRes.statusCode).toBe(200); + await createSecret({ projectKey, path, ...secret }); // shared one + await createSecret({ projectKey, path, ...secret, type: SecretType.Personal }); // shared secret deletion should delete personal ones also const secrets = await getSecrets(seedData1.environment.slug, path); @@ -320,6 +339,7 @@ describe("Secret V3 Router", async () => { }) ]) ); + await deleteSecret({ path, key: secret.key }); } ); @@ -334,7 +354,7 @@ describe("Secret V3 Router", async () => { workspaceId: seedData1.project.id, environment: seedData1.environment.slug, secretPath: path, - secrets: Array.from(Array(10)).map((_e, i) => ({ + secrets: Array.from(Array(5)).map((_e, i) => ({ secretName: `BULK-${secret.key}-${i + 1}`, ...encryptSecret(projectKey, `BULK-${secret.key}-${i + 1}`, secret.value, secret.comment) })) @@ -348,7 +368,7 @@ describe("Secret V3 Router", async () => { const secrets = await getSecrets(seedData1.environment.slug, path); expect(secrets).toEqual( expect.arrayContaining( - Array.from(Array(10)).map((_e, i) => + Array.from(Array(5)).map((_e, i) => expect.objectContaining({ key: `BULK-${secret.key}-${i + 1}`, type: SecretType.Shared @@ -356,9 +376,13 @@ describe("Secret V3 Router", async () => { ) ) ); + + await Promise.all(Array.from(Array(5)).map((_e, i) => deleteSecret({ path, key: `BULK-${secret.key}-${i + 1}` }))); }); test.each(testSecrets)("Bulk create fail on existing secret in path $path", async ({ secret, path }) => { + await createSecret({ projectKey, ...secret, key: `BULK-${secret.key}-1`, path }); + const createSharedSecRes = await testServer.inject({ method: "POST", url: `/api/v3/secrets/batch`, @@ -369,16 +393,24 @@ describe("Secret V3 Router", async () => { workspaceId: seedData1.project.id, environment: seedData1.environment.slug, secretPath: path, - secrets: Array.from(Array(10)).map((_e, i) => ({ + secrets: Array.from(Array(5)).map((_e, i) => ({ secretName: `BULK-${secret.key}-${i + 1}`, ...encryptSecret(projectKey, `BULK-${secret.key}-${i + 1}`, secret.value, secret.comment) })) } }); expect(createSharedSecRes.statusCode).toBe(400); + + await deleteSecret({ path, key: `BULK-${secret.key}-1` }); }); test.each(testSecrets)("Bulk update secrets in path $path", async ({ secret, path }) => { + await Promise.all( + Array.from(Array(5)).map((_e, i) => + createSecret({ projectKey, ...secret, key: `BULK-${secret.key}-${i + 1}`, path }) + ) + ); + const updateSharedSecRes = await testServer.inject({ method: "PATCH", url: `/api/v3/secrets/batch`, @@ -389,7 +421,7 @@ describe("Secret V3 Router", async () => { workspaceId: seedData1.project.id, environment: seedData1.environment.slug, secretPath: path, - secrets: Array.from(Array(10)).map((_e, i) => ({ + secrets: Array.from(Array(5)).map((_e, i) => ({ secretName: `BULK-${secret.key}-${i + 1}`, ...encryptSecret(projectKey, `BULK-${secret.key}-${i + 1}`, "update-value", secret.comment) })) @@ -403,7 +435,7 @@ describe("Secret V3 Router", async () => { const secrets = await getSecrets(seedData1.environment.slug, path); expect(secrets).toEqual( expect.arrayContaining( - Array.from(Array(10)).map((_e, i) => + Array.from(Array(5)).map((_e, i) => expect.objectContaining({ key: `BULK-${secret.key}-${i + 1}`, value: "update-value", @@ -412,10 +444,17 @@ describe("Secret V3 Router", async () => { ) ) ); + await Promise.all(Array.from(Array(5)).map((_e, i) => deleteSecret({ path, key: `BULK-${secret.key}-${i + 1}` }))); }); test.each(testSecrets)("Bulk delete secrets in path $path", async ({ secret, path }) => { - const updateSharedSecRes = await testServer.inject({ + await Promise.all( + Array.from(Array(5)).map((_e, i) => + createSecret({ projectKey, ...secret, key: `BULK-${secret.key}-${i + 1}`, path }) + ) + ); + + const deletedSharedSecRes = await testServer.inject({ method: "DELETE", url: `/api/v3/secrets/batch`, headers: { @@ -425,20 +464,21 @@ describe("Secret V3 Router", async () => { workspaceId: seedData1.project.id, environment: seedData1.environment.slug, secretPath: path, - secrets: Array.from(Array(10)).map((_e, i) => ({ + secrets: Array.from(Array(5)).map((_e, i) => ({ secretName: `BULK-${secret.key}-${i + 1}` })) } }); - expect(updateSharedSecRes.statusCode).toBe(200); - const updateSharedSecPayload = JSON.parse(updateSharedSecRes.payload); - expect(updateSharedSecPayload).toHaveProperty("secrets"); + + expect(deletedSharedSecRes.statusCode).toBe(200); + const deletedSecretPayload = JSON.parse(deletedSharedSecRes.payload); + expect(deletedSecretPayload).toHaveProperty("secrets"); // bulk ones should exist const secrets = await getSecrets(seedData1.environment.slug, path); expect(secrets).toEqual( expect.not.arrayContaining( - Array.from(Array(10)).map((_e, i) => + Array.from(Array(5)).map((_e, i) => expect.objectContaining({ key: `BULK-${secret.value}-${i + 1}`, type: SecretType.Shared @@ -449,8 +489,57 @@ describe("Secret V3 Router", async () => { }); }); +const createRawSecret = async (dto: { + path: string; + key: string; + value: string; + comment: string; + type?: SecretType; +}) => { + const createSecretReqBody = { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + type: dto.type || SecretType.Shared, + secretValue: dto.value, + secretComment: dto.comment, + secretPath: dto.path + }; + const createSecRes = await testServer.inject({ + method: "POST", + url: `/api/v3/secrets/raw/${dto.key}`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: createSecretReqBody + }); + expect(createSecRes.statusCode).toBe(200); + const createdSecretPayload = JSON.parse(createSecRes.payload); + expect(createdSecretPayload).toHaveProperty("secret"); + return createdSecretPayload.secret; +}; + +const deleteRawSecret = async (dto: { path: string; key: string }) => { + const deleteSecRes = await testServer.inject({ + method: "DELETE", + url: `/api/v3/secrets/raw/${dto.key}`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + secretPath: dto.path + } + }); + expect(deleteSecRes.statusCode).toBe(200); + const updatedSecretPayload = JSON.parse(deleteSecRes.payload); + expect(updatedSecretPayload).toHaveProperty("secret"); + return updatedSecretPayload.secret; +}; + // raw secret endpoints describe("Secret V3 Raw Router", async () => { + let folderId = ""; const testRawSecrets = [ { path: "/", @@ -543,6 +632,7 @@ describe("Secret V3 Raw Router", async () => { } }); expect(folderCreate.statusCode).toBe(200); + folderId = folderCreate.json().folder.id; }); afterAll(async () => { @@ -569,6 +659,19 @@ describe("Secret V3 Raw Router", async () => { } }); expect(setBotInActive.statusCode).toEqual(200); + const deleteFolder = await testServer.inject({ + method: "DELETE", + url: `/api/v1/folders/${folderId}`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + path: "/nested1/nested2" + } + }); + expect(deleteFolder.statusCode).toBe(200); }); const getSecrets = async (environment: string, secretPath = "/") => { @@ -621,9 +724,13 @@ describe("Secret V3 Raw Router", async () => { }) ]) ); + + await deleteRawSecret({ path, key: secret.key }); }); test.each(testRawSecrets)("Get secret by name raw in path $path", async ({ secret, path }) => { + await createRawSecret({ path, ...secret }); + const getSecByNameRes = await testServer.inject({ method: "GET", url: `/api/v3/secrets/raw/${secret.key}`, @@ -645,9 +752,33 @@ describe("Secret V3 Raw Router", async () => { secretValue: secret.value }) ); + + await deleteRawSecret({ path, key: secret.key }); + }); + + test.each(testRawSecrets)("List secret raw in path $path", async ({ secret, path }) => { + await Promise.all( + Array.from(Array(5)).map((_e, i) => createRawSecret({ path, ...secret, key: `BULK-${secret.key}-${i + 1}` })) + ); + + const secrets = await getSecrets(seedData1.environment.slug, path); + expect(secrets.length).toEqual(5); + expect(secrets).toEqual( + expect.arrayContaining( + Array.from(Array(5)).map((_e, i) => + expect.objectContaining({ value: expect.any(String), key: `BULK-${secret.key}-${i + 1}` }) + ) + ) + ); + + await Promise.all( + Array.from(Array(5)).map((_e, i) => deleteRawSecret({ path, key: `BULK-${secret.key}-${i + 1}` })) + ); }); test.each(testRawSecrets)("Update secret raw in path $path", async ({ secret, path }) => { + await createRawSecret({ path, ...secret }); + const updateSecretReqBody = { workspaceId: seedData1.project.id, environment: seedData1.environment.slug, @@ -655,7 +786,6 @@ describe("Secret V3 Raw Router", async () => { secretValue: "new-value", secretPath: path }; - const updateSecRes = await testServer.inject({ method: "PATCH", url: `/api/v3/secrets/raw/${secret.key}`, @@ -680,9 +810,13 @@ describe("Secret V3 Raw Router", async () => { }) ]) ); + + await deleteRawSecret({ path, key: secret.key }); }); test.each(testRawSecrets)("Delete secret raw in path $path", async ({ path, secret }) => { + await createRawSecret({ path, ...secret }); + const deletedSecretReqBody = { workspaceId: seedData1.project.id, environment: seedData1.environment.slug, @@ -703,14 +837,7 @@ describe("Secret V3 Raw Router", async () => { // fetch secrets const secrets = await getSecrets(seedData1.environment.slug, path); - expect(secrets).toEqual( - expect.arrayContaining([ - expect.not.objectContaining({ - key: secret.key, - type: SecretType.Shared - }) - ]) - ); + expect(secrets).toEqual([]); }); }); From 35d589a15fc3b370415f064298aa6a8af75ae1a0 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Mon, 19 Feb 2024 15:04:49 -0500 Subject: [PATCH 18/29] add more secret test cases --- backend/e2e-test/routes/v1/identity.spec.ts | 59 +++++++++++++++++---- 1 file changed, 48 insertions(+), 11 deletions(-) diff --git a/backend/e2e-test/routes/v1/identity.spec.ts b/backend/e2e-test/routes/v1/identity.spec.ts index 9497655cab..ab8f74b2be 100644 --- a/backend/e2e-test/routes/v1/identity.spec.ts +++ b/backend/e2e-test/routes/v1/identity.spec.ts @@ -51,7 +51,7 @@ const deleteRawSecret = async (dto: { path: string; key: string; token: string } return updatedSecretPayload.secret; }; -describe("Identity token secret ops", async () => { +describe("Secret operations with Identity token", async () => { let identityToken = ""; let folderId = ""; beforeAll(async () => { @@ -183,11 +183,11 @@ describe("Identity token secret ops", async () => { expect(deleteFolder.statusCode).toBe(200); }); - const testRawSecrets = [ + const secretTestCases = [ { path: "/", secret: { - key: "ID-SEC", + key: "secret-key-1", value: "something-secret", comment: "some comment" } @@ -195,9 +195,46 @@ describe("Identity token secret ops", async () => { { path: "/nested1/nested2/folder", secret: { - key: "NESTED-ID-SEC", - value: "something-secret", - comment: "some comment" + key: "secret-key-2", + value: `-----BEGIN PRIVATE KEY----- + MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCa6eeFk+cMVqFn + hoVQDYgn2Ptp5Azysr2UPq6P73pCL9BzUtOXKZROqDyGehzzfg3wE2KdYU1Jk5Uq + fP0ZOWDIlM2SaVCSI3FW32o5+ZiggjpqcVdLFc/PS0S/ZdSmpPd8h11iO2brtIAI + ugTW8fcKlGSNUwx9aFmE7A6JnTRliTxB1l6QaC+YAwTK39VgeVH2gDSWC407aS15 + QobAkaBKKmFkzB5D7i2ZJwt+uXJV/rbLmyDmtnw0lubciGn7NX9wbYef180fisqT + aPNAz0nPKk0fFH2Wd5MZixNGbrrpDA+FCYvI5doThZyT2hpj08qWP07oXXCAqw46 + IEupNSILAgMBAAECggEBAIJb5KzeaiZS3B3O8G4OBQ5rJB3WfyLYUHnoSWLsBbie + nc392/ovThLmtZAAQE6SO85Tsb93+t64Z2TKqv1H8G658UeMgfWIB78v4CcLJ2mi + TN/3opqXrzjkQOTDHzBgT7al/mpETHZ6fOdbCemK0fVALGFUioUZg4M8VXtuI4Jw + q28jAyoRKrCrzda4BeQ553NZ4G5RvwhX3O2I8B8upTbt5hLcisBKy8MPLYY5LUFj + YKAP+raf6QLliP6KYHuVxUlgzxjLTxVG41etcyqqZF+foyiKBO3PU3n8oh++tgQP + ExOxiR0JSkBG5b+oOBD0zxcvo3/SjBHn0dJOZCSU2SkCgYEAyCe676XnNyBZMRD7 + 6trsaoiCWBpA6M8H44+x3w4cQFtqV38RyLy60D+iMKjIaLqeBbnay61VMzo24Bz3 + EuF2n4+9k/MetLJ0NCw8HmN5k0WSMD2BFsJWG8glVbzaqzehP4tIclwDTYc1jQVt + IoV2/iL7HGT+x2daUwbU5kN5hK0CgYEAxiLB+fmjxJW7VY4SHDLqPdpIW0q/kv4K + d/yZBrCX799vjmFb9vLh7PkQUfJhMJ/ttJOd7EtT3xh4mfkBeLfHwVU0d/ahbmSH + UJu/E9ZGxAW3PP0kxHZtPrLKQwBnfq8AxBauIhR3rPSorQTIOKtwz1jMlHFSUpuL + 3KeK2YfDYJcCgYEAkQnJOlNcAuRb/WQzSHIvktssqK8NjiZHryy3Vc0hx7j2jES2 + HGI2dSVHYD9OSiXA0KFm3OTTsnViwm/60iGzFdjRJV6tR39xGUVcoyCuPnvRfUd0 + PYvBXgxgkYpyYlPDcwp5CvWGJy3tLi1acgOIwIuUr3S38sL//t4adGk8q1kCgYB8 + Jbs1Tl53BvrimKpwUNbE+sjrquJu0A7vL68SqgQJoQ7dP9PH4Ff/i+/V6PFM7mib + BQOm02wyFbs7fvKVGVJoqWK+6CIucX732x7W5yRgHtS5ukQXdbzt1Ek3wkEW98Cb + HTruz7RNAt/NyXlLSODeit1lBbx3Vk9EaxZtRsv88QKBgGn7JwXgez9NOyobsNIo + QVO80rpUeenSjuFi+R0VmbLKe/wgAQbYJ0xTAsQ0btqViMzB27D6mJyC+KUIwWNX + MN8a+m46v4kqvZkKL2c4gmDibyURNe/vCtCHFuanJS/1mo2tr4XDyEeiuK52eTd9 + omQDpP86RX/hIIQ+JyLSaWYa + -----END PRIVATE KEY-----`, + comment: + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation" + } + }, + { + path: "/nested1/nested2/folder", + secret: { + key: "secret-key-3", + value: + "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdC4gU2VkIGRvIGVpdXNtb2QgdGVtcG9yIGluY2lkaWR1bnQgdXQgbGFib3JlIGV0IGRvbG9yZSBtYWduYSBhbGlxdWEuIFV0IGVuaW0gYWQgbWluaW0gdmVuaWFtLCBxdWlzIG5vc3RydWQgZXhlcmNpdGF0aW9uCg==", + comment: "" } } ]; @@ -220,7 +257,7 @@ describe("Identity token secret ops", async () => { return secrets.map((el) => ({ key: el.secretKey, value: el.secretValue, type: el.type, version: el.version })); }; - test.each(testRawSecrets)("Create secret raw in path $path", async ({ secret, path }) => { + test.each(secretTestCases)("Create raw secret", async ({ secret, path }) => { const createSecretReqBody = { workspaceId: seedData1.project.id, environment: seedData1.environment.slug, @@ -256,7 +293,7 @@ describe("Identity token secret ops", async () => { await deleteRawSecret({ path, key: secret.key, token: identityToken }); }); - test.each(testRawSecrets)("Get secret by name raw in path $path", async ({ secret, path }) => { + test.each(secretTestCases)("Fetch raw secret by name", async ({ secret, path }) => { await createRawSecret({ path, ...secret, token: identityToken }); const getSecByNameRes = await testServer.inject({ @@ -284,7 +321,7 @@ describe("Identity token secret ops", async () => { await deleteRawSecret({ path, key: secret.key, token: identityToken }); }); - test.each(testRawSecrets)("List secret raw in path $path", async ({ secret, path }) => { + test.each(secretTestCases)("List secret raw in path $path", async ({ secret, path }) => { await Promise.all( Array.from(Array(5)).map((_e, i) => createRawSecret({ path, token: identityToken, ...secret, key: `BULK-${secret.key}-${i + 1}` }) @@ -308,7 +345,7 @@ describe("Identity token secret ops", async () => { ); }); - test.each(testRawSecrets)("Update secret raw in path $path", async ({ secret, path }) => { + test.each(secretTestCases)("Update raw secret", async ({ secret, path }) => { await createRawSecret({ path, ...secret, token: identityToken }); const updateSecretReqBody = { @@ -346,7 +383,7 @@ describe("Identity token secret ops", async () => { await deleteRawSecret({ path, key: secret.key, token: identityToken }); }); - test.each(testRawSecrets)("Delete secret raw in path $path", async ({ path, secret }) => { + test.each(secretTestCases)("Delete raw secret", async ({ path, secret }) => { await createRawSecret({ path, ...secret, token: identityToken }); const deletedSecretReqBody = { From e4b89371f0a584b6b4ae282633e9cbd7212ca50c Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Mon, 19 Feb 2024 16:08:21 -0500 Subject: [PATCH 19/29] add env slug to make expect more strict --- backend/e2e-test/routes/v1/secret-import.spec.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/backend/e2e-test/routes/v1/secret-import.spec.ts b/backend/e2e-test/routes/v1/secret-import.spec.ts index 56a8aad51a..ba37b5f420 100644 --- a/backend/e2e-test/routes/v1/secret-import.spec.ts +++ b/backend/e2e-test/routes/v1/secret-import.spec.ts @@ -103,8 +103,11 @@ describe("Secret Import Router", async () => { }); test("Update secret import position", async () => { - const createdImport1 = await createSecretImport("/", "dev"); - const createdImport2 = await createSecretImport("/", "staging"); + const devImportDetails = { path: "/", envSlug: "dev" }; + const stagingImportDetails = { path: "/", envSlug: "staging" }; + + const createdImport1 = await createSecretImport(devImportDetails.path, devImportDetails.envSlug); + const createdImport2 = await createSecretImport(stagingImportDetails.path, stagingImportDetails.envSlug); const updateImportRes = await testServer.inject({ method: "PATCH", @@ -133,7 +136,7 @@ describe("Secret Import Router", async () => { position: 2, importEnv: expect.objectContaining({ name: expect.any(String), - slug: expect.any(String), + slug: expect.stringMatching(devImportDetails.envSlug), id: expect.any(String) }) }) From d428fd055b0df6ae94842d17a41cc5c147b295e4 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Mon, 19 Feb 2024 16:52:59 -0500 Subject: [PATCH 20/29] update test envs --- .env.test.example | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/.env.test.example b/.env.test.example index 2bbeccb9c6..bce047f77d 100644 --- a/.env.test.example +++ b/.env.test.example @@ -1,7 +1,4 @@ -DB_CONNECTION_URI=postgres://infisical:infisical@localhost:5430/infisical-test -REDIS_URL=redis://redis:6379 -# Keys -# Required key for platform encryption/decryption ops -# THIS IS A SAMPLE ENCRYPTION KEY AND SHOULD NOT BE USED FOR PRODUCTION -ENCRYPTION_KEY=6c1fe4e407b8911c104518103505b218 -AUTH_SECRET=something-random +REDIS_URL=redis://localhost:6379 +DB_CONNECTION_URI=postgres://infisical:infisical@localhost/infisical?sslmode=disable +AUTH_SECRET=4bnfe4e407b8921c104518903515b218 +ENCRYPTION_KEY=4bnfe4e407b8921c104518903515b218 \ No newline at end of file From 43879f68136e29099adbf40e1c678a8c62e45f57 Mon Sep 17 00:00:00 2001 From: vmatsiiako <78047717+vmatsiiako@users.noreply.github.com> Date: Mon, 19 Feb 2024 17:05:36 -0800 Subject: [PATCH 21/29] Update scanning-overview.mdx --- docs/cli/scanning-overview.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cli/scanning-overview.mdx b/docs/cli/scanning-overview.mdx index 0163d5dcaa..748c9d4e1b 100644 --- a/docs/cli/scanning-overview.mdx +++ b/docs/cli/scanning-overview.mdx @@ -34,7 +34,7 @@ In addition to scanning for past leaks, this new addition also actively aids in infisical scan git-changes # Display the full secret findings - infisical git-changes --verbose + infisical scan git-changes --verbose ``` Scanning for secrets before you commit your changes is great way to prevent leaks. Infisical makes this easy with the sub command `git-changes`. From 2996efe9d559939b388e7c305679fabc01448f53 Mon Sep 17 00:00:00 2001 From: Akhil Mohan Date: Tue, 20 Feb 2024 20:58:10 +0530 Subject: [PATCH 22/29] feat: made secrets ops test to have both identity and jwt token based and test for identity --- backend/e2e-test/routes/v1/identity.spec.ts | 422 ++----------- backend/e2e-test/routes/v3/secrets.spec.ts | 568 +++++++++--------- .../services/license/__mocks__/licence-fns.ts | 27 + 3 files changed, 362 insertions(+), 655 deletions(-) create mode 100644 backend/src/ee/services/license/__mocks__/licence-fns.ts diff --git a/backend/e2e-test/routes/v1/identity.spec.ts b/backend/e2e-test/routes/v1/identity.spec.ts index ab8f74b2be..ccb530c796 100644 --- a/backend/e2e-test/routes/v1/identity.spec.ts +++ b/backend/e2e-test/routes/v1/identity.spec.ts @@ -1,411 +1,71 @@ -import { SecretType } from "@app/db/schemas"; -import { getUserPrivateKey, seedData1 } from "@app/db/seed-data"; -import { decryptAsymmetric, encryptAsymmetric } from "@app/lib/crypto"; +import { OrgMembershipRole } from "@app/db/schemas"; +import { seedData1 } from "@app/db/seed-data"; -const createRawSecret = async (dto: { - path: string; - key: string; - value: string; - comment: string; - type?: SecretType; - token: string; -}) => { - const createSecretReqBody = { - workspaceId: seedData1.project.id, - environment: seedData1.environment.slug, - type: dto.type || SecretType.Shared, - secretValue: dto.value, - secretComment: dto.comment, - secretPath: dto.path - }; - const createSecRes = await testServer.inject({ +export const createIdentity = async (name: string, role: string) => { + const createIdentityRes = await testServer.inject({ method: "POST", - url: `/api/v3/secrets/raw/${dto.key}`, - headers: { - authorization: `Bearer ${dto.token}` + url: "/api/v1/identities", + body: { + name, + role, + organizationId: seedData1.organization.id }, - body: createSecretReqBody + headers: { + authorization: `Bearer ${jwtAuthToken}` + } }); - expect(createSecRes.statusCode).toBe(200); - const createdSecretPayload = JSON.parse(createSecRes.payload); - expect(createdSecretPayload).toHaveProperty("secret"); - return createdSecretPayload.secret; + expect(createIdentityRes.statusCode).toBe(200); + return createIdentityRes.json().identity; }; -const deleteRawSecret = async (dto: { path: string; key: string; token: string }) => { - const deleteSecRes = await testServer.inject({ +export const deleteIdentity = async (id: string) => { + const deleteIdentityRes = await testServer.inject({ method: "DELETE", - url: `/api/v3/secrets/raw/${dto.key}`, + url: `/api/v1/identities/${id}`, headers: { - authorization: `Bearer ${dto.token}` - }, - body: { - workspaceId: seedData1.project.id, - environment: seedData1.environment.slug, - secretPath: dto.path + authorization: `Bearer ${jwtAuthToken}` } }); - expect(deleteSecRes.statusCode).toBe(200); - const updatedSecretPayload = JSON.parse(deleteSecRes.payload); - expect(updatedSecretPayload).toHaveProperty("secret"); - return updatedSecretPayload.secret; + expect(deleteIdentityRes.statusCode).toBe(200); + return deleteIdentityRes.json().identity; }; -describe("Secret operations with Identity token", async () => { - let identityToken = ""; - let folderId = ""; - beforeAll(async () => { - // enable bot - const res = await testServer.inject({ - method: "GET", - url: `/api/v2/workspace/${seedData1.project.id}/encrypted-key`, - headers: { - authorization: `Bearer ${jwtAuthToken}` - } - }); - expect(res.statusCode).toEqual(200); - const projectKeyEnc = JSON.parse(res.payload); - - const userInfoRes = await testServer.inject({ - method: "GET", - url: "/api/v2/users/me", - headers: { - authorization: `Bearer ${jwtAuthToken}` - } - }); - expect(userInfoRes.statusCode).toEqual(200); - const { user: userInfo } = JSON.parse(userInfoRes.payload); - - const privateKey = await getUserPrivateKey(seedData1.password, userInfo); - const projectKey = decryptAsymmetric({ - ciphertext: projectKeyEnc.encryptedKey, - nonce: projectKeyEnc.nonce, - publicKey: projectKeyEnc.sender.publicKey, - privateKey - }); - - const projectBotRes = await testServer.inject({ - method: "GET", - url: `/api/v1/bot/${seedData1.project.id}`, - headers: { - authorization: `Bearer ${jwtAuthToken}` - } - }); - expect(projectBotRes.statusCode).toEqual(200); - const projectBot = JSON.parse(projectBotRes.payload).bot; - const botKey = encryptAsymmetric(projectKey, projectBot.publicKey, privateKey); +describe("Identity v1", async () => { + test("Create identity", async () => { + const newIdentity = await createIdentity("mac1", OrgMembershipRole.Admin); + expect(newIdentity.name).toBe("mac1"); + expect(newIdentity.authMethod).toBeNull(); - // set bot as active - const setBotActive = await testServer.inject({ - method: "PATCH", - url: `/api/v1/bot/${projectBot.id}/active`, - headers: { - authorization: `Bearer ${jwtAuthToken}` - }, - body: { - isActive: true, - workspaceId: seedData1.project.id, - botKey: { - encryptedKey: botKey.ciphertext, - nonce: botKey.nonce - } - } - }); - expect(setBotActive.statusCode).toEqual(200); - - // set identity token - const identityLogin = await testServer.inject({ - method: "POST", - url: "/api/v1/auth/universal-auth/login", - body: { - clientSecret: seedData1.machineIdentity.clientCredentials.secret, - clientId: seedData1.machineIdentity.clientCredentials.id - } - }); - expect(identityLogin.statusCode).toBe(200); - identityToken = identityLogin.json().accessToken; - - // create a deep folder - const folderCreate = await testServer.inject({ - method: "POST", - url: `/api/v1/folders`, - headers: { - authorization: `Bearer ${jwtAuthToken}` - }, - body: { - workspaceId: seedData1.project.id, - environment: seedData1.environment.slug, - name: "folder", - path: "/nested1/nested2" - } - }); - expect(folderCreate.statusCode).toBe(200); - folderId = folderCreate.json().folder.id; + await deleteIdentity(newIdentity.id); }); - afterAll(async () => { - const projectBotRes = await testServer.inject({ - method: "GET", - url: `/api/v1/bot/${seedData1.project.id}`, - headers: { - authorization: `Bearer ${jwtAuthToken}` - } - }); - expect(projectBotRes.statusCode).toEqual(200); - const projectBot = JSON.parse(projectBotRes.payload).bot; + test("Update identity", async () => { + const newIdentity = await createIdentity("mac1", OrgMembershipRole.Admin); + expect(newIdentity.name).toBe("mac1"); + expect(newIdentity.authMethod).toBeNull(); - // set bot as inactive - const setBotInActive = await testServer.inject({ + const updatedIdentity = await testServer.inject({ method: "PATCH", - url: `/api/v1/bot/${projectBot.id}/active`, + url: `/api/v1/identities/${newIdentity.id}`, headers: { authorization: `Bearer ${jwtAuthToken}` }, body: { - isActive: false, - workspaceId: seedData1.project.id + name: "updated-mac-1", + role: OrgMembershipRole.Member } }); - expect(setBotInActive.statusCode).toEqual(200); - const deleteFolder = await testServer.inject({ - method: "DELETE", - url: `/api/v1/folders/${folderId}`, - headers: { - authorization: `Bearer ${jwtAuthToken}` - }, - body: { - workspaceId: seedData1.project.id, - environment: seedData1.environment.slug, - path: "/nested1/nested2" - } - }); - expect(deleteFolder.statusCode).toBe(200); - }); - - const secretTestCases = [ - { - path: "/", - secret: { - key: "secret-key-1", - value: "something-secret", - comment: "some comment" - } - }, - { - path: "/nested1/nested2/folder", - secret: { - key: "secret-key-2", - value: `-----BEGIN PRIVATE KEY----- - MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCa6eeFk+cMVqFn - hoVQDYgn2Ptp5Azysr2UPq6P73pCL9BzUtOXKZROqDyGehzzfg3wE2KdYU1Jk5Uq - fP0ZOWDIlM2SaVCSI3FW32o5+ZiggjpqcVdLFc/PS0S/ZdSmpPd8h11iO2brtIAI - ugTW8fcKlGSNUwx9aFmE7A6JnTRliTxB1l6QaC+YAwTK39VgeVH2gDSWC407aS15 - QobAkaBKKmFkzB5D7i2ZJwt+uXJV/rbLmyDmtnw0lubciGn7NX9wbYef180fisqT - aPNAz0nPKk0fFH2Wd5MZixNGbrrpDA+FCYvI5doThZyT2hpj08qWP07oXXCAqw46 - IEupNSILAgMBAAECggEBAIJb5KzeaiZS3B3O8G4OBQ5rJB3WfyLYUHnoSWLsBbie - nc392/ovThLmtZAAQE6SO85Tsb93+t64Z2TKqv1H8G658UeMgfWIB78v4CcLJ2mi - TN/3opqXrzjkQOTDHzBgT7al/mpETHZ6fOdbCemK0fVALGFUioUZg4M8VXtuI4Jw - q28jAyoRKrCrzda4BeQ553NZ4G5RvwhX3O2I8B8upTbt5hLcisBKy8MPLYY5LUFj - YKAP+raf6QLliP6KYHuVxUlgzxjLTxVG41etcyqqZF+foyiKBO3PU3n8oh++tgQP - ExOxiR0JSkBG5b+oOBD0zxcvo3/SjBHn0dJOZCSU2SkCgYEAyCe676XnNyBZMRD7 - 6trsaoiCWBpA6M8H44+x3w4cQFtqV38RyLy60D+iMKjIaLqeBbnay61VMzo24Bz3 - EuF2n4+9k/MetLJ0NCw8HmN5k0WSMD2BFsJWG8glVbzaqzehP4tIclwDTYc1jQVt - IoV2/iL7HGT+x2daUwbU5kN5hK0CgYEAxiLB+fmjxJW7VY4SHDLqPdpIW0q/kv4K - d/yZBrCX799vjmFb9vLh7PkQUfJhMJ/ttJOd7EtT3xh4mfkBeLfHwVU0d/ahbmSH - UJu/E9ZGxAW3PP0kxHZtPrLKQwBnfq8AxBauIhR3rPSorQTIOKtwz1jMlHFSUpuL - 3KeK2YfDYJcCgYEAkQnJOlNcAuRb/WQzSHIvktssqK8NjiZHryy3Vc0hx7j2jES2 - HGI2dSVHYD9OSiXA0KFm3OTTsnViwm/60iGzFdjRJV6tR39xGUVcoyCuPnvRfUd0 - PYvBXgxgkYpyYlPDcwp5CvWGJy3tLi1acgOIwIuUr3S38sL//t4adGk8q1kCgYB8 - Jbs1Tl53BvrimKpwUNbE+sjrquJu0A7vL68SqgQJoQ7dP9PH4Ff/i+/V6PFM7mib - BQOm02wyFbs7fvKVGVJoqWK+6CIucX732x7W5yRgHtS5ukQXdbzt1Ek3wkEW98Cb - HTruz7RNAt/NyXlLSODeit1lBbx3Vk9EaxZtRsv88QKBgGn7JwXgez9NOyobsNIo - QVO80rpUeenSjuFi+R0VmbLKe/wgAQbYJ0xTAsQ0btqViMzB27D6mJyC+KUIwWNX - MN8a+m46v4kqvZkKL2c4gmDibyURNe/vCtCHFuanJS/1mo2tr4XDyEeiuK52eTd9 - omQDpP86RX/hIIQ+JyLSaWYa - -----END PRIVATE KEY-----`, - comment: - "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation" - } - }, - { - path: "/nested1/nested2/folder", - secret: { - key: "secret-key-3", - value: - "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdC4gU2VkIGRvIGVpdXNtb2QgdGVtcG9yIGluY2lkaWR1bnQgdXQgbGFib3JlIGV0IGRvbG9yZSBtYWduYSBhbGlxdWEuIFV0IGVuaW0gYWQgbWluaW0gdmVuaWFtLCBxdWlzIG5vc3RydWQgZXhlcmNpdGF0aW9uCg==", - comment: "" - } - } - ]; - - const getSecrets = async (environment: string, secretPath = "/") => { - const res = await testServer.inject({ - method: "GET", - url: `/api/v3/secrets/raw`, - headers: { - authorization: `Bearer ${identityToken}` - }, - query: { - secretPath, - environment, - workspaceId: seedData1.project.id - } - }); - const secrets: { secretKey: string; secretValue: string; type: SecretType; version: number }[] = - JSON.parse(res.payload).secrets || []; - return secrets.map((el) => ({ key: el.secretKey, value: el.secretValue, type: el.type, version: el.version })); - }; - - test.each(secretTestCases)("Create raw secret", async ({ secret, path }) => { - const createSecretReqBody = { - workspaceId: seedData1.project.id, - environment: seedData1.environment.slug, - type: SecretType.Shared, - secretValue: secret.value, - secretComment: secret.comment, - secretPath: path - }; - const createSecRes = await testServer.inject({ - method: "POST", - url: `/api/v3/secrets/raw/${secret.key}`, - headers: { - authorization: `Bearer ${identityToken}` - }, - body: createSecretReqBody - }); - expect(createSecRes.statusCode).toBe(200); - const createdSecretPayload = JSON.parse(createSecRes.payload); - expect(createdSecretPayload).toHaveProperty("secret"); - - // fetch secrets - const secrets = await getSecrets(seedData1.environment.slug, path); - expect(secrets).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - key: secret.key, - value: secret.value, - type: SecretType.Shared - }) - ]) - ); - - await deleteRawSecret({ path, key: secret.key, token: identityToken }); - }); + expect(updatedIdentity.statusCode).toBe(200); + expect(updatedIdentity.json().identity.name).toBe("updated-mac-1"); - test.each(secretTestCases)("Fetch raw secret by name", async ({ secret, path }) => { - await createRawSecret({ path, ...secret, token: identityToken }); - - const getSecByNameRes = await testServer.inject({ - method: "GET", - url: `/api/v3/secrets/raw/${secret.key}`, - headers: { - authorization: `Bearer ${identityToken}` - }, - query: { - workspaceId: seedData1.project.id, - environment: seedData1.environment.slug, - secretPath: path - } - }); - expect(getSecByNameRes.statusCode).toBe(200); - const secretPayload = JSON.parse(getSecByNameRes.payload); - expect(secretPayload).toHaveProperty("secret"); - expect(secretPayload.secret).toEqual( - expect.objectContaining({ - secretKey: secret.key, - secretValue: secret.value - }) - ); - - await deleteRawSecret({ path, key: secret.key, token: identityToken }); + await deleteIdentity(newIdentity.id); }); - test.each(secretTestCases)("List secret raw in path $path", async ({ secret, path }) => { - await Promise.all( - Array.from(Array(5)).map((_e, i) => - createRawSecret({ path, token: identityToken, ...secret, key: `BULK-${secret.key}-${i + 1}` }) - ) - ); - - const secrets = await getSecrets(seedData1.environment.slug, path); - expect(secrets.length).toEqual(5); - expect(secrets).toEqual( - expect.arrayContaining( - Array.from(Array(5)).map((_e, i) => - expect.objectContaining({ value: expect.any(String), key: `BULK-${secret.key}-${i + 1}` }) - ) - ) - ); - - await Promise.all( - Array.from(Array(5)).map((_e, i) => - deleteRawSecret({ path, token: identityToken, key: `BULK-${secret.key}-${i + 1}` }) - ) - ); - }); - - test.each(secretTestCases)("Update raw secret", async ({ secret, path }) => { - await createRawSecret({ path, ...secret, token: identityToken }); - - const updateSecretReqBody = { - workspaceId: seedData1.project.id, - environment: seedData1.environment.slug, - type: SecretType.Shared, - secretValue: "new-value", - secretPath: path - }; - const updateSecRes = await testServer.inject({ - method: "PATCH", - url: `/api/v3/secrets/raw/${secret.key}`, - headers: { - authorization: `Bearer ${identityToken}` - }, - body: updateSecretReqBody - }); - expect(updateSecRes.statusCode).toBe(200); - const updatedSecretPayload = JSON.parse(updateSecRes.payload); - expect(updatedSecretPayload).toHaveProperty("secret"); - - // fetch secrets - const secrets = await getSecrets(seedData1.environment.slug, path); - expect(secrets).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - key: secret.key, - value: "new-value", - version: 2, - type: SecretType.Shared - }) - ]) - ); - - await deleteRawSecret({ path, key: secret.key, token: identityToken }); - }); - - test.each(secretTestCases)("Delete raw secret", async ({ path, secret }) => { - await createRawSecret({ path, ...secret, token: identityToken }); - - const deletedSecretReqBody = { - workspaceId: seedData1.project.id, - environment: seedData1.environment.slug, - type: SecretType.Shared, - secretPath: path - }; - const deletedSecRes = await testServer.inject({ - method: "DELETE", - url: `/api/v3/secrets/raw/${secret.key}`, - headers: { - authorization: `Bearer ${identityToken}` - }, - body: deletedSecretReqBody - }); - expect(deletedSecRes.statusCode).toBe(200); - const deletedSecretPayload = JSON.parse(deletedSecRes.payload); - expect(deletedSecretPayload).toHaveProperty("secret"); + test("Delete Identity", async () => { + const newIdentity = await createIdentity("mac1", OrgMembershipRole.Admin); - // fetch secrets - const secrets = await getSecrets(seedData1.environment.slug, path); - expect(secrets).toEqual([]); + const deletedIdentity = await deleteIdentity(newIdentity.id); + expect(deletedIdentity.name).toBe("mac1"); }); }); diff --git a/backend/e2e-test/routes/v3/secrets.spec.ts b/backend/e2e-test/routes/v3/secrets.spec.ts index b0a8ea04cd..c74db4e6de 100644 --- a/backend/e2e-test/routes/v3/secrets.spec.ts +++ b/backend/e2e-test/routes/v3/secrets.spec.ts @@ -1,6 +1,7 @@ import { SecretType, TSecrets } from "@app/db/schemas"; import { decryptSecret, encryptSecret, getUserPrivateKey, seedData1 } from "@app/db/seed-data"; import { decryptAsymmetric, encryptAsymmetric } from "@app/lib/crypto"; +import { AuthMode } from "@app/services/auth/auth-type"; const createSecret = async (dto: { projectKey: string; @@ -538,308 +539,327 @@ const deleteRawSecret = async (dto: { path: string; key: string }) => { }; // raw secret endpoints -describe("Secret V3 Raw Router", async () => { - let folderId = ""; - const testRawSecrets = [ - { - path: "/", - secret: { - key: "RAW-SEC1", - value: "something-secret", - comment: "some comment" - } - }, - { - path: "/nested1/nested2/folder", - secret: { - key: "NESTED-RAW-SEC1", - value: "something-secret", - comment: "some comment" - } - } - ]; - - beforeAll(async () => { - const res = await testServer.inject({ - method: "GET", - url: `/api/v2/workspace/${seedData1.project.id}/encrypted-key`, - headers: { - authorization: `Bearer ${jwtAuthToken}` +describe.each([{ auth: AuthMode.JWT }, { auth: AuthMode.IDENTITY_ACCESS_TOKEN }])( + "Secret V3 Raw Router - $auth mode", + async ({ auth }) => { + let folderId = ""; + let authToken = ""; + const testRawSecrets = [ + { + path: "/", + secret: { + key: "RAW-SEC1", + value: "something-secret", + comment: "some comment" + } + }, + { + path: "/nested1/nested2/folder", + secret: { + key: "NESTED-RAW-SEC1", + value: "something-secret", + comment: "some comment" + } } - }); - expect(res.statusCode).toEqual(200); - const projectKeyEnc = JSON.parse(res.payload); - - const userInfoRes = await testServer.inject({ - method: "GET", - url: "/api/v2/users/me", - headers: { - authorization: `Bearer ${jwtAuthToken}` + ]; + + beforeAll(async () => { + const res = await testServer.inject({ + method: "GET", + url: `/api/v2/workspace/${seedData1.project.id}/encrypted-key`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + } + }); + expect(res.statusCode).toEqual(200); + const projectKeyEnc = JSON.parse(res.payload); + + const userInfoRes = await testServer.inject({ + method: "GET", + url: "/api/v2/users/me", + headers: { + authorization: `Bearer ${jwtAuthToken}` + } + }); + expect(userInfoRes.statusCode).toEqual(200); + const { user: userInfo } = JSON.parse(userInfoRes.payload); + + const privateKey = await getUserPrivateKey(seedData1.password, userInfo); + const projectKey = decryptAsymmetric({ + ciphertext: projectKeyEnc.encryptedKey, + nonce: projectKeyEnc.nonce, + publicKey: projectKeyEnc.sender.publicKey, + privateKey + }); + + const projectBotRes = await testServer.inject({ + method: "GET", + url: `/api/v1/bot/${seedData1.project.id}`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + } + }); + expect(projectBotRes.statusCode).toEqual(200); + const projectBot = JSON.parse(projectBotRes.payload).bot; + const botKey = encryptAsymmetric(projectKey, projectBot.publicKey, privateKey); + + // set bot as active + const setBotActive = await testServer.inject({ + method: "PATCH", + url: `/api/v1/bot/${projectBot.id}/active`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: { + isActive: true, + workspaceId: seedData1.project.id, + botKey: { + encryptedKey: botKey.ciphertext, + nonce: botKey.nonce + } + } + }); + expect(setBotActive.statusCode).toEqual(200); + + // create a deep folder + const folderCreate = await testServer.inject({ + method: "POST", + url: `/api/v1/folders`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + name: "folder", + path: "/nested1/nested2" + } + }); + expect(folderCreate.statusCode).toBe(200); + folderId = folderCreate.json().folder.id; + + if (auth === AuthMode.JWT) { + authToken = jwtAuthToken; + } else if (auth === AuthMode.IDENTITY_ACCESS_TOKEN) { + const identityLogin = await testServer.inject({ + method: "POST", + url: "/api/v1/auth/universal-auth/login", + body: { + clientSecret: seedData1.machineIdentity.clientCredentials.secret, + clientId: seedData1.machineIdentity.clientCredentials.id + } + }); + expect(identityLogin.statusCode).toBe(200); + authToken = identityLogin.json().accessToken; } }); - expect(userInfoRes.statusCode).toEqual(200); - const { user: userInfo } = JSON.parse(userInfoRes.payload); - - const privateKey = await getUserPrivateKey(seedData1.password, userInfo); - const projectKey = decryptAsymmetric({ - ciphertext: projectKeyEnc.encryptedKey, - nonce: projectKeyEnc.nonce, - publicKey: projectKeyEnc.sender.publicKey, - privateKey - }); - const projectBotRes = await testServer.inject({ - method: "GET", - url: `/api/v1/bot/${seedData1.project.id}`, - headers: { - authorization: `Bearer ${jwtAuthToken}` - } + afterAll(async () => { + const projectBotRes = await testServer.inject({ + method: "GET", + url: `/api/v1/bot/${seedData1.project.id}`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + } + }); + expect(projectBotRes.statusCode).toEqual(200); + const projectBot = JSON.parse(projectBotRes.payload).bot; + + // set bot as inactive + const setBotInActive = await testServer.inject({ + method: "PATCH", + url: `/api/v1/bot/${projectBot.id}/active`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: { + isActive: false, + workspaceId: seedData1.project.id + } + }); + expect(setBotInActive.statusCode).toEqual(200); + const deleteFolder = await testServer.inject({ + method: "DELETE", + url: `/api/v1/folders/${folderId}`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + path: "/nested1/nested2" + } + }); + expect(deleteFolder.statusCode).toBe(200); }); - expect(projectBotRes.statusCode).toEqual(200); - const projectBot = JSON.parse(projectBotRes.payload).bot; - const botKey = encryptAsymmetric(projectKey, projectBot.publicKey, privateKey); - // set bot as active - const setBotActive = await testServer.inject({ - method: "PATCH", - url: `/api/v1/bot/${projectBot.id}/active`, - headers: { - authorization: `Bearer ${jwtAuthToken}` - }, - body: { - isActive: true, - workspaceId: seedData1.project.id, - botKey: { - encryptedKey: botKey.ciphertext, - nonce: botKey.nonce + const getSecrets = async (environment: string, secretPath = "/") => { + const res = await testServer.inject({ + method: "GET", + url: `/api/v3/secrets/raw`, + headers: { + authorization: `Bearer ${authToken}` + }, + query: { + secretPath, + environment, + workspaceId: seedData1.project.id } - } - }); - expect(setBotActive.statusCode).toEqual(200); + }); + const secrets: { secretKey: string; secretValue: string; type: SecretType; version: number }[] = + JSON.parse(res.payload).secrets || []; + return secrets.map((el) => ({ key: el.secretKey, value: el.secretValue, type: el.type, version: el.version })); + }; - // create a deep folder - const folderCreate = await testServer.inject({ - method: "POST", - url: `/api/v1/folders`, - headers: { - authorization: `Bearer ${jwtAuthToken}` - }, - body: { + test.each(testRawSecrets)("Create secret raw in path $path", async ({ secret, path }) => { + const createSecretReqBody = { workspaceId: seedData1.project.id, environment: seedData1.environment.slug, - name: "folder", - path: "/nested1/nested2" - } - }); - expect(folderCreate.statusCode).toBe(200); - folderId = folderCreate.json().folder.id; - }); + type: SecretType.Shared, + secretValue: secret.value, + secretComment: secret.comment, + secretPath: path + }; + const createSecRes = await testServer.inject({ + method: "POST", + url: `/api/v3/secrets/raw/${secret.key}`, + headers: { + authorization: `Bearer ${authToken}` + }, + body: createSecretReqBody + }); + expect(createSecRes.statusCode).toBe(200); + const createdSecretPayload = JSON.parse(createSecRes.payload); + expect(createdSecretPayload).toHaveProperty("secret"); + + // fetch secrets + const secrets = await getSecrets(seedData1.environment.slug, path); + expect(secrets).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + key: secret.key, + value: secret.value, + type: SecretType.Shared + }) + ]) + ); - afterAll(async () => { - const projectBotRes = await testServer.inject({ - method: "GET", - url: `/api/v1/bot/${seedData1.project.id}`, - headers: { - authorization: `Bearer ${jwtAuthToken}` - } + await deleteRawSecret({ path, key: secret.key }); }); - expect(projectBotRes.statusCode).toEqual(200); - const projectBot = JSON.parse(projectBotRes.payload).bot; - // set bot as inactive - const setBotInActive = await testServer.inject({ - method: "PATCH", - url: `/api/v1/bot/${projectBot.id}/active`, - headers: { - authorization: `Bearer ${jwtAuthToken}` - }, - body: { - isActive: false, - workspaceId: seedData1.project.id - } - }); - expect(setBotInActive.statusCode).toEqual(200); - const deleteFolder = await testServer.inject({ - method: "DELETE", - url: `/api/v1/folders/${folderId}`, - headers: { - authorization: `Bearer ${jwtAuthToken}` - }, - body: { - workspaceId: seedData1.project.id, - environment: seedData1.environment.slug, - path: "/nested1/nested2" - } - }); - expect(deleteFolder.statusCode).toBe(200); - }); + test.each(testRawSecrets)("Get secret by name raw in path $path", async ({ secret, path }) => { + await createRawSecret({ path, ...secret }); + + const getSecByNameRes = await testServer.inject({ + method: "GET", + url: `/api/v3/secrets/raw/${secret.key}`, + headers: { + authorization: `Bearer ${authToken}` + }, + query: { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + secretPath: path + } + }); + expect(getSecByNameRes.statusCode).toBe(200); + const secretPayload = JSON.parse(getSecByNameRes.payload); + expect(secretPayload).toHaveProperty("secret"); + expect(secretPayload.secret).toEqual( + expect.objectContaining({ + secretKey: secret.key, + secretValue: secret.value + }) + ); - const getSecrets = async (environment: string, secretPath = "/") => { - const res = await testServer.inject({ - method: "GET", - url: `/api/v3/secrets/raw`, - headers: { - authorization: `Bearer ${jwtAuthToken}` - }, - query: { - secretPath, - environment, - workspaceId: seedData1.project.id - } + await deleteRawSecret({ path, key: secret.key }); }); - const secrets: { secretKey: string; secretValue: string; type: SecretType; version: number }[] = - JSON.parse(res.payload).secrets || []; - return secrets.map((el) => ({ key: el.secretKey, value: el.secretValue, type: el.type, version: el.version })); - }; - test.each(testRawSecrets)("Create secret raw in path $path", async ({ secret, path }) => { - const createSecretReqBody = { - workspaceId: seedData1.project.id, - environment: seedData1.environment.slug, - type: SecretType.Shared, - secretValue: secret.value, - secretComment: secret.comment, - secretPath: path - }; - const createSecRes = await testServer.inject({ - method: "POST", - url: `/api/v3/secrets/raw/${secret.key}`, - headers: { - authorization: `Bearer ${jwtAuthToken}` - }, - body: createSecretReqBody - }); - expect(createSecRes.statusCode).toBe(200); - const createdSecretPayload = JSON.parse(createSecRes.payload); - expect(createdSecretPayload).toHaveProperty("secret"); + test.each(testRawSecrets)("List secret raw in path $path", async ({ secret, path }) => { + await Promise.all( + Array.from(Array(5)).map((_e, i) => createRawSecret({ path, ...secret, key: `BULK-${secret.key}-${i + 1}` })) + ); - // fetch secrets - const secrets = await getSecrets(seedData1.environment.slug, path); - expect(secrets).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - key: secret.key, - value: secret.value, - type: SecretType.Shared - }) - ]) - ); + const secrets = await getSecrets(seedData1.environment.slug, path); + expect(secrets.length).toEqual(5); + expect(secrets).toEqual( + expect.arrayContaining( + Array.from(Array(5)).map((_e, i) => + expect.objectContaining({ value: expect.any(String), key: `BULK-${secret.key}-${i + 1}` }) + ) + ) + ); - await deleteRawSecret({ path, key: secret.key }); - }); + await Promise.all( + Array.from(Array(5)).map((_e, i) => deleteRawSecret({ path, key: `BULK-${secret.key}-${i + 1}` })) + ); + }); - test.each(testRawSecrets)("Get secret by name raw in path $path", async ({ secret, path }) => { - await createRawSecret({ path, ...secret }); + test.each(testRawSecrets)("Update secret raw in path $path", async ({ secret, path }) => { + await createRawSecret({ path, ...secret }); - const getSecByNameRes = await testServer.inject({ - method: "GET", - url: `/api/v3/secrets/raw/${secret.key}`, - headers: { - authorization: `Bearer ${jwtAuthToken}` - }, - query: { + const updateSecretReqBody = { workspaceId: seedData1.project.id, environment: seedData1.environment.slug, + type: SecretType.Shared, + secretValue: "new-value", secretPath: path - } - }); - expect(getSecByNameRes.statusCode).toBe(200); - const secretPayload = JSON.parse(getSecByNameRes.payload); - expect(secretPayload).toHaveProperty("secret"); - expect(secretPayload.secret).toEqual( - expect.objectContaining({ - secretKey: secret.key, - secretValue: secret.value - }) - ); - - await deleteRawSecret({ path, key: secret.key }); - }); - - test.each(testRawSecrets)("List secret raw in path $path", async ({ secret, path }) => { - await Promise.all( - Array.from(Array(5)).map((_e, i) => createRawSecret({ path, ...secret, key: `BULK-${secret.key}-${i + 1}` })) - ); - - const secrets = await getSecrets(seedData1.environment.slug, path); - expect(secrets.length).toEqual(5); - expect(secrets).toEqual( - expect.arrayContaining( - Array.from(Array(5)).map((_e, i) => - expect.objectContaining({ value: expect.any(String), key: `BULK-${secret.key}-${i + 1}` }) - ) - ) - ); - - await Promise.all( - Array.from(Array(5)).map((_e, i) => deleteRawSecret({ path, key: `BULK-${secret.key}-${i + 1}` })) - ); - }); - - test.each(testRawSecrets)("Update secret raw in path $path", async ({ secret, path }) => { - await createRawSecret({ path, ...secret }); + }; + const updateSecRes = await testServer.inject({ + method: "PATCH", + url: `/api/v3/secrets/raw/${secret.key}`, + headers: { + authorization: `Bearer ${authToken}` + }, + body: updateSecretReqBody + }); + expect(updateSecRes.statusCode).toBe(200); + const updatedSecretPayload = JSON.parse(updateSecRes.payload); + expect(updatedSecretPayload).toHaveProperty("secret"); + + // fetch secrets + const secrets = await getSecrets(seedData1.environment.slug, path); + expect(secrets).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + key: secret.key, + value: "new-value", + version: 2, + type: SecretType.Shared + }) + ]) + ); - const updateSecretReqBody = { - workspaceId: seedData1.project.id, - environment: seedData1.environment.slug, - type: SecretType.Shared, - secretValue: "new-value", - secretPath: path - }; - const updateSecRes = await testServer.inject({ - method: "PATCH", - url: `/api/v3/secrets/raw/${secret.key}`, - headers: { - authorization: `Bearer ${jwtAuthToken}` - }, - body: updateSecretReqBody + await deleteRawSecret({ path, key: secret.key }); }); - expect(updateSecRes.statusCode).toBe(200); - const updatedSecretPayload = JSON.parse(updateSecRes.payload); - expect(updatedSecretPayload).toHaveProperty("secret"); - // fetch secrets - const secrets = await getSecrets(seedData1.environment.slug, path); - expect(secrets).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - key: secret.key, - value: "new-value", - version: 2, - type: SecretType.Shared - }) - ]) - ); + test.each(testRawSecrets)("Delete secret raw in path $path", async ({ path, secret }) => { + await createRawSecret({ path, ...secret }); - await deleteRawSecret({ path, key: secret.key }); - }); - - test.each(testRawSecrets)("Delete secret raw in path $path", async ({ path, secret }) => { - await createRawSecret({ path, ...secret }); - - const deletedSecretReqBody = { - workspaceId: seedData1.project.id, - environment: seedData1.environment.slug, - type: SecretType.Shared, - secretPath: path - }; - const deletedSecRes = await testServer.inject({ - method: "DELETE", - url: `/api/v3/secrets/raw/${secret.key}`, - headers: { - authorization: `Bearer ${jwtAuthToken}` - }, - body: deletedSecretReqBody + const deletedSecretReqBody = { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + type: SecretType.Shared, + secretPath: path + }; + const deletedSecRes = await testServer.inject({ + method: "DELETE", + url: `/api/v3/secrets/raw/${secret.key}`, + headers: { + authorization: `Bearer ${authToken}` + }, + body: deletedSecretReqBody + }); + expect(deletedSecRes.statusCode).toBe(200); + const deletedSecretPayload = JSON.parse(deletedSecRes.payload); + expect(deletedSecretPayload).toHaveProperty("secret"); + + // fetch secrets + const secrets = await getSecrets(seedData1.environment.slug, path); + expect(secrets).toEqual([]); }); - expect(deletedSecRes.statusCode).toBe(200); - const deletedSecretPayload = JSON.parse(deletedSecRes.payload); - expect(deletedSecretPayload).toHaveProperty("secret"); - - // fetch secrets - const secrets = await getSecrets(seedData1.environment.slug, path); - expect(secrets).toEqual([]); - }); -}); + } +); describe("Secret V3 Raw Router Without E2EE enabled", async () => { const secret = { diff --git a/backend/src/ee/services/license/__mocks__/licence-fns.ts b/backend/src/ee/services/license/__mocks__/licence-fns.ts new file mode 100644 index 0000000000..e404aaace3 --- /dev/null +++ b/backend/src/ee/services/license/__mocks__/licence-fns.ts @@ -0,0 +1,27 @@ +export const getDefaultOnPremFeatures = () => { + return { + _id: null, + slug: null, + tier: -1, + workspaceLimit: null, + workspacesUsed: 0, + memberLimit: null, + membersUsed: 0, + environmentLimit: null, + environmentsUsed: 0, + secretVersioning: true, + pitRecovery: false, + ipAllowlisting: true, + rbac: false, + customRateLimits: false, + customAlerts: false, + auditLogs: false, + auditLogsRetentionDays: 0, + samlSSO: false, + status: null, + trial_end: null, + has_used_trial: true, + secretApproval: false, + secretRotation: true + }; +}; From 8d06a6c96960c459490e89285b9c29d3a191a054 Mon Sep 17 00:00:00 2001 From: Akhil Mohan Date: Tue, 20 Feb 2024 21:08:34 +0530 Subject: [PATCH 23/29] feat: made identity crud with identity auth mode and api key for user me --- backend/src/server/routes/v1/identity-router.ts | 6 +++--- backend/src/server/routes/v2/user-router.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/src/server/routes/v1/identity-router.ts b/backend/src/server/routes/v1/identity-router.ts index 4ca4ef3249..7441d58cb4 100644 --- a/backend/src/server/routes/v1/identity-router.ts +++ b/backend/src/server/routes/v1/identity-router.ts @@ -9,7 +9,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => { server.route({ method: "POST", url: "/", - onRequest: verifyAuth([AuthMode.JWT]), + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { description: "Create identity", security: [ @@ -56,7 +56,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => { server.route({ method: "PATCH", url: "/:identityId", - onRequest: verifyAuth([AuthMode.JWT]), + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { description: "Update identity", security: [ @@ -105,7 +105,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => { server.route({ method: "DELETE", url: "/:identityId", - onRequest: verifyAuth([AuthMode.JWT]), + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { description: "Delete identity", security: [ diff --git a/backend/src/server/routes/v2/user-router.ts b/backend/src/server/routes/v2/user-router.ts index 8fc114e766..97bc3d864b 100644 --- a/backend/src/server/routes/v2/user-router.ts +++ b/backend/src/server/routes/v2/user-router.ts @@ -197,7 +197,7 @@ export const registerUserRouter = async (server: FastifyZodProvider) => { }) } }, - onRequest: verifyAuth([AuthMode.JWT]), + onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]), handler: async (req) => { const user = await server.services.user.getMe(req.permission.id); return { user }; From b4db06c76353e9d4ed83b8905d58f5c85e0bce19 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Tue, 20 Feb 2024 11:35:06 -0500 Subject: [PATCH 24/29] return empty string for decrypt fuction --- backend/src/db/seed-data.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/db/seed-data.ts b/backend/src/db/seed-data.ts index 8745d7f02e..63821e15e2 100644 --- a/backend/src/db/seed-data.ts +++ b/backend/src/db/seed-data.ts @@ -206,7 +206,7 @@ export const decryptSecret = (decryptKey: string, encSecret: TSecrets) => { tag: encSecret.secretCommentTag, iv: encSecret.secretCommentIV }) - : null; + : ""; return { key: secretKey, From ccaa9fd96eb4a6076a695bbf26ca2fffbda47f8e Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Tue, 20 Feb 2024 11:35:52 -0500 Subject: [PATCH 25/29] add more test cases --- backend/e2e-test/routes/v3/secrets.spec.ts | 146 ++++++++++++++++----- 1 file changed, 115 insertions(+), 31 deletions(-) diff --git a/backend/e2e-test/routes/v3/secrets.spec.ts b/backend/e2e-test/routes/v3/secrets.spec.ts index c74db4e6de..03e1c2f503 100644 --- a/backend/e2e-test/routes/v3/secrets.spec.ts +++ b/backend/e2e-test/routes/v3/secrets.spec.ts @@ -52,7 +52,7 @@ const deleteSecret = async (dto: { path: string; key: string }) => { }; describe("Secret V3 Router", async () => { - const testSecrets = [ + const secretTestCases = [ { path: "/", secret: { @@ -68,6 +68,87 @@ describe("Secret V3 Router", async () => { value: "something-secret", comment: "some comment" } + }, + { + path: "/", + secret: { + key: "secret-key-2", + value: `-----BEGIN PRIVATE KEY----- + MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCa6eeFk+cMVqFn + hoVQDYgn2Ptp5Azysr2UPq6P73pCL9BzUtOXKZROqDyGehzzfg3wE2KdYU1Jk5Uq + fP0ZOWDIlM2SaVCSI3FW32o5+ZiggjpqcVdLFc/PS0S/ZdSmpPd8h11iO2brtIAI + ugTW8fcKlGSNUwx9aFmE7A6JnTRliTxB1l6QaC+YAwTK39VgeVH2gDSWC407aS15 + QobAkaBKKmFkzB5D7i2ZJwt+uXJV/rbLmyDmtnw0lubciGn7NX9wbYef180fisqT + aPNAz0nPKk0fFH2Wd5MZixNGbrrpDA+FCYvI5doThZyT2hpj08qWP07oXXCAqw46 + IEupNSILAgMBAAECggEBAIJb5KzeaiZS3B3O8G4OBQ5rJB3WfyLYUHnoSWLsBbie + nc392/ovThLmtZAAQE6SO85Tsb93+t64Z2TKqv1H8G658UeMgfWIB78v4CcLJ2mi + TN/3opqXrzjkQOTDHzBgT7al/mpETHZ6fOdbCemK0fVALGFUioUZg4M8VXtuI4Jw + q28jAyoRKrCrzda4BeQ553NZ4G5RvwhX3O2I8B8upTbt5hLcisBKy8MPLYY5LUFj + YKAP+raf6QLliP6KYHuVxUlgzxjLTxVG41etcyqqZF+foyiKBO3PU3n8oh++tgQP + ExOxiR0JSkBG5b+oOBD0zxcvo3/SjBHn0dJOZCSU2SkCgYEAyCe676XnNyBZMRD7 + 6trsaoiCWBpA6M8H44+x3w4cQFtqV38RyLy60D+iMKjIaLqeBbnay61VMzo24Bz3 + EuF2n4+9k/MetLJ0NCw8HmN5k0WSMD2BFsJWG8glVbzaqzehP4tIclwDTYc1jQVt + IoV2/iL7HGT+x2daUwbU5kN5hK0CgYEAxiLB+fmjxJW7VY4SHDLqPdpIW0q/kv4K + d/yZBrCX799vjmFb9vLh7PkQUfJhMJ/ttJOd7EtT3xh4mfkBeLfHwVU0d/ahbmSH + UJu/E9ZGxAW3PP0kxHZtPrLKQwBnfq8AxBauIhR3rPSorQTIOKtwz1jMlHFSUpuL + 3KeK2YfDYJcCgYEAkQnJOlNcAuRb/WQzSHIvktssqK8NjiZHryy3Vc0hx7j2jES2 + HGI2dSVHYD9OSiXA0KFm3OTTsnViwm/60iGzFdjRJV6tR39xGUVcoyCuPnvRfUd0 + PYvBXgxgkYpyYlPDcwp5CvWGJy3tLi1acgOIwIuUr3S38sL//t4adGk8q1kCgYB8 + Jbs1Tl53BvrimKpwUNbE+sjrquJu0A7vL68SqgQJoQ7dP9PH4Ff/i+/V6PFM7mib + BQOm02wyFbs7fvKVGVJoqWK+6CIucX732x7W5yRgHtS5ukQXdbzt1Ek3wkEW98Cb + HTruz7RNAt/NyXlLSODeit1lBbx3Vk9EaxZtRsv88QKBgGn7JwXgez9NOyobsNIo + QVO80rpUeenSjuFi+R0VmbLKe/wgAQbYJ0xTAsQ0btqViMzB27D6mJyC+KUIwWNX + MN8a+m46v4kqvZkKL2c4gmDibyURNe/vCtCHFuanJS/1mo2tr4XDyEeiuK52eTd9 + omQDpP86RX/hIIQ+JyLSaWYa + -----END PRIVATE KEY-----`, + comment: + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation" + } + }, + { + path: "/nested1/nested2/folder", + secret: { + key: "secret-key-3", + value: `-----BEGIN PRIVATE KEY----- + MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCa6eeFk+cMVqFn + hoVQDYgn2Ptp5Azysr2UPq6P73pCL9BzUtOXKZROqDyGehzzfg3wE2KdYU1Jk5Uq + fP0ZOWDIlM2SaVCSI3FW32o5+ZiggjpqcVdLFc/PS0S/ZdSmpPd8h11iO2brtIAI + ugTW8fcKlGSNUwx9aFmE7A6JnTRliTxB1l6QaC+YAwTK39VgeVH2gDSWC407aS15 + QobAkaBKKmFkzB5D7i2ZJwt+uXJV/rbLmyDmtnw0lubciGn7NX9wbYef180fisqT + aPNAz0nPKk0fFH2Wd5MZixNGbrrpDA+FCYvI5doThZyT2hpj08qWP07oXXCAqw46 + IEupNSILAgMBAAECggEBAIJb5KzeaiZS3B3O8G4OBQ5rJB3WfyLYUHnoSWLsBbie + nc392/ovThLmtZAAQE6SO85Tsb93+t64Z2TKqv1H8G658UeMgfWIB78v4CcLJ2mi + TN/3opqXrzjkQOTDHzBgT7al/mpETHZ6fOdbCemK0fVALGFUioUZg4M8VXtuI4Jw + q28jAyoRKrCrzda4BeQ553NZ4G5RvwhX3O2I8B8upTbt5hLcisBKy8MPLYY5LUFj + YKAP+raf6QLliP6KYHuVxUlgzxjLTxVG41etcyqqZF+foyiKBO3PU3n8oh++tgQP + ExOxiR0JSkBG5b+oOBD0zxcvo3/SjBHn0dJOZCSU2SkCgYEAyCe676XnNyBZMRD7 + 6trsaoiCWBpA6M8H44+x3w4cQFtqV38RyLy60D+iMKjIaLqeBbnay61VMzo24Bz3 + EuF2n4+9k/MetLJ0NCw8HmN5k0WSMD2BFsJWG8glVbzaqzehP4tIclwDTYc1jQVt + IoV2/iL7HGT+x2daUwbU5kN5hK0CgYEAxiLB+fmjxJW7VY4SHDLqPdpIW0q/kv4K + d/yZBrCX799vjmFb9vLh7PkQUfJhMJ/ttJOd7EtT3xh4mfkBeLfHwVU0d/ahbmSH + UJu/E9ZGxAW3PP0kxHZtPrLKQwBnfq8AxBauIhR3rPSorQTIOKtwz1jMlHFSUpuL + 3KeK2YfDYJcCgYEAkQnJOlNcAuRb/WQzSHIvktssqK8NjiZHryy3Vc0hx7j2jES2 + HGI2dSVHYD9OSiXA0KFm3OTTsnViwm/60iGzFdjRJV6tR39xGUVcoyCuPnvRfUd0 + PYvBXgxgkYpyYlPDcwp5CvWGJy3tLi1acgOIwIuUr3S38sL//t4adGk8q1kCgYB8 + Jbs1Tl53BvrimKpwUNbE+sjrquJu0A7vL68SqgQJoQ7dP9PH4Ff/i+/V6PFM7mib + BQOm02wyFbs7fvKVGVJoqWK+6CIucX732x7W5yRgHtS5ukQXdbzt1Ek3wkEW98Cb + HTruz7RNAt/NyXlLSODeit1lBbx3Vk9EaxZtRsv88QKBgGn7JwXgez9NOyobsNIo + QVO80rpUeenSjuFi+R0VmbLKe/wgAQbYJ0xTAsQ0btqViMzB27D6mJyC+KUIwWNX + MN8a+m46v4kqvZkKL2c4gmDibyURNe/vCtCHFuanJS/1mo2tr4XDyEeiuK52eTd9 + omQDpP86RX/hIIQ+JyLSaWYa + -----END PRIVATE KEY-----`, + comment: + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation" + } + }, + { + path: "/nested1/nested2/folder", + secret: { + key: "secret-key-3", + value: + "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdC4gU2VkIGRvIGVpdXNtb2QgdGVtcG9yIGluY2lkaWR1bnQgdXQgbGFib3JlIGV0IGRvbG9yZSBtYWduYSBhbGlxdWEuIFV0IGVuaW0gYWQgbWluaW0gdmVuaWFtLCBxdWlzIG5vc3RydWQgZXhlcmNpdGF0aW9uCg==", + comment: "" + } } ]; @@ -150,7 +231,7 @@ describe("Secret V3 Router", async () => { return secrets.map((el) => ({ ...decryptSecret(projectKey, el), type: el.type })); }; - test.each(testSecrets)("Create secret in path $path", async ({ secret, path }) => { + test.each(secretTestCases)("Create secret in path $path", async ({ secret, path }) => { const createdSecret = await createSecret({ projectKey, path, ...secret }); const decryptedSecret = decryptSecret(projectKey, createdSecret); expect(decryptedSecret.key).toEqual(secret.key); @@ -171,7 +252,7 @@ describe("Secret V3 Router", async () => { await deleteSecret({ path, key: secret.key }); }); - test.each(testSecrets)("Get secret by name in path $path", async ({ secret, path }) => { + test.each(secretTestCases)("Get secret by name in path $path", async ({ secret, path }) => { await createSecret({ projectKey, path, ...secret }); const getSecByNameRes = await testServer.inject({ @@ -197,28 +278,31 @@ describe("Secret V3 Router", async () => { await deleteSecret({ path, key: secret.key }); }); - test.each(testSecrets)("Creating personal secret without shared throw error in path $path", async ({ secret }) => { - const createSecretReqBody = { - workspaceId: seedData1.project.id, - environment: seedData1.environment.slug, - type: SecretType.Personal, - ...encryptSecret(projectKey, "SEC2", secret.value, secret.comment) - }; - const createSecRes = await testServer.inject({ - method: "POST", - url: `/api/v3/secrets/SEC2`, - headers: { - authorization: `Bearer ${jwtAuthToken}` - }, - body: createSecretReqBody - }); - const payload = JSON.parse(createSecRes.payload); - expect(createSecRes.statusCode).toBe(400); - expect(payload.error).toEqual("BadRequest"); - expect(payload.message).toEqual("Failed to create personal secret override for no corresponding shared secret"); - }); + test.each(secretTestCases)( + "Creating personal secret without shared throw error in path $path", + async ({ secret }) => { + const createSecretReqBody = { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + type: SecretType.Personal, + ...encryptSecret(projectKey, "SEC2", secret.value, secret.comment) + }; + const createSecRes = await testServer.inject({ + method: "POST", + url: `/api/v3/secrets/SEC2`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: createSecretReqBody + }); + const payload = JSON.parse(createSecRes.payload); + expect(createSecRes.statusCode).toBe(400); + expect(payload.error).toEqual("BadRequest"); + expect(payload.message).toEqual("Failed to create personal secret override for no corresponding shared secret"); + } + ); - test.each(testSecrets)("Creating personal secret in path $path", async ({ secret, path }) => { + test.each(secretTestCases)("Creating personal secret in path $path", async ({ secret, path }) => { await createSecret({ projectKey, path, ...secret }); const createSecretReqBody = { @@ -258,7 +342,7 @@ describe("Secret V3 Router", async () => { await deleteSecret({ path, key: secret.key }); }); - test.each(testSecrets)("Update secret in path $path", async ({ path, secret }) => { + test.each(secretTestCases)("Update secret in path $path", async ({ path, secret }) => { await createSecret({ projectKey, path, ...secret }); const updateSecretReqBody = { workspaceId: seedData1.project.id, @@ -298,7 +382,7 @@ describe("Secret V3 Router", async () => { await deleteSecret({ path, key: secret.key }); }); - test.each(testSecrets)("Delete secret in path $path", async ({ secret, path }) => { + test.each(secretTestCases)("Delete secret in path $path", async ({ secret, path }) => { await createSecret({ projectKey, path, ...secret }); const deletedSecret = await deleteSecret({ path, key: secret.key }); const decryptedSecret = decryptSecret(projectKey, deletedSecret); @@ -320,7 +404,7 @@ describe("Secret V3 Router", async () => { ); }); - test.each(testSecrets)( + test.each(secretTestCases)( "Deleting personal one should not delete shared secret in path $path", async ({ secret, path }) => { await createSecret({ projectKey, path, ...secret }); // shared one @@ -344,7 +428,7 @@ describe("Secret V3 Router", async () => { } ); - test.each(testSecrets)("Bulk create secrets in path $path", async ({ secret, path }) => { + test.each(secretTestCases)("Bulk create secrets in path $path", async ({ secret, path }) => { const createSharedSecRes = await testServer.inject({ method: "POST", url: `/api/v3/secrets/batch`, @@ -381,7 +465,7 @@ describe("Secret V3 Router", async () => { await Promise.all(Array.from(Array(5)).map((_e, i) => deleteSecret({ path, key: `BULK-${secret.key}-${i + 1}` }))); }); - test.each(testSecrets)("Bulk create fail on existing secret in path $path", async ({ secret, path }) => { + test.each(secretTestCases)("Bulk create fail on existing secret in path $path", async ({ secret, path }) => { await createSecret({ projectKey, ...secret, key: `BULK-${secret.key}-1`, path }); const createSharedSecRes = await testServer.inject({ @@ -405,7 +489,7 @@ describe("Secret V3 Router", async () => { await deleteSecret({ path, key: `BULK-${secret.key}-1` }); }); - test.each(testSecrets)("Bulk update secrets in path $path", async ({ secret, path }) => { + test.each(secretTestCases)("Bulk update secrets in path $path", async ({ secret, path }) => { await Promise.all( Array.from(Array(5)).map((_e, i) => createSecret({ projectKey, ...secret, key: `BULK-${secret.key}-${i + 1}`, path }) @@ -448,7 +532,7 @@ describe("Secret V3 Router", async () => { await Promise.all(Array.from(Array(5)).map((_e, i) => deleteSecret({ path, key: `BULK-${secret.key}-${i + 1}` }))); }); - test.each(testSecrets)("Bulk delete secrets in path $path", async ({ secret, path }) => { + test.each(secretTestCases)("Bulk delete secrets in path $path", async ({ secret, path }) => { await Promise.all( Array.from(Array(5)).map((_e, i) => createSecret({ projectKey, ...secret, key: `BULK-${secret.key}-${i + 1}`, path }) From f0e73474b7aa81b0fd31d4cc917c7ee75797c114 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Tue, 20 Feb 2024 11:49:11 -0500 Subject: [PATCH 26/29] add check out before integ tests run --- .../release-standalone-docker-img-postgres-offical.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/release-standalone-docker-img-postgres-offical.yml b/.github/workflows/release-standalone-docker-img-postgres-offical.yml index 2e9044a9fa..10e53e3773 100644 --- a/.github/workflows/release-standalone-docker-img-postgres-offical.yml +++ b/.github/workflows/release-standalone-docker-img-postgres-offical.yml @@ -9,6 +9,8 @@ jobs: name: Run tests before deployment runs-on: ubuntu-latest steps: + - name: ☁️ Checkout source + uses: actions/checkout@v3 - name: Run backend api integration tests # https://docs.github.com/en/actions/using-workflows/reusing-workflows#overview uses: ./.github/workflows/run-backend-tests.yml From 326093274185d86a8439f975cf2b015fff03bf4b Mon Sep 17 00:00:00 2001 From: Akhil Mohan Date: Tue, 20 Feb 2024 22:39:14 +0530 Subject: [PATCH 27/29] fix: resovled be test failing as reusable job --- .../release-standalone-docker-img-postgres-offical.yml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release-standalone-docker-img-postgres-offical.yml b/.github/workflows/release-standalone-docker-img-postgres-offical.yml index 10e53e3773..f08e882aaa 100644 --- a/.github/workflows/release-standalone-docker-img-postgres-offical.yml +++ b/.github/workflows/release-standalone-docker-img-postgres-offical.yml @@ -7,13 +7,8 @@ on: jobs: infisical-tests: name: Run tests before deployment - runs-on: ubuntu-latest - steps: - - name: ☁️ Checkout source - uses: actions/checkout@v3 - - name: Run backend api integration tests - # https://docs.github.com/en/actions/using-workflows/reusing-workflows#overview - uses: ./.github/workflows/run-backend-tests.yml + # https://docs.github.com/en/actions/using-workflows/reusing-workflows#overview + uses: ./.github/workflows/run-backend-tests.yml infisical-standalone: name: Build infisical standalone image postgres runs-on: ubuntu-latest From e20c623e91db506d586d2f06ead6f3eac5634dba Mon Sep 17 00:00:00 2001 From: snyk-bot Date: Tue, 20 Feb 2024 17:51:48 +0000 Subject: [PATCH 28/29] fix: upgrade @aws-sdk/client-secrets-manager from 3.485.0 to 3.502.0 Snyk has created this PR to upgrade @aws-sdk/client-secrets-manager from 3.485.0 to 3.502.0. See this package in npm: https://www.npmjs.com/package/@aws-sdk/client-secrets-manager See this project in Snyk: https://app.snyk.io/org/maidul98/project/35057e82-ed7d-4e19-ba4d-719a42135cd6?utm_source=github&utm_medium=referral&page=upgrade-pr --- backend/package-lock.json | 613 +++++++++++++++++++------------------- backend/package.json | 2 +- 2 files changed, 313 insertions(+), 302 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index 6827ad05db..fc65537451 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@aws-sdk/client-secrets-manager": "^3.485.0", + "@aws-sdk/client-secrets-manager": "^3.502.0", "@casl/ability": "^6.5.0", "@fastify/cookie": "^9.2.0", "@fastify/cors": "^8.4.1", @@ -356,22 +356,6 @@ "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/core": { - "version": "3.496.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.496.0.tgz", - "integrity": "sha512-yT+ug7Cw/3eJi7x2es0+46x12+cIJm5Xv+GPWsrTFD1TKgqO/VPEgfDtHFagDNbFmjNQA65Ygc/kEdIX9ICX/A==", - "dependencies": { - "@smithy/core": "^1.3.1", - "@smithy/protocol-http": "^3.1.1", - "@smithy/signature-v4": "^2.1.1", - "@smithy/smithy-client": "^2.3.1", - "@smithy/types": "^2.9.1", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/credential-provider-env": { "version": "3.496.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.496.0.tgz", @@ -677,49 +661,49 @@ } }, "node_modules/@aws-sdk/client-secrets-manager": { - "version": "3.485.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.485.0.tgz", - "integrity": "sha512-TruRGEdTy1y/5ln1NcU5LvIZyK38O89zU9vCfNQIKwTSrpS0sDJQukjg8VfMC8gbqUUvXdiPcS61Fxr1WfWn7g==", + "version": "3.502.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.502.0.tgz", + "integrity": "sha512-ICU084A/EbYMqca6NVFqeMtHh+KCdn0H7UjARUy5ur1yOlXXvxqAJGtKZDYFjuEO08F30zbv7+4HCOy6yjOJ0Q==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.485.0", - "@aws-sdk/core": "3.485.0", - "@aws-sdk/credential-provider-node": "3.485.0", - "@aws-sdk/middleware-host-header": "3.485.0", - "@aws-sdk/middleware-logger": "3.485.0", - "@aws-sdk/middleware-recursion-detection": "3.485.0", - "@aws-sdk/middleware-signing": "3.485.0", - "@aws-sdk/middleware-user-agent": "3.485.0", - "@aws-sdk/region-config-resolver": "3.485.0", - "@aws-sdk/types": "3.485.0", - "@aws-sdk/util-endpoints": "3.485.0", - "@aws-sdk/util-user-agent-browser": "3.485.0", - "@aws-sdk/util-user-agent-node": "3.485.0", - "@smithy/config-resolver": "^2.0.23", - "@smithy/core": "^1.2.2", - "@smithy/fetch-http-handler": "^2.3.2", - "@smithy/hash-node": "^2.0.18", - "@smithy/invalid-dependency": "^2.0.16", - "@smithy/middleware-content-length": "^2.0.18", - "@smithy/middleware-endpoint": "^2.3.0", - "@smithy/middleware-retry": "^2.0.26", - "@smithy/middleware-serde": "^2.0.16", - "@smithy/middleware-stack": "^2.0.10", - "@smithy/node-config-provider": "^2.1.9", - "@smithy/node-http-handler": "^2.2.2", - "@smithy/protocol-http": "^3.0.12", - "@smithy/smithy-client": "^2.2.1", - "@smithy/types": "^2.8.0", - "@smithy/url-parser": "^2.0.16", - "@smithy/util-base64": "^2.0.1", - "@smithy/util-body-length-browser": "^2.0.1", - "@smithy/util-body-length-node": "^2.1.0", - "@smithy/util-defaults-mode-browser": "^2.0.24", - "@smithy/util-defaults-mode-node": "^2.0.32", - "@smithy/util-endpoints": "^1.0.8", - "@smithy/util-retry": "^2.0.9", - "@smithy/util-utf8": "^2.0.2", + "@aws-sdk/client-sts": "3.502.0", + "@aws-sdk/core": "3.496.0", + "@aws-sdk/credential-provider-node": "3.502.0", + "@aws-sdk/middleware-host-header": "3.502.0", + "@aws-sdk/middleware-logger": "3.502.0", + "@aws-sdk/middleware-recursion-detection": "3.502.0", + "@aws-sdk/middleware-signing": "3.502.0", + "@aws-sdk/middleware-user-agent": "3.502.0", + "@aws-sdk/region-config-resolver": "3.502.0", + "@aws-sdk/types": "3.502.0", + "@aws-sdk/util-endpoints": "3.502.0", + "@aws-sdk/util-user-agent-browser": "3.502.0", + "@aws-sdk/util-user-agent-node": "3.502.0", + "@smithy/config-resolver": "^2.1.1", + "@smithy/core": "^1.3.1", + "@smithy/fetch-http-handler": "^2.4.1", + "@smithy/hash-node": "^2.1.1", + "@smithy/invalid-dependency": "^2.1.1", + "@smithy/middleware-content-length": "^2.1.1", + "@smithy/middleware-endpoint": "^2.4.1", + "@smithy/middleware-retry": "^2.1.1", + "@smithy/middleware-serde": "^2.1.1", + "@smithy/middleware-stack": "^2.1.1", + "@smithy/node-config-provider": "^2.2.1", + "@smithy/node-http-handler": "^2.3.1", + "@smithy/protocol-http": "^3.1.1", + "@smithy/smithy-client": "^2.3.1", + "@smithy/types": "^2.9.1", + "@smithy/url-parser": "^2.1.1", + "@smithy/util-base64": "^2.1.1", + "@smithy/util-body-length-browser": "^2.1.1", + "@smithy/util-body-length-node": "^2.2.1", + "@smithy/util-defaults-mode-browser": "^2.1.1", + "@smithy/util-defaults-mode-node": "^2.1.1", + "@smithy/util-endpoints": "^1.1.1", + "@smithy/util-retry": "^2.1.1", + "@smithy/util-utf8": "^2.1.1", "tslib": "^2.5.0", "uuid": "^8.3.2" }, @@ -736,112 +720,166 @@ } }, "node_modules/@aws-sdk/client-sso": { - "version": "3.485.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.485.0.tgz", - "integrity": "sha512-apN2bEn0PZs0jD4jAfvwO3dlWqw9YIQJ6TAudM1bd3S5vzWqlBBcLfQpK6taHoQaI+WqgUWXLuOf7gRFbGXKPg==", + "version": "3.502.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.502.0.tgz", + "integrity": "sha512-OZAYal1+PQgUUtWiHhRayDtX0OD+XpXHKAhjYgEIPbyhQaCMp3/Bq1xDX151piWXvXqXLJHFKb8DUEqzwGO9QA==", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/core": "3.496.0", + "@aws-sdk/middleware-host-header": "3.502.0", + "@aws-sdk/middleware-logger": "3.502.0", + "@aws-sdk/middleware-recursion-detection": "3.502.0", + "@aws-sdk/middleware-user-agent": "3.502.0", + "@aws-sdk/region-config-resolver": "3.502.0", + "@aws-sdk/types": "3.502.0", + "@aws-sdk/util-endpoints": "3.502.0", + "@aws-sdk/util-user-agent-browser": "3.502.0", + "@aws-sdk/util-user-agent-node": "3.502.0", + "@smithy/config-resolver": "^2.1.1", + "@smithy/core": "^1.3.1", + "@smithy/fetch-http-handler": "^2.4.1", + "@smithy/hash-node": "^2.1.1", + "@smithy/invalid-dependency": "^2.1.1", + "@smithy/middleware-content-length": "^2.1.1", + "@smithy/middleware-endpoint": "^2.4.1", + "@smithy/middleware-retry": "^2.1.1", + "@smithy/middleware-serde": "^2.1.1", + "@smithy/middleware-stack": "^2.1.1", + "@smithy/node-config-provider": "^2.2.1", + "@smithy/node-http-handler": "^2.3.1", + "@smithy/protocol-http": "^3.1.1", + "@smithy/smithy-client": "^2.3.1", + "@smithy/types": "^2.9.1", + "@smithy/url-parser": "^2.1.1", + "@smithy/util-base64": "^2.1.1", + "@smithy/util-body-length-browser": "^2.1.1", + "@smithy/util-body-length-node": "^2.2.1", + "@smithy/util-defaults-mode-browser": "^2.1.1", + "@smithy/util-defaults-mode-node": "^2.1.1", + "@smithy/util-endpoints": "^1.1.1", + "@smithy/util-retry": "^2.1.1", + "@smithy/util-utf8": "^2.1.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc": { + "version": "3.502.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.502.0.tgz", + "integrity": "sha512-Yc9tZqTOMWtdgpkrdjKShgWb9oKNsFQrItfoiN1xWDllaFFRPi2KTiZiR0AbSTrNasJy13d210DOxrIdte+kWQ==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/core": "3.485.0", - "@aws-sdk/middleware-host-header": "3.485.0", - "@aws-sdk/middleware-logger": "3.485.0", - "@aws-sdk/middleware-recursion-detection": "3.485.0", - "@aws-sdk/middleware-user-agent": "3.485.0", - "@aws-sdk/region-config-resolver": "3.485.0", - "@aws-sdk/types": "3.485.0", - "@aws-sdk/util-endpoints": "3.485.0", - "@aws-sdk/util-user-agent-browser": "3.485.0", - "@aws-sdk/util-user-agent-node": "3.485.0", - "@smithy/config-resolver": "^2.0.23", - "@smithy/core": "^1.2.2", - "@smithy/fetch-http-handler": "^2.3.2", - "@smithy/hash-node": "^2.0.18", - "@smithy/invalid-dependency": "^2.0.16", - "@smithy/middleware-content-length": "^2.0.18", - "@smithy/middleware-endpoint": "^2.3.0", - "@smithy/middleware-retry": "^2.0.26", - "@smithy/middleware-serde": "^2.0.16", - "@smithy/middleware-stack": "^2.0.10", - "@smithy/node-config-provider": "^2.1.9", - "@smithy/node-http-handler": "^2.2.2", - "@smithy/protocol-http": "^3.0.12", - "@smithy/smithy-client": "^2.2.1", - "@smithy/types": "^2.8.0", - "@smithy/url-parser": "^2.0.16", - "@smithy/util-base64": "^2.0.1", - "@smithy/util-body-length-browser": "^2.0.1", - "@smithy/util-body-length-node": "^2.1.0", - "@smithy/util-defaults-mode-browser": "^2.0.24", - "@smithy/util-defaults-mode-node": "^2.0.32", - "@smithy/util-endpoints": "^1.0.8", - "@smithy/util-retry": "^2.0.9", - "@smithy/util-utf8": "^2.0.2", + "@aws-sdk/client-sts": "3.502.0", + "@aws-sdk/core": "3.496.0", + "@aws-sdk/middleware-host-header": "3.502.0", + "@aws-sdk/middleware-logger": "3.502.0", + "@aws-sdk/middleware-recursion-detection": "3.502.0", + "@aws-sdk/middleware-signing": "3.502.0", + "@aws-sdk/middleware-user-agent": "3.502.0", + "@aws-sdk/region-config-resolver": "3.502.0", + "@aws-sdk/types": "3.502.0", + "@aws-sdk/util-endpoints": "3.502.0", + "@aws-sdk/util-user-agent-browser": "3.502.0", + "@aws-sdk/util-user-agent-node": "3.502.0", + "@smithy/config-resolver": "^2.1.1", + "@smithy/core": "^1.3.1", + "@smithy/fetch-http-handler": "^2.4.1", + "@smithy/hash-node": "^2.1.1", + "@smithy/invalid-dependency": "^2.1.1", + "@smithy/middleware-content-length": "^2.1.1", + "@smithy/middleware-endpoint": "^2.4.1", + "@smithy/middleware-retry": "^2.1.1", + "@smithy/middleware-serde": "^2.1.1", + "@smithy/middleware-stack": "^2.1.1", + "@smithy/node-config-provider": "^2.2.1", + "@smithy/node-http-handler": "^2.3.1", + "@smithy/protocol-http": "^3.1.1", + "@smithy/smithy-client": "^2.3.1", + "@smithy/types": "^2.9.1", + "@smithy/url-parser": "^2.1.1", + "@smithy/util-base64": "^2.1.1", + "@smithy/util-body-length-browser": "^2.1.1", + "@smithy/util-body-length-node": "^2.2.1", + "@smithy/util-defaults-mode-browser": "^2.1.1", + "@smithy/util-defaults-mode-node": "^2.1.1", + "@smithy/util-endpoints": "^1.1.1", + "@smithy/util-retry": "^2.1.1", + "@smithy/util-utf8": "^2.1.1", "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" + }, + "peerDependencies": { + "@aws-sdk/credential-provider-node": "*" } }, "node_modules/@aws-sdk/client-sts": { - "version": "3.485.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.485.0.tgz", - "integrity": "sha512-PI4q36kVF0fpIPZyeQhrwwJZ6SRkOGvU3rX5Qn4b5UY5X+Ct1aLhqSX8/OB372UZIcnh6eSvERu8POHleDO7Jw==", + "version": "3.502.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.502.0.tgz", + "integrity": "sha512-0q08gsvn6nuRqjK+i/e30PT/t7vvYwmGJS0PhJikZWv5yRDNSUxSYG0uDwKSbLDzmc2UX5+mLeyjPHlL4hbGlA==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/core": "3.485.0", - "@aws-sdk/credential-provider-node": "3.485.0", - "@aws-sdk/middleware-host-header": "3.485.0", - "@aws-sdk/middleware-logger": "3.485.0", - "@aws-sdk/middleware-recursion-detection": "3.485.0", - "@aws-sdk/middleware-user-agent": "3.485.0", - "@aws-sdk/region-config-resolver": "3.485.0", - "@aws-sdk/types": "3.485.0", - "@aws-sdk/util-endpoints": "3.485.0", - "@aws-sdk/util-user-agent-browser": "3.485.0", - "@aws-sdk/util-user-agent-node": "3.485.0", - "@smithy/config-resolver": "^2.0.23", - "@smithy/core": "^1.2.2", - "@smithy/fetch-http-handler": "^2.3.2", - "@smithy/hash-node": "^2.0.18", - "@smithy/invalid-dependency": "^2.0.16", - "@smithy/middleware-content-length": "^2.0.18", - "@smithy/middleware-endpoint": "^2.3.0", - "@smithy/middleware-retry": "^2.0.26", - "@smithy/middleware-serde": "^2.0.16", - "@smithy/middleware-stack": "^2.0.10", - "@smithy/node-config-provider": "^2.1.9", - "@smithy/node-http-handler": "^2.2.2", - "@smithy/protocol-http": "^3.0.12", - "@smithy/smithy-client": "^2.2.1", - "@smithy/types": "^2.8.0", - "@smithy/url-parser": "^2.0.16", - "@smithy/util-base64": "^2.0.1", - "@smithy/util-body-length-browser": "^2.0.1", - "@smithy/util-body-length-node": "^2.1.0", - "@smithy/util-defaults-mode-browser": "^2.0.24", - "@smithy/util-defaults-mode-node": "^2.0.32", - "@smithy/util-endpoints": "^1.0.8", - "@smithy/util-middleware": "^2.0.9", - "@smithy/util-retry": "^2.0.9", - "@smithy/util-utf8": "^2.0.2", + "@aws-sdk/core": "3.496.0", + "@aws-sdk/middleware-host-header": "3.502.0", + "@aws-sdk/middleware-logger": "3.502.0", + "@aws-sdk/middleware-recursion-detection": "3.502.0", + "@aws-sdk/middleware-user-agent": "3.502.0", + "@aws-sdk/region-config-resolver": "3.502.0", + "@aws-sdk/types": "3.502.0", + "@aws-sdk/util-endpoints": "3.502.0", + "@aws-sdk/util-user-agent-browser": "3.502.0", + "@aws-sdk/util-user-agent-node": "3.502.0", + "@smithy/config-resolver": "^2.1.1", + "@smithy/core": "^1.3.1", + "@smithy/fetch-http-handler": "^2.4.1", + "@smithy/hash-node": "^2.1.1", + "@smithy/invalid-dependency": "^2.1.1", + "@smithy/middleware-content-length": "^2.1.1", + "@smithy/middleware-endpoint": "^2.4.1", + "@smithy/middleware-retry": "^2.1.1", + "@smithy/middleware-serde": "^2.1.1", + "@smithy/middleware-stack": "^2.1.1", + "@smithy/node-config-provider": "^2.2.1", + "@smithy/node-http-handler": "^2.3.1", + "@smithy/protocol-http": "^3.1.1", + "@smithy/smithy-client": "^2.3.1", + "@smithy/types": "^2.9.1", + "@smithy/url-parser": "^2.1.1", + "@smithy/util-base64": "^2.1.1", + "@smithy/util-body-length-browser": "^2.1.1", + "@smithy/util-body-length-node": "^2.2.1", + "@smithy/util-defaults-mode-browser": "^2.1.1", + "@smithy/util-defaults-mode-node": "^2.1.1", + "@smithy/util-endpoints": "^1.1.1", + "@smithy/util-middleware": "^2.1.1", + "@smithy/util-retry": "^2.1.1", + "@smithy/util-utf8": "^2.1.1", "fast-xml-parser": "4.2.5", "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" + }, + "peerDependencies": { + "@aws-sdk/credential-provider-node": "*" } }, "node_modules/@aws-sdk/core": { - "version": "3.485.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.485.0.tgz", - "integrity": "sha512-Yvi80DQcbjkYCft471ClE3HuetuNVqntCs6eFOomDcrJaqdOFrXv2kJAxky84MRA/xb7bGlDGAPbTuj1ICputg==", - "dependencies": { - "@smithy/core": "^1.2.2", - "@smithy/protocol-http": "^3.0.12", - "@smithy/signature-v4": "^2.0.0", - "@smithy/smithy-client": "^2.2.1", - "@smithy/types": "^2.8.0", + "version": "3.496.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.496.0.tgz", + "integrity": "sha512-yT+ug7Cw/3eJi7x2es0+46x12+cIJm5Xv+GPWsrTFD1TKgqO/VPEgfDtHFagDNbFmjNQA65Ygc/kEdIX9ICX/A==", + "dependencies": { + "@smithy/core": "^1.3.1", + "@smithy/protocol-http": "^3.1.1", + "@smithy/signature-v4": "^2.1.1", + "@smithy/smithy-client": "^2.3.1", + "@smithy/types": "^2.9.1", "tslib": "^2.5.0" }, "engines": { @@ -849,13 +887,13 @@ } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.485.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.485.0.tgz", - "integrity": "sha512-3XkFgwVU1XOB33dV7t9BKJ/ptdl2iS+0dxE7ecq8aqT2/gsfKmLCae1G17P8WmdD3z0kMDTvnqM2aWgUnSOkmg==", + "version": "3.502.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.502.0.tgz", + "integrity": "sha512-KIB8Ae1Z7domMU/jU4KiIgK4tmYgvuXlhR54ehwlVHxnEoFPoPuGHFZU7oFn79jhhSLUFQ1lRYMxP0cEwb7XeQ==", "dependencies": { - "@aws-sdk/types": "3.485.0", - "@smithy/property-provider": "^2.0.0", - "@smithy/types": "^2.8.0", + "@aws-sdk/types": "3.502.0", + "@smithy/property-provider": "^2.1.1", + "@smithy/types": "^2.9.1", "tslib": "^2.5.0" }, "engines": { @@ -863,19 +901,20 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.485.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.485.0.tgz", - "integrity": "sha512-cFYF/Bdw7EnT4viSxYpNIv3IBkri/Yb+JpQXl8uDq7bfVJfAN5qZmK07vRkg08xL6TC4F41wshhMSAucGdTwIw==", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.485.0", - "@aws-sdk/credential-provider-process": "3.485.0", - "@aws-sdk/credential-provider-sso": "3.485.0", - "@aws-sdk/credential-provider-web-identity": "3.485.0", - "@aws-sdk/types": "3.485.0", - "@smithy/credential-provider-imds": "^2.0.0", - "@smithy/property-provider": "^2.0.0", - "@smithy/shared-ini-file-loader": "^2.0.6", - "@smithy/types": "^2.8.0", + "version": "3.502.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.502.0.tgz", + "integrity": "sha512-1wB/escbspUY6uRDEMp9AMMyypUSyuQ0AMO1yQNtXviV8cPf+CuRbqP/UVnimHO1RuX0n5BmjDVVjUIEU6kuGA==", + "dependencies": { + "@aws-sdk/client-sts": "3.502.0", + "@aws-sdk/credential-provider-env": "3.502.0", + "@aws-sdk/credential-provider-process": "3.502.0", + "@aws-sdk/credential-provider-sso": "3.502.0", + "@aws-sdk/credential-provider-web-identity": "3.502.0", + "@aws-sdk/types": "3.502.0", + "@smithy/credential-provider-imds": "^2.2.1", + "@smithy/property-provider": "^2.1.1", + "@smithy/shared-ini-file-loader": "^2.3.1", + "@smithy/types": "^2.9.1", "tslib": "^2.5.0" }, "engines": { @@ -883,20 +922,20 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.485.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.485.0.tgz", - "integrity": "sha512-2DwzO2azkSzngifKDT61W/DL0tSzewuaFHiLJWdfc8Et3mdAQJ9x3KAj8u7XFpjIcGNqk7FiKjN+zeGUuNiEhA==", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.485.0", - "@aws-sdk/credential-provider-ini": "3.485.0", - "@aws-sdk/credential-provider-process": "3.485.0", - "@aws-sdk/credential-provider-sso": "3.485.0", - "@aws-sdk/credential-provider-web-identity": "3.485.0", - "@aws-sdk/types": "3.485.0", - "@smithy/credential-provider-imds": "^2.0.0", - "@smithy/property-provider": "^2.0.0", - "@smithy/shared-ini-file-loader": "^2.0.6", - "@smithy/types": "^2.8.0", + "version": "3.502.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.502.0.tgz", + "integrity": "sha512-qg71UpYeFrjhu5hD+vdRqZ+EYFB11BeszsbfEJGaHhOMHmmTHNBaDAexW+bUnJSXcJL0a8vniCvca+rElbcAHQ==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.502.0", + "@aws-sdk/credential-provider-ini": "3.502.0", + "@aws-sdk/credential-provider-process": "3.502.0", + "@aws-sdk/credential-provider-sso": "3.502.0", + "@aws-sdk/credential-provider-web-identity": "3.502.0", + "@aws-sdk/types": "3.502.0", + "@smithy/credential-provider-imds": "^2.2.1", + "@smithy/property-provider": "^2.1.1", + "@smithy/shared-ini-file-loader": "^2.3.1", + "@smithy/types": "^2.9.1", "tslib": "^2.5.0" }, "engines": { @@ -904,14 +943,14 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.485.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.485.0.tgz", - "integrity": "sha512-X9qS6ZO/rDKYDgWqD1YmSX7sAUUHax9HbXlgGiTTdtfhZvQh1ZmnH6wiPu5WNliafHZFtZT2W07kgrDLPld/Ug==", - "dependencies": { - "@aws-sdk/types": "3.485.0", - "@smithy/property-provider": "^2.0.0", - "@smithy/shared-ini-file-loader": "^2.0.6", - "@smithy/types": "^2.8.0", + "version": "3.502.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.502.0.tgz", + "integrity": "sha512-fJJowOjQ4infYQX0E1J3xFVlmuwEYJAFk0Mo1qwafWmEthsBJs+6BR2RiWDELHKrSK35u4Pf3fu3RkYuCtmQFw==", + "dependencies": { + "@aws-sdk/types": "3.502.0", + "@smithy/property-provider": "^2.1.1", + "@smithy/shared-ini-file-loader": "^2.3.1", + "@smithy/types": "^2.9.1", "tslib": "^2.5.0" }, "engines": { @@ -919,16 +958,16 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.485.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.485.0.tgz", - "integrity": "sha512-l0oC8GTrWh+LFQQfSmG1Jai1PX7Mhj9arb/CaS1/tmeZE0hgIXW++tvljYs/Dds4LGXUlaWG+P7BrObf6OyIXA==", - "dependencies": { - "@aws-sdk/client-sso": "3.485.0", - "@aws-sdk/token-providers": "3.485.0", - "@aws-sdk/types": "3.485.0", - "@smithy/property-provider": "^2.0.0", - "@smithy/shared-ini-file-loader": "^2.0.6", - "@smithy/types": "^2.8.0", + "version": "3.502.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.502.0.tgz", + "integrity": "sha512-/2Nyvo+cWQpH283lmZBimTJ9JDhES9FzQUkhUXZgxQo3Ez4sguLVi2V9xoFFyG0cMff5fuNivdKHfj4FeMGjZw==", + "dependencies": { + "@aws-sdk/client-sso": "3.502.0", + "@aws-sdk/token-providers": "3.502.0", + "@aws-sdk/types": "3.502.0", + "@smithy/property-provider": "^2.1.1", + "@smithy/shared-ini-file-loader": "^2.3.1", + "@smithy/types": "^2.9.1", "tslib": "^2.5.0" }, "engines": { @@ -936,13 +975,14 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.485.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.485.0.tgz", - "integrity": "sha512-WpBFZFE0iXtnibH5POMEKITj/hR0YV5l2n9p8BEvKjdJ63s3Xke1RN20ZdIyKDaRDwj8adnKDgNPEnAKdS4kLw==", + "version": "3.502.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.502.0.tgz", + "integrity": "sha512-veBAjDqjMMgA2Qxxf9ywDfHYLeJpaeHWLWCQ9XCHwJJ6ZIGWmAZPTq3he/UMr5JIQXooIccqqyqXMDIXPenXpA==", "dependencies": { - "@aws-sdk/types": "3.485.0", - "@smithy/property-provider": "^2.0.0", - "@smithy/types": "^2.8.0", + "@aws-sdk/client-sts": "3.502.0", + "@aws-sdk/types": "3.502.0", + "@smithy/property-provider": "^2.1.1", + "@smithy/types": "^2.9.1", "tslib": "^2.5.0" }, "engines": { @@ -950,13 +990,13 @@ } }, "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.485.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.485.0.tgz", - "integrity": "sha512-1mAUX9dQNGo2RIKseVj7SI/D5abQJQ/Os8hQ0NyVAyyVYF+Yjx5PphKgfhM5yoBwuwZUl6q71XPYEGNx7be6SA==", + "version": "3.502.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.502.0.tgz", + "integrity": "sha512-EjnG0GTYXT/wJBmm5/mTjDcAkzU8L7wQjOzd3FTXuTCNNyvAvwrszbOj5FlarEw5XJBbQiZtBs+I5u9+zy560w==", "dependencies": { - "@aws-sdk/types": "3.485.0", - "@smithy/protocol-http": "^3.0.12", - "@smithy/types": "^2.8.0", + "@aws-sdk/types": "3.502.0", + "@smithy/protocol-http": "^3.1.1", + "@smithy/types": "^2.9.1", "tslib": "^2.5.0" }, "engines": { @@ -964,12 +1004,12 @@ } }, "node_modules/@aws-sdk/middleware-logger": { - "version": "3.485.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.485.0.tgz", - "integrity": "sha512-O8IgJ0LHi5wTs5GlpI7nqmmSSagkVdd1shpGgQWY2h0kMSCII8CJZHBG97dlFFpGTvx5EDlhPNek7rl/6F4dRw==", + "version": "3.502.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.502.0.tgz", + "integrity": "sha512-FDyv6K4nCoHxbjLGS2H8ex8I0KDIiu4FJgVRPs140ZJy6gE5Pwxzv6YTzZGLMrnqcIs9gh065Lf6DjwMelZqaw==", "dependencies": { - "@aws-sdk/types": "3.485.0", - "@smithy/types": "^2.8.0", + "@aws-sdk/types": "3.502.0", + "@smithy/types": "^2.9.1", "tslib": "^2.5.0" }, "engines": { @@ -977,13 +1017,13 @@ } }, "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.485.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.485.0.tgz", - "integrity": "sha512-ZeVNATGNFcqkWDut3luVszROTUzkU5u+rJpB/xmeMoenlDAjPRiHt/ca3WkI5wAnIJ1VSNGpD2sOFLMCH+EWag==", + "version": "3.502.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.502.0.tgz", + "integrity": "sha512-hvbyGJbxeuezxOu8VfFmcV4ql1hKXLxHTe5FNYfEBat2KaZXVhc1Hg+4TvB06/53p+E8J99Afmumkqbxs2esUA==", "dependencies": { - "@aws-sdk/types": "3.485.0", - "@smithy/protocol-http": "^3.0.12", - "@smithy/types": "^2.8.0", + "@aws-sdk/types": "3.502.0", + "@smithy/protocol-http": "^3.1.1", + "@smithy/types": "^2.9.1", "tslib": "^2.5.0" }, "engines": { @@ -991,16 +1031,16 @@ } }, "node_modules/@aws-sdk/middleware-signing": { - "version": "3.485.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.485.0.tgz", - "integrity": "sha512-41xzT2p1sOibhsLkdE5rwPJkNbBtKD8Gp36/ySfu0KE415wfXKacElSVxAaBw39/j7iSWDYqqybeEYbAzk+3GQ==", - "dependencies": { - "@aws-sdk/types": "3.485.0", - "@smithy/property-provider": "^2.0.0", - "@smithy/protocol-http": "^3.0.12", - "@smithy/signature-v4": "^2.0.0", - "@smithy/types": "^2.8.0", - "@smithy/util-middleware": "^2.0.9", + "version": "3.502.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.502.0.tgz", + "integrity": "sha512-4hF08vSzJ7L6sB+393gOFj3s2N6nLusYS0XrMW6wYNFU10IDdbf8Z3TZ7gysDJJHEGQPmTAesPEDBsasGWcMxg==", + "dependencies": { + "@aws-sdk/types": "3.502.0", + "@smithy/property-provider": "^2.1.1", + "@smithy/protocol-http": "^3.1.1", + "@smithy/signature-v4": "^2.1.1", + "@smithy/types": "^2.9.1", + "@smithy/util-middleware": "^2.1.1", "tslib": "^2.5.0" }, "engines": { @@ -1008,14 +1048,14 @@ } }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.485.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.485.0.tgz", - "integrity": "sha512-CddCVOn+OPQ0CcchketIg+WF6v+MDLAf3GOYTR2htUxxIm7HABuRd6R3kvQ5Jny9CV8gMt22G1UZITsFexSJlQ==", - "dependencies": { - "@aws-sdk/types": "3.485.0", - "@aws-sdk/util-endpoints": "3.485.0", - "@smithy/protocol-http": "^3.0.12", - "@smithy/types": "^2.8.0", + "version": "3.502.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.502.0.tgz", + "integrity": "sha512-TxbBZbRiXPH0AUxegqiNd9aM9zNSbfjtBs5MEfcBsweeT/B2O7K1EjP9+CkB8Xmk/5FLKhAKLr19b1TNoE27rw==", + "dependencies": { + "@aws-sdk/types": "3.502.0", + "@aws-sdk/util-endpoints": "3.502.0", + "@smithy/protocol-http": "^3.1.1", + "@smithy/types": "^2.9.1", "tslib": "^2.5.0" }, "engines": { @@ -1023,14 +1063,15 @@ } }, "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.485.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.485.0.tgz", - "integrity": "sha512-2FB2EQ0sIE+YgFqGtkE1lDIMIL6nYe6MkOHBwBM7bommadKIrbbr2L22bPZGs3ReTsxiJabjzxbuCAVhrpHmhg==", - "dependencies": { - "@smithy/node-config-provider": "^2.1.9", - "@smithy/types": "^2.8.0", - "@smithy/util-config-provider": "^2.1.0", - "@smithy/util-middleware": "^2.0.9", + "version": "3.502.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.502.0.tgz", + "integrity": "sha512-mxmsX2AGgnSM+Sah7mcQCIneOsJQNiLX0COwEttuf8eO+6cLMAZvVudH3BnWTfea4/A9nuri9DLCqBvEmPrilg==", + "dependencies": { + "@aws-sdk/types": "3.502.0", + "@smithy/node-config-provider": "^2.2.1", + "@smithy/types": "^2.9.1", + "@smithy/util-config-provider": "^2.2.1", + "@smithy/util-middleware": "^2.1.1", "tslib": "^2.5.0" }, "engines": { @@ -1038,46 +1079,15 @@ } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.485.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.485.0.tgz", - "integrity": "sha512-kOXA1WKIVIFNRqHL8ynVZ3hCKLsgnEmGr2iDR6agDNw5fYIlCO/6N2xR6QdGcLTvUUbwOlz4OvKLUQnWMKAnnA==", + "version": "3.502.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.502.0.tgz", + "integrity": "sha512-RQgMgIXYlSf0xGl6EUeD+pqIPBlb7e29dbqHOBFc66hJVYUC2ULZX7Y+jLvcGIEaMiIaTPyvntZRFip+U+9hag==", "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/middleware-host-header": "3.485.0", - "@aws-sdk/middleware-logger": "3.485.0", - "@aws-sdk/middleware-recursion-detection": "3.485.0", - "@aws-sdk/middleware-user-agent": "3.485.0", - "@aws-sdk/region-config-resolver": "3.485.0", - "@aws-sdk/types": "3.485.0", - "@aws-sdk/util-endpoints": "3.485.0", - "@aws-sdk/util-user-agent-browser": "3.485.0", - "@aws-sdk/util-user-agent-node": "3.485.0", - "@smithy/config-resolver": "^2.0.23", - "@smithy/fetch-http-handler": "^2.3.2", - "@smithy/hash-node": "^2.0.18", - "@smithy/invalid-dependency": "^2.0.16", - "@smithy/middleware-content-length": "^2.0.18", - "@smithy/middleware-endpoint": "^2.3.0", - "@smithy/middleware-retry": "^2.0.26", - "@smithy/middleware-serde": "^2.0.16", - "@smithy/middleware-stack": "^2.0.10", - "@smithy/node-config-provider": "^2.1.9", - "@smithy/node-http-handler": "^2.2.2", - "@smithy/property-provider": "^2.0.0", - "@smithy/protocol-http": "^3.0.12", - "@smithy/shared-ini-file-loader": "^2.0.6", - "@smithy/smithy-client": "^2.2.1", - "@smithy/types": "^2.8.0", - "@smithy/url-parser": "^2.0.16", - "@smithy/util-base64": "^2.0.1", - "@smithy/util-body-length-browser": "^2.0.1", - "@smithy/util-body-length-node": "^2.1.0", - "@smithy/util-defaults-mode-browser": "^2.0.24", - "@smithy/util-defaults-mode-node": "^2.0.32", - "@smithy/util-endpoints": "^1.0.8", - "@smithy/util-retry": "^2.0.9", - "@smithy/util-utf8": "^2.0.2", + "@aws-sdk/client-sso-oidc": "3.502.0", + "@aws-sdk/types": "3.502.0", + "@smithy/property-provider": "^2.1.1", + "@smithy/shared-ini-file-loader": "^2.3.1", + "@smithy/types": "^2.9.1", "tslib": "^2.5.0" }, "engines": { @@ -1085,11 +1095,11 @@ } }, "node_modules/@aws-sdk/types": { - "version": "3.485.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.485.0.tgz", - "integrity": "sha512-+QW32YQdvZRDOwrAQPo/qCyXoSjgXB6RwJwCwkd8ebJXRXw6tmGKIHaZqYHt/LtBymvnaBgBBADNa4+qFvlOFw==", + "version": "3.502.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.502.0.tgz", + "integrity": "sha512-M0DSPYe/gXhwD2QHgoukaZv5oDxhW3FfvYIrJptyqUq3OnPJBcDbihHjrE0PBtfh/9kgMZT60/fQ2NVFANfa2g==", "dependencies": { - "@smithy/types": "^2.8.0", + "@smithy/types": "^2.9.1", "tslib": "^2.5.0" }, "engines": { @@ -1097,12 +1107,13 @@ } }, "node_modules/@aws-sdk/util-endpoints": { - "version": "3.485.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.485.0.tgz", - "integrity": "sha512-dTd642F7nJisApF8YjniqQ6U59CP/DCtar11fXf1nG9YNBCBsNNVw5ZfZb5nSNzaIdy27mQioWTCV18JEj1mxg==", + "version": "3.502.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.502.0.tgz", + "integrity": "sha512-6LKFlJPp2J24r1Kpfoz5ESQn+1v5fEjDB3mtUKRdpwarhm3syu7HbKlHCF3KbcCOyahobvLvhoedT78rJFEeeg==", "dependencies": { - "@aws-sdk/types": "3.485.0", - "@smithy/util-endpoints": "^1.0.8", + "@aws-sdk/types": "3.502.0", + "@smithy/types": "^2.9.1", + "@smithy/util-endpoints": "^1.1.1", "tslib": "^2.5.0" }, "engines": { @@ -1121,24 +1132,24 @@ } }, "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.485.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.485.0.tgz", - "integrity": "sha512-QliWbjg0uOhGTcWgWTKPMY0SBi07g253DjwrCINT1auqDrdQPxa10xozpZExBYjAK2KuhYDNUzni127ae6MHOw==", + "version": "3.502.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.502.0.tgz", + "integrity": "sha512-v8gKyCs2obXoIkLETAeEQ3AM+QmhHhst9xbM1cJtKUGsRlVIak/XyyD+kVE6kmMm1cjfudHpHKABWk9apQcIZQ==", "dependencies": { - "@aws-sdk/types": "3.485.0", - "@smithy/types": "^2.8.0", + "@aws-sdk/types": "3.502.0", + "@smithy/types": "^2.9.1", "bowser": "^2.11.0", "tslib": "^2.5.0" } }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.485.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.485.0.tgz", - "integrity": "sha512-QF+aQ9jnDlPUlFBxBRqOylPf86xQuD3aEPpOErR+50qJawVvKa94uiAFdvtI9jv6hnRZmuFsTj2rsyytnbAYBA==", + "version": "3.502.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.502.0.tgz", + "integrity": "sha512-9RjxpkGZKbTdl96tIJvAo+vZoz4P/cQh36SBUt9xfRfW0BtsaLyvSrvlR5wyUYhvRcC12Axqh/8JtnAPq//+Vw==", "dependencies": { - "@aws-sdk/types": "3.485.0", - "@smithy/node-config-provider": "^2.1.9", - "@smithy/types": "^2.8.0", + "@aws-sdk/types": "3.502.0", + "@smithy/node-config-provider": "^2.2.1", + "@smithy/types": "^2.9.1", "tslib": "^2.5.0" }, "engines": { diff --git a/backend/package.json b/backend/package.json index cd9e507192..4921d7f303 100644 --- a/backend/package.json +++ b/backend/package.json @@ -70,7 +70,7 @@ "vitest": "^1.2.2" }, "dependencies": { - "@aws-sdk/client-secrets-manager": "^3.485.0", + "@aws-sdk/client-secrets-manager": "^3.502.0", "@casl/ability": "^6.5.0", "@fastify/cookie": "^9.2.0", "@fastify/cors": "^8.4.1", From 7c098529f727fe4c277617b68893a69afdb78e50 Mon Sep 17 00:00:00 2001 From: Akhil Mohan Date: Wed, 21 Feb 2024 14:09:28 +0530 Subject: [PATCH 29/29] fix(admin): resolved undefined on redirect after admin signup --- backend/src/server/routes/v1/admin-router.ts | 6 ++++-- .../services/super-admin/super-admin-service.ts | 8 ++++++-- frontend/src/hooks/api/admin/mutation.ts | 8 +++++++- .../src/views/admin/SignUpPage/SignUpPage.tsx | 17 ++++++++++++++--- 4 files changed, 31 insertions(+), 8 deletions(-) diff --git a/backend/src/server/routes/v1/admin-router.ts b/backend/src/server/routes/v1/admin-router.ts index da13d5e00c..ab069a2ddb 100644 --- a/backend/src/server/routes/v1/admin-router.ts +++ b/backend/src/server/routes/v1/admin-router.ts @@ -1,6 +1,6 @@ import { z } from "zod"; -import { SuperAdminSchema, UsersSchema } from "@app/db/schemas"; +import { OrganizationsSchema, SuperAdminSchema, UsersSchema } from "@app/db/schemas"; import { getConfig } from "@app/lib/config/env"; import { UnauthorizedError } from "@app/lib/errors"; import { verifySuperAdmin } from "@app/server/plugins/auth/superAdmin"; @@ -72,6 +72,7 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => { 200: z.object({ message: z.string(), user: UsersSchema, + organization: OrganizationsSchema, token: z.string(), new: z.string() }) @@ -82,7 +83,7 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => { 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({ + const { user, token, organization } = await server.services.superAdmin.adminSignUp({ ...req.body, ip: req.realIp, userAgent: req.headers["user-agent"] || "" @@ -109,6 +110,7 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => { message: "Successfully set up admin account", user: user.user, token: token.access, + organization, new: "123" }; } diff --git a/backend/src/services/super-admin/super-admin-service.ts b/backend/src/services/super-admin/super-admin-service.ts index 1144bd414a..9cdc55c8af 100644 --- a/backend/src/services/super-admin/super-admin-service.ts +++ b/backend/src/services/super-admin/super-admin-service.ts @@ -96,7 +96,11 @@ export const superAdminServiceFactory = ({ const initialOrganizationName = appCfg.INITIAL_ORGANIZATION_NAME ?? "Admin Org"; - await orgService.createOrganization(userInfo.user.id, userInfo.user.email, initialOrganizationName); + const organization = await orgService.createOrganization( + userInfo.user.id, + userInfo.user.email, + initialOrganizationName + ); await updateServerCfg({ initialized: true }); const token = await authService.generateUserTokens({ @@ -106,7 +110,7 @@ export const superAdminServiceFactory = ({ organizationId: undefined }); // TODO(akhilmhdh-pg): telemetry service - return { token, user: userInfo }; + return { token, user: userInfo, organization }; }; return { diff --git a/frontend/src/hooks/api/admin/mutation.ts b/frontend/src/hooks/api/admin/mutation.ts index ea53ff1db4..6d25944ef4 100644 --- a/frontend/src/hooks/api/admin/mutation.ts +++ b/frontend/src/hooks/api/admin/mutation.ts @@ -2,6 +2,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; import { apiRequest } from "@app/config/request"; +import { organizationKeys } from "../organization/queries"; import { User } from "../users/types"; import { adminQueryKeys } from "./queries"; import { TCreateAdminUserDTO, TServerConfig } from "./types"; @@ -9,7 +10,11 @@ import { TCreateAdminUserDTO, TServerConfig } from "./types"; export const useCreateAdminUser = () => { const queryClient = useQueryClient(); - return useMutation<{ user: User; token: string }, {}, TCreateAdminUserDTO>({ + return useMutation< + { user: User; token: string; organization: { id: string } }, + {}, + TCreateAdminUserDTO + >({ mutationFn: async (opt) => { const { data } = await apiRequest.post("/api/v1/admin/signup", opt); return data; @@ -34,6 +39,7 @@ export const useUpdateServerConfig = () => { onSuccess: (data) => { queryClient.setQueryData(adminQueryKeys.serverConfig(), data); queryClient.invalidateQueries(adminQueryKeys.serverConfig()); + queryClient.invalidateQueries(organizationKeys.getUserOrganizations); } }); }; diff --git a/frontend/src/views/admin/SignUpPage/SignUpPage.tsx b/frontend/src/views/admin/SignUpPage/SignUpPage.tsx index f452e114ec..8a26030ce2 100644 --- a/frontend/src/views/admin/SignUpPage/SignUpPage.tsx +++ b/frontend/src/views/admin/SignUpPage/SignUpPage.tsx @@ -84,6 +84,10 @@ export const SignUpPage = () => { tag: userPass.encryptedPrivateKeyTag, privateKey }); + // TODO(akhilmhdh): This is such a confusing pattern and too unreliable + // Will be refactored in next iteration to make it url based rather than local storage ones + // Part of migration to nextjs 14 + localStorage.setItem("orgData.id", res.organization.id); setStep(SignupSteps.BackupKey); } catch (err) { console.log(err); @@ -130,8 +134,8 @@ export const SignUpPage = () => { >
Infisical logo -
Welcome to Infisical
-
Create your first Super Admin Account
+
Welcome to Infisical
+
Create your first Super Admin Account
@@ -199,7 +203,14 @@ export const SignUpPage = () => { )} />
-