diff --git a/backend/package-lock.json b/backend/package-lock.json index 262d974a7..1b8e1af72 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -28,7 +28,7 @@ "bigint-conversion": "^2.4.0", "cookie-parser": "^1.4.6", "cors": "^2.8.5", - "crypto-js": "^4.1.1", + "crypto-js": "^4.2.0", "dotenv": "^16.0.1", "express": "^4.18.1", "express-async-errors": "^3.1.1", @@ -6892,42 +6892,6 @@ "node": ">=8" } }, - "node_modules/bl": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", - "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==", - "dependencies": { - "readable-stream": "^2.3.5", - "safe-buffer": "^5.1.1" - } - }, - "node_modules/bl/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/bl/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/bl/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, "node_modules/body-parser": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", @@ -7519,7 +7483,8 @@ "node_modules/core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "dev": true }, "node_modules/cors": { "version": "2.8.5", @@ -13668,17 +13633,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/optional-require": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.1.8.tgz", - "integrity": "sha512-jq83qaUb0wNg9Krv1c5OQ+58EK+vHde6aBPzLvPPqJm89UQWsvSuFy9X/OSNJnFeSOKo7btE0n8Nl2+nE+z5nA==", - "dependencies": { - "require-at": "^1.0.6" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -14563,11 +14517,6 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, "node_modules/process-warning": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-1.0.0.tgz", @@ -14765,36 +14714,33 @@ "underscore": "1.12.1" } }, - "node_modules/rate-limit-mongo/node_modules/bson": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz", - "integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==", - "engines": { - "node": ">=0.6.19" - } - }, "node_modules/rate-limit-mongo/node_modules/mongodb": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.7.4.tgz", - "integrity": "sha512-K5q8aBqEXMwWdVNh94UQTwZ6BejVbFhh1uB6c5FKtPE9eUMZPUO3sRZdgIEcHSrAWmxzpG/FeODDKL388sqRmw==", + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.8.0.tgz", + "integrity": "sha512-xx4CXmxcj3bNe7iGBlhntVrUqrNARYhUZteXaz4epEESv4oXD/FONAovcyoCaEffdYlw25Yz284OxMfpnPLlgQ==", "dependencies": { - "bl": "^2.2.1", - "bson": "^1.1.4", - "denque": "^1.4.1", - "optional-require": "^1.1.8", - "safe-buffer": "^5.1.2" + "bson": "^5.4.0", + "mongodb-connection-string-url": "^2.6.0", + "socks": "^2.7.1" }, "engines": { - "node": ">=4" + "node": ">=14.20.1" }, "optionalDependencies": { - "saslprep": "^1.0.0" + "@mongodb-js/saslprep": "^1.1.0" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.0.0", + "kerberos": "^1.0.0 || ^2.0.0", + "mongodb-client-encryption": ">=2.3.0 <3", + "snappy": "^7.2.2" }, "peerDependenciesMeta": { - "aws4": { + "@aws-sdk/credential-providers": { "optional": true }, - "bson-ext": { + "@mongodb-js/zstd": { "optional": true }, "kerberos": { @@ -14803,9 +14749,6 @@ "mongodb-client-encryption": { "optional": true }, - "mongodb-extjson": { - "optional": true - }, "snappy": { "optional": true } @@ -14885,14 +14828,6 @@ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" }, - "node_modules/require-at": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/require-at/-/require-at-1.0.6.tgz", - "integrity": "sha512-7i1auJbMUrXEAZCOQ0VNJgmcT2VOKPRl2YGJwgpHpC9CE91Mv4/4UYIUm4chGJaI381ZDq1JUicFii64Hapd8g==", - "engines": { - "node": ">=4" - } - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -15050,18 +14985,6 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, - "node_modules/saslprep": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", - "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", - "optional": true, - "dependencies": { - "sparse-bitfield": "^3.0.3" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/sax": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", @@ -22279,44 +22202,6 @@ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true }, - "bl": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", - "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==", - "requires": { - "readable-stream": "^2.3.5", - "safe-buffer": "^5.1.1" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, "body-parser": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", @@ -22761,7 +22646,8 @@ "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "dev": true }, "cors": { "version": "2.8.5", @@ -27229,14 +27115,6 @@ "mimic-fn": "^2.1.0" } }, - "optional-require": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.1.8.tgz", - "integrity": "sha512-jq83qaUb0wNg9Krv1c5OQ+58EK+vHde6aBPzLvPPqJm89UQWsvSuFy9X/OSNJnFeSOKo7btE0n8Nl2+nE+z5nA==", - "requires": { - "require-at": "^1.0.6" - } - }, "optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -27978,11 +27856,6 @@ } } }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, "process-warning": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-1.0.0.tgz", @@ -28116,27 +27989,20 @@ "resolved": "https://registry.npmjs.org/rate-limit-mongo/-/rate-limit-mongo-2.3.2.tgz", "integrity": "sha512-dLck0j5N/AX9ycVHn5lX9Ti2Wrrwi1LfbXitu/mMBZOo2nC26RgYKJVbcb2mYgb9VMaPI2IwJVzIa2hAQrMaDA==", "requires": { - "mongodb": "^3.6.7", + "mongodb": "5.8.0", "twostep": "0.4.2", "underscore": "1.12.1" }, "dependencies": { - "bson": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz", - "integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==" - }, "mongodb": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.7.4.tgz", - "integrity": "sha512-K5q8aBqEXMwWdVNh94UQTwZ6BejVbFhh1uB6c5FKtPE9eUMZPUO3sRZdgIEcHSrAWmxzpG/FeODDKL388sqRmw==", + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.8.0.tgz", + "integrity": "sha512-xx4CXmxcj3bNe7iGBlhntVrUqrNARYhUZteXaz4epEESv4oXD/FONAovcyoCaEffdYlw25Yz284OxMfpnPLlgQ==", "requires": { - "bl": "^2.2.1", - "bson": "^1.1.4", - "denque": "^1.4.1", - "optional-require": "^1.1.8", - "safe-buffer": "^5.1.2", - "saslprep": "^1.0.0" + "@mongodb-js/saslprep": "^1.1.0", + "bson": "^5.4.0", + "mongodb-connection-string-url": "^2.6.0", + "socks": "^2.7.1" } } } @@ -28200,11 +28066,6 @@ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" }, - "require-at": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/require-at/-/require-at-1.0.6.tgz", - "integrity": "sha512-7i1auJbMUrXEAZCOQ0VNJgmcT2VOKPRl2YGJwgpHpC9CE91Mv4/4UYIUm4chGJaI381ZDq1JUicFii64Hapd8g==" - }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -28302,15 +28163,6 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, - "saslprep": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", - "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", - "optional": true, - "requires": { - "sparse-bitfield": "^3.0.3" - } - }, "sax": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", diff --git a/backend/package.json b/backend/package.json index 8e379bd37..82a640b95 100644 --- a/backend/package.json +++ b/backend/package.json @@ -19,7 +19,7 @@ "bigint-conversion": "^2.4.0", "cookie-parser": "^1.4.6", "cors": "^2.8.5", - "crypto-js": "^4.1.1", + "crypto-js": "^4.2.0", "dotenv": "^16.0.1", "express": "^4.18.1", "express-async-errors": "^3.1.1", @@ -56,6 +56,11 @@ "winston-loki": "^6.0.6", "zod": "^3.22.3" }, + "overrides": { + "rate-limit-mongo": { + "mongodb": "5.8.0" + } + }, "name": "infisical-api", "version": "1.0.0", "main": "src/index.js", diff --git a/backend/src/controllers/v1/webhookController.ts b/backend/src/controllers/v1/webhookController.ts index 73d92e7dc..09175ebfe 100644 --- a/backend/src/controllers/v1/webhookController.ts +++ b/backend/src/controllers/v1/webhookController.ts @@ -1,12 +1,16 @@ import { Request, Response } from "express"; import { Types } from "mongoose"; -import { client, getRootEncryptionKey } from "../../config"; +import { client, getEncryptionKey, getRootEncryptionKey } from "../../config"; import { Webhook } from "../../models"; import { getWebhookPayload, triggerWebhookRequest } from "../../services/WebhookService"; import { BadRequestError, ResourceNotFoundError } from "../../utils/errors"; import { EEAuditLogService } from "../../ee/services"; import { EventType } from "../../ee/models"; -import { ALGORITHM_AES_256_GCM, ENCODING_SCHEME_BASE64 } from "../../variables"; +import { + ALGORITHM_AES_256_GCM, + ENCODING_SCHEME_BASE64, + ENCODING_SCHEME_UTF8 +} from "../../variables"; import { validateRequest } from "../../helpers/validation"; import * as reqValidator from "../../validation/webhooks"; import { @@ -15,6 +19,7 @@ import { getUserProjectPermissions } from "../../ee/services/ProjectRoleService"; import { ForbiddenError } from "@casl/ability"; +import { encryptSymmetric128BitHexKeyUTF8 } from "../../utils/crypto"; export const createWebhook = async (req: Request, res: Response) => { const { @@ -31,17 +36,31 @@ export const createWebhook = async (req: Request, res: Response) => { workspace: workspaceId, environment, secretPath, - url: webhookUrl, - algorithm: ALGORITHM_AES_256_GCM, - keyEncoding: ENCODING_SCHEME_BASE64 + url: webhookUrl }); if (webhookSecretKey) { + const encryptionKey = await getEncryptionKey(); const rootEncryptionKey = await getRootEncryptionKey(); - const { ciphertext, iv, tag } = client.encryptSymmetric(webhookSecretKey, rootEncryptionKey); - webhook.iv = iv; - webhook.tag = tag; - webhook.encryptedSecretKey = ciphertext; + + if (rootEncryptionKey) { + const { ciphertext, iv, tag } = client.encryptSymmetric(webhookSecretKey, rootEncryptionKey); + webhook.iv = iv; + webhook.tag = tag; + webhook.encryptedSecretKey = ciphertext; + webhook.algorithm = ALGORITHM_AES_256_GCM; + webhook.keyEncoding = ENCODING_SCHEME_BASE64; + } else if (encryptionKey) { + const { ciphertext, iv, tag } = encryptSymmetric128BitHexKeyUTF8({ + plaintext: webhookSecretKey, + key: encryptionKey + }); + webhook.iv = iv; + webhook.tag = tag; + webhook.encryptedSecretKey = ciphertext; + webhook.algorithm = ALGORITHM_AES_256_GCM; + webhook.keyEncoding = ENCODING_SCHEME_UTF8; + } } await webhook.save(); diff --git a/backend/src/models/webhooks.ts b/backend/src/models/webhooks.ts index 845c6ada2..bef5e795a 100644 --- a/backend/src/models/webhooks.ts +++ b/backend/src/models/webhooks.ts @@ -65,13 +65,11 @@ const WebhookSchema = new Schema( // the encryption algorithm used type: String, enum: [ALGORITHM_AES_256_GCM], - required: true, select: false }, keyEncoding: { type: String, enum: [ENCODING_SCHEME_UTF8, ENCODING_SCHEME_BASE64], - required: true, select: false } }, @@ -80,4 +78,4 @@ const WebhookSchema = new Schema( } ); -export const Webhook = model("Webhook", WebhookSchema); \ No newline at end of file +export const Webhook = model("Webhook", WebhookSchema); diff --git a/backend/src/services/WebhookService.ts b/backend/src/services/WebhookService.ts index 7506e25c1..cc2106a1c 100644 --- a/backend/src/services/WebhookService.ts +++ b/backend/src/services/WebhookService.ts @@ -2,26 +2,42 @@ import axios from "axios"; import crypto from "crypto"; import { Types } from "mongoose"; import picomatch from "picomatch"; -import { client, getRootEncryptionKey } from "../config"; +import { client, getEncryptionKey, getRootEncryptionKey } from "../config"; import { IWebhook, Webhook } from "../models"; +import { decryptSymmetric128BitHexKeyUTF8 } from "../utils/crypto"; +import { ENCODING_SCHEME_BASE64, ENCODING_SCHEME_UTF8 } from "../variables"; export const triggerWebhookRequest = async ( - { url, encryptedSecretKey, iv, tag }: IWebhook, + { url, encryptedSecretKey, iv, tag, keyEncoding }: IWebhook, payload: Record ) => { const headers: Record = {}; payload["timestamp"] = Date.now(); if (encryptedSecretKey) { + const encryptionKey = await getEncryptionKey(); const rootEncryptionKey = await getRootEncryptionKey(); - const secretKey = client.decryptSymmetric(encryptedSecretKey, rootEncryptionKey, iv, tag); - const webhookSign = crypto - .createHmac("sha256", secretKey) - .update(JSON.stringify(payload)) - .digest("hex"); - headers["x-infisical-signature"] = `t=${payload["timestamp"]};${webhookSign}`; + let secretKey; + if (rootEncryptionKey && keyEncoding === ENCODING_SCHEME_BASE64) { + // case: encoding scheme is base64 + secretKey = client.decryptSymmetric(encryptedSecretKey, rootEncryptionKey, iv, tag); + } else if (encryptionKey && keyEncoding === ENCODING_SCHEME_UTF8) { + // case: encoding scheme is utf8 + secretKey = decryptSymmetric128BitHexKeyUTF8({ + ciphertext: encryptedSecretKey, + iv: iv, + tag: tag, + key: encryptionKey + }); + } + if (secretKey) { + const webhookSign = crypto + .createHmac("sha256", secretKey) + .update(JSON.stringify(payload)) + .digest("hex"); + headers["x-infisical-signature"] = `t=${payload["timestamp"]};${webhookSign}`; + } } - const req = await axios.post(url, payload, { headers }); return req; }; diff --git a/docs/self-hosting/deployment-options/kubernetes-helm.mdx b/docs/self-hosting/deployment-options/kubernetes-helm.mdx index e0a4f36e6..6ca373c25 100644 --- a/docs/self-hosting/deployment-options/kubernetes-helm.mdx +++ b/docs/self-hosting/deployment-options/kubernetes-helm.mdx @@ -36,8 +36,7 @@ By default, the application will use the `latest` docker image tag. This is okay backend: replicaCount: 2 image: - repository: infisical/infisical - tag: "v0.39.5" + tag: "v0.39.5" # <--- update to the newest version found here https://hub.docker.com/r/infisical/infisical/tags pullPolicy: Always ``` @@ -96,7 +95,6 @@ Managed database connection string can be set in the `backendEnvironmentVariable backend: replicaCount: 2 image: - repository: infisical/infisical tag: "v0.39.5" pullPolicy: Always @@ -122,7 +120,6 @@ ingress: deploymentAnnotations: {} replicaCount: 4 image: - repository: infisical/infisical tag: "v0.39.5" pullPolicy: IfNotPresent kubeSecretRef: null diff --git a/frontend/package-lock.json b/frontend/package-lock.json index a97ea5d88..9ed46770b 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -93,7 +93,7 @@ "uuidv4": "^6.2.13", "yaml": "^2.2.2", "yup": "^0.32.11", - "zod": "^3.22.0", + "zod": "^3.22.3", "zustand": "^4.4.1" }, "devDependencies": { @@ -24758,9 +24758,9 @@ } }, "node_modules/zod": { - "version": "3.22.0", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.0.tgz", - "integrity": "sha512-y5KZY/ssf5n7hCGDGGtcJO/EBJEm5Pa+QQvFBeyMOtnFYOSflalxIFFvdaYevPhePcmcKC4aTbFkCcXN7D0O8Q==", + "version": "3.22.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz", + "integrity": "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==", "funding": { "url": "https://github.com/sponsors/colinhacks" } @@ -42809,9 +42809,9 @@ } }, "zod": { - "version": "3.22.0", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.0.tgz", - "integrity": "sha512-y5KZY/ssf5n7hCGDGGtcJO/EBJEm5Pa+QQvFBeyMOtnFYOSflalxIFFvdaYevPhePcmcKC4aTbFkCcXN7D0O8Q==" + "version": "3.22.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz", + "integrity": "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==" }, "zustand": { "version": "4.4.1", diff --git a/frontend/package.json b/frontend/package.json index deb95d775..ea03a7472 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -101,7 +101,7 @@ "uuidv4": "^6.2.13", "yaml": "^2.2.2", "yup": "^0.32.11", - "zod": "^3.22.0", + "zod": "^3.22.3", "zustand": "^4.4.1" }, "devDependencies": { diff --git a/frontend/src/components/v2/ContentLoader/ContentLoader.tsx b/frontend/src/components/v2/ContentLoader/ContentLoader.tsx index af83ac81c..f43ee923c 100644 --- a/frontend/src/components/v2/ContentLoader/ContentLoader.tsx +++ b/frontend/src/components/v2/ContentLoader/ContentLoader.tsx @@ -13,7 +13,7 @@ export const ContentLoader = ({ text, frequency = 2000 }: Props) => { const [pos, setPos] = useState(0); const isTextArray = Array.isArray(text); useEffect(() => { - let interval: NodeJS.Timer; + let interval: NodeJS.Timeout; if (isTextArray) { interval = setInterval(() => { setPos((state) => (state + 1) % text.length); diff --git a/frontend/src/hooks/api/organization/queries.tsx b/frontend/src/hooks/api/organization/queries.tsx index 697daede5..1b075249c 100644 --- a/frontend/src/hooks/api/organization/queries.tsx +++ b/frontend/src/hooks/api/organization/queries.tsx @@ -2,11 +2,11 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { apiRequest } from "@app/config/request"; -import { +import { BillingDetails, Invoice, License, - Organization, + Organization, OrgPlanTable, PlanBillingInfo, PmtMethod, @@ -19,7 +19,8 @@ const organizationKeys = { getUserOrganizations: ["organization"] as const, getOrgPlanBillingInfo: (orgId: string) => [{ orgId }, "organization-plan-billing"] as const, getOrgPlanTable: (orgId: string) => [{ orgId }, "organization-plan-table"] as const, - getOrgPlansTable: (orgId: string, billingCycle: "monthly" | "yearly") => [{ orgId, billingCycle }, "organization-plans-table"] as const, + getOrgPlansTable: (orgId: string, billingCycle: "monthly" | "yearly") => + [{ orgId, billingCycle }, "organization-plans-table"] as const, getOrgBillingDetails: (orgId: string) => [{ orgId }, "organization-billing-details"] as const, getOrgPmtMethods: (orgId: string) => [{ orgId }, "organization-pmt-methods"] as const, getOrgTaxIds: (orgId: string) => [{ orgId }, "organization-tax-ids"] as const, @@ -28,34 +29,36 @@ const organizationKeys = { }; export const fetchOrganizations = async () => { - const { data: { organizations } } = await apiRequest.get<{ organizations: Organization[] }>("/api/v1/organization"); + const { + data: { organizations } + } = await apiRequest.get<{ organizations: Organization[] }>("/api/v1/organization"); return organizations; -} +}; export const useGetOrganizations = () => { - return useQuery({ - queryKey: organizationKeys.getUserOrganizations, + return useQuery({ + queryKey: organizationKeys.getUserOrganizations, queryFn: async () => { return fetchOrganizations(); } }); -} +}; export const useCreateOrg = () => { + const queryClient = useQueryClient(); + return useMutation({ - mutationFn: async ({ - name - }: { - name: string; - }) => { - const { data: { organization } } = await apiRequest.post( - "/api/v2/organizations", - { - name - } - ); + mutationFn: async ({ name }: { name: string }) => { + const { + data: { organization } + } = await apiRequest.post("/api/v2/organizations", { + name + }); return organization; + }, + onSuccess: () => { + queryClient.invalidateQueries(organizationKeys.getUserOrganizations); } }); }; @@ -75,17 +78,13 @@ export const useRenameOrg = () => { export const useGetOrgTrialUrl = () => { return useMutation({ - mutationFn: async ({ - orgId, - success_url - }: { - orgId: string; - success_url: string; - }) => { - const { data: { url } } = await apiRequest.post(`/api/v1/organizations/${orgId}/session/trial`, { + mutationFn: async ({ orgId, success_url }: { orgId: string; success_url: string }) => { + const { + data: { url } + } = await apiRequest.post(`/api/v1/organizations/${orgId}/session/trial`, { success_url - }) - + }); + return url; } }); @@ -99,11 +98,11 @@ export const useGetOrgPlanBillingInfo = (organizationId: string) => { `/api/v1/organizations/${organizationId}/plan/billing` ); - return data; + return data; }, enabled: true }); -} +}; export const useGetOrgPlanTable = (organizationId: string) => { return useQuery({ @@ -113,18 +112,18 @@ export const useGetOrgPlanTable = (organizationId: string) => { `/api/v1/organizations/${organizationId}/plan/table` ); - return data; + return data; }, enabled: true }); -} +}; export const useGetOrgPlansTable = ({ organizationId, billingCycle }: { organizationId: string; - billingCycle: "monthly" | "yearly" + billingCycle: "monthly" | "yearly"; }) => { return useQuery({ queryKey: organizationKeys.getOrgPlansTable(organizationId, billingCycle), @@ -133,11 +132,11 @@ export const useGetOrgPlansTable = ({ `/api/v1/organizations/${organizationId}/plans/table?billingCycle=${billingCycle}` ); - return data; + return data; }, enabled: true }); -} +}; export const useGetOrgBillingDetails = (organizationId: string) => { return useQuery({ @@ -151,7 +150,7 @@ export const useGetOrgBillingDetails = (organizationId: string) => { }, enabled: true }); -} +}; export const useUpdateOrgBillingDetails = () => { const queryClient = useQueryClient(); @@ -166,7 +165,7 @@ export const useUpdateOrgBillingDetails = () => { email?: string; }) => { const { data } = await apiRequest.patch( - `/api/v1/organizations/${organizationId}/billing-details`, + `/api/v1/organizations/${organizationId}/billing-details`, { name, email @@ -193,7 +192,7 @@ export const useGetOrgPmtMethods = (organizationId: string) => { }, enabled: true }); -} +}; export const useAddOrgPmtMethod = () => { const queryClient = useQueryClient(); @@ -208,8 +207,10 @@ export const useAddOrgPmtMethod = () => { success_url: string; cancel_url: string; }) => { - const { data: { url } } = await apiRequest.post( - `/api/v1/organizations/${organizationId}/billing-details/payment-methods`, + const { + data: { url } + } = await apiRequest.post( + `/api/v1/organizations/${organizationId}/billing-details/payment-methods`, { success_url, cancel_url @@ -230,7 +231,7 @@ export const useDeleteOrgPmtMethod = () => { return useMutation({ mutationFn: async ({ organizationId, - pmtMethodId, + pmtMethodId }: { organizationId: string; pmtMethodId: string; @@ -245,7 +246,7 @@ export const useDeleteOrgPmtMethod = () => { queryClient.invalidateQueries(organizationKeys.getOrgPmtMethods(dto.organizationId)); } }); -} +}; export const useGetOrgTaxIds = (organizationId: string) => { return useQuery({ @@ -259,7 +260,7 @@ export const useGetOrgTaxIds = (organizationId: string) => { }, enabled: true }); -} +}; export const useAddOrgTaxId = () => { const queryClient = useQueryClient(); @@ -275,7 +276,7 @@ export const useAddOrgTaxId = () => { value: string; }) => { const { data } = await apiRequest.post( - `/api/v1/organizations/${organizationId}/billing-details/tax-ids`, + `/api/v1/organizations/${organizationId}/billing-details/tax-ids`, { type, value @@ -294,13 +295,7 @@ export const useDeleteOrgTaxId = () => { const queryClient = useQueryClient(); return useMutation({ - mutationFn: async ({ - organizationId, - taxId, - }: { - organizationId: string; - taxId: string; - }) => { + mutationFn: async ({ organizationId, taxId }: { organizationId: string; taxId: string }) => { const { data } = await apiRequest.delete( `/api/v1/organizations/${organizationId}/billing-details/tax-ids/${taxId}` ); @@ -311,7 +306,7 @@ export const useDeleteOrgTaxId = () => { queryClient.invalidateQueries(organizationKeys.getOrgTaxIds(dto.organizationId)); } }); -} +}; export const useGetOrgInvoices = (organizationId: string) => { return useQuery({ @@ -325,7 +320,7 @@ export const useGetOrgInvoices = (organizationId: string) => { }, enabled: true }); -} +}; export const useCreateCustomerPortalSession = () => { return useMutation({ @@ -343,7 +338,7 @@ export const useGetOrgLicenses = (organizationId: string) => { queryKey: organizationKeys.getOrgLicenses(organizationId), queryFn: async () => { if (organizationId === "") return undefined; - + const { data } = await apiRequest.get( `/api/v1/organizations/${organizationId}/licenses` ); @@ -352,18 +347,16 @@ export const useGetOrgLicenses = (organizationId: string) => { }, enabled: true }); -} +}; export const useDeleteOrgById = () => { const queryClient = useQueryClient(); return useMutation({ - mutationFn: async ({ - organizationId, - }: { - organizationId: string; - }) => { - const { data: { organization } } = await apiRequest.delete<{ organization: Organization }>( + mutationFn: async ({ organizationId }: { organizationId: string }) => { + const { + data: { organization } + } = await apiRequest.delete<{ organization: Organization }>( `/api/v2/organizations/${organizationId}` ); return organization; @@ -372,8 +365,12 @@ export const useDeleteOrgById = () => { queryClient.invalidateQueries(organizationKeys.getUserOrganizations); queryClient.invalidateQueries(organizationKeys.getOrgPlanBillingInfo(dto.organizationId)); queryClient.invalidateQueries(organizationKeys.getOrgPlanTable(dto.organizationId)); - queryClient.invalidateQueries(organizationKeys.getOrgPlansTable(dto.organizationId, "monthly")); // You might need to invalidate for 'yearly' as well. - queryClient.invalidateQueries(organizationKeys.getOrgPlansTable(dto.organizationId, "yearly")); + queryClient.invalidateQueries( + organizationKeys.getOrgPlansTable(dto.organizationId, "monthly") + ); // You might need to invalidate for 'yearly' as well. + queryClient.invalidateQueries( + organizationKeys.getOrgPlansTable(dto.organizationId, "yearly") + ); queryClient.invalidateQueries(organizationKeys.getOrgBillingDetails(dto.organizationId)); queryClient.invalidateQueries(organizationKeys.getOrgPmtMethods(dto.organizationId)); queryClient.invalidateQueries(organizationKeys.getOrgTaxIds(dto.organizationId)); @@ -381,4 +378,4 @@ export const useDeleteOrgById = () => { queryClient.invalidateQueries(organizationKeys.getOrgLicenses(dto.organizationId)); } }); -} \ No newline at end of file +}; diff --git a/frontend/src/layouts/AppLayout/AppLayout.tsx b/frontend/src/layouts/AppLayout/AppLayout.tsx index fd4f469d3..04302548e 100644 --- a/frontend/src/layouts/AppLayout/AppLayout.tsx +++ b/frontend/src/layouts/AppLayout/AppLayout.tsx @@ -70,7 +70,9 @@ import { useGetUserAction, useLogoutUser, useRegisterUserAction, - useUploadWsKey} from "@app/hooks/api"; + useUploadWsKey +} from "@app/hooks/api"; +import { CreateOrgModal } from "@app/views/Org/components"; interface LayoutProps { children: React.ReactNode; @@ -114,12 +116,12 @@ export const AppLayout = ({ children }: LayoutProps) => { // eslint-disable-next-line prefer-const const { workspaces, currentWorkspace } = useWorkspace(); const { orgs, currentOrg } = useOrganization(); - + const { user } = useUser(); const { subscription } = useSubscription(); const workspaceId = currentWorkspace?._id || ""; const { data: updateClosed } = useGetUserAction("september_update_closed"); - + const { data: secretApprovalReqCount } = useGetSecretApprovalRequestCount({ workspaceId }); const isAddingProjectsAllowed = subscription?.workspaceLimit @@ -133,7 +135,8 @@ export const AppLayout = ({ children }: LayoutProps) => { const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ "addNewWs", - "upgradePlan" + "upgradePlan", + "createOrg" ] as const); const { control, @@ -150,7 +153,7 @@ export const AppLayout = ({ children }: LayoutProps) => { const closeUpdate = async () => { await registerUserAction.mutateAsync("september_update_closed"); - } + }; const logout = useLogoutUser(); const logOutUser = async () => { @@ -316,6 +319,22 @@ export const AppLayout = ({ children }: LayoutProps) => { ))} + + +
@@ -617,9 +650,10 @@ export const AppLayout = ({ children }: LayoutProps) => { href="https://infisical.com/blog/infisical-update-september-2023" target="_blank" rel="noopener noreferrer" - className="text-sm text-mineshaft-400 font-normal leading-[1.2rem] hover:text-mineshaft-100 duration-200" + className="text-sm font-normal leading-[1.2rem] text-mineshaft-400 duration-200 hover:text-mineshaft-100" > - Learn More + Learn More{" "} +
@@ -778,6 +812,10 @@ export const AppLayout = ({ children }: LayoutProps) => { onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)} text="You have exceeded the number of projects allowed on the free plan." /> + handlePopUpToggle("createOrg", false)} + />
{children}
diff --git a/frontend/src/views/Org/NonePage/NonePage.tsx b/frontend/src/views/Org/NonePage/NonePage.tsx index 6e21c302f..9d6b4a65c 100644 --- a/frontend/src/views/Org/NonePage/NonePage.tsx +++ b/frontend/src/views/Org/NonePage/NonePage.tsx @@ -1,114 +1,16 @@ -import { useEffect } from "react"; -import { Controller, useForm } from "react-hook-form"; -import { yupResolver } from "@hookform/resolvers/yup"; -import * as yup from "yup"; - -import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; -import { - Button, - FormControl, - Input, - Modal, - ModalContent} from "@app/components/v2"; -import { useCreateOrg } from "@app/hooks/api"; import { usePopUp } from "@app/hooks/usePopUp"; -const schema = yup.object({ - name: yup.string().required("Organization name is required"), -}).required(); - -export type FormData = yup.InferType; +import { CreateOrgModal } from "../components"; export const NonePage = () => { - const { createNotification } = useNotificationContext(); - const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([ - "createOrg", - ] as const); - - const { mutateAsync } = useCreateOrg(); - - const { - control, - handleSubmit, - reset, - formState: { isSubmitting } - } = useForm({ - resolver: yupResolver(schema), - defaultValues: { - name: "" - } - }); - - useEffect(() => { - handlePopUpOpen("createOrg"); - }, []); - - const onFormSubmit = async ({ name }: FormData) => { - try { - - const organization = await mutateAsync({ - name - }); - - localStorage.setItem("orgData.id", organization._id); + const { popUp, handlePopUpToggle } = usePopUp(["createOrg"] as const); - createNotification({ - text: "Successfully created organization", - type: "success" - }); - - window.location.href = `/org/${organization._id}/overview`; - - reset(); - handlePopUpToggle("createOrg", false); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to created organization", - type: "error" - }); - } - } - - return ( -
- - -
- ( - - - - )} - /> - - -
-
-
- ); -} \ No newline at end of file + return ( +
+ handlePopUpToggle("createOrg", false)} + /> +
+ ); +}; diff --git a/frontend/src/views/Org/components/CreateOrgModal.tsx b/frontend/src/views/Org/components/CreateOrgModal.tsx new file mode 100644 index 000000000..b62dfb9cb --- /dev/null +++ b/frontend/src/views/Org/components/CreateOrgModal.tsx @@ -0,0 +1,104 @@ +import { FC } from "react"; +import { Controller, useForm } from "react-hook-form"; +import { useRouter } from "next/router"; +import { zodResolver } from "@hookform/resolvers/zod"; +import z from "zod"; + +import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; +import { Button, FormControl, Input, Modal, ModalContent } from "@app/components/v2"; +import { useCreateOrg } from "@app/hooks/api"; + +const schema = z + .object({ + name: z.string().nonempty({ message: "Name is required" }) + }) + .required(); + +export type FormData = z.infer; + +interface CreateOrgModalProps { + isOpen: boolean; + onClose: () => void; +} + +export const CreateOrgModal: FC = ({ isOpen, onClose }) => { + const { createNotification } = useNotificationContext(); + const router = useRouter(); + + const { + control, + handleSubmit, + reset, + formState: { isSubmitting } + } = useForm({ + resolver: zodResolver(schema), + defaultValues: { + name: "" + } + }); + + const { mutateAsync } = useCreateOrg(); + + const onFormSubmit = async ({ name }: FormData) => { + try { + const organization = await mutateAsync({ + name + }); + + createNotification({ + text: "Successfully created organization", + type: "success" + }); + + if (router.isReady) router.push(`/org/${organization._id}/overview`); + else window.location.href = `/org/${organization._id}/overview`; + + localStorage.setItem("orgData.id", organization._id); + + reset(); + onClose(); + } catch (err) { + console.error(err); + createNotification({ + text: "Failed to created organization", + type: "error" + }); + } + }; + + return ( + + +
+ ( + + + + )} + /> +
+ + +
+ +
+
+ ); +}; diff --git a/frontend/src/views/Org/components/index.tsx b/frontend/src/views/Org/components/index.tsx new file mode 100644 index 000000000..7b794731a --- /dev/null +++ b/frontend/src/views/Org/components/index.tsx @@ -0,0 +1 @@ +export { CreateOrgModal } from "./CreateOrgModal"; diff --git a/frontend/src/views/SecretApprovalPage/components/SecretApprovalPolicyList/SecretApprovalPolicyList.tsx b/frontend/src/views/SecretApprovalPage/components/SecretApprovalPolicyList/SecretApprovalPolicyList.tsx index 76633aa28..0eb0a849d 100644 --- a/frontend/src/views/SecretApprovalPage/components/SecretApprovalPolicyList/SecretApprovalPolicyList.tsx +++ b/frontend/src/views/SecretApprovalPage/components/SecretApprovalPolicyList/SecretApprovalPolicyList.tsx @@ -164,7 +164,7 @@ export const SecretApprovalPolicyList = ({ workspaceId }: Props) => { handlePopUpToggle("upgradePlan", isOpen)} - text="You can add secret approval policy if you switch to Infisical's Team plan." + text="You can add secret approval policy if you switch to Infisical's Enterprise plan." /> ); diff --git a/frontend/src/views/SecretMainPage/components/SecretListView/SecretItem.tsx b/frontend/src/views/SecretMainPage/components/SecretListView/SecretItem.tsx index c420cdd07..9c92ae6b8 100644 --- a/frontend/src/views/SecretMainPage/components/SecretListView/SecretItem.tsx +++ b/frontend/src/views/SecretMainPage/components/SecretListView/SecretItem.tsx @@ -172,7 +172,11 @@ export const SecretItem = memo( const copyTokenToClipboard = () => { const [overrideValue, value] = getValues(["value", "valueOverride"]); - navigator.clipboard.writeText((overrideValue || value) as string); + if (isOverriden) { + navigator.clipboard.writeText(value as string); + } else { + navigator.clipboard.writeText(overrideValue as string); + } setIsSecValueCopied.on(); }; diff --git a/helm-charts/infisical/Chart.yaml b/helm-charts/infisical/Chart.yaml index 3be4eeb27..b8353410c 100644 --- a/helm-charts/infisical/Chart.yaml +++ b/helm-charts/infisical/Chart.yaml @@ -7,7 +7,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.4.1 +version: 0.4.2 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/helm-charts/infisical/templates/backend-deployment.yaml b/helm-charts/infisical/templates/backend-deployment.yaml index dced5f0a2..036e168a3 100644 --- a/helm-charts/infisical/templates/backend-deployment.yaml +++ b/helm-charts/infisical/templates/backend-deployment.yaml @@ -44,9 +44,9 @@ spec: envFrom: - secretRef: name: {{ $backend.kubeSecretRef | default (include "infisical.backend.fullname" .) }} - # {{- if $backend.resources }} - # resources: {{- toYaml $backend.resources | nindent 12 }} - # {{- end }} + {{- if $backend.resources }} + resources: {{- toYaml $backend.resources | nindent 12 }} + {{- end }} --- apiVersion: v1