diff --git a/README.md b/README.md index 7a958827c..8f03f9960 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ git commit activity - Cloudsmith downloads + Cloudsmith downloads Slack community channel @@ -46,6 +46,7 @@ [Portuguese - Brazil](i18n/README.pt-br.md) [Japanese language](i18n/README.ja.md) [Italian language](i18n/README.it.md) +[Hindi language](i18n/README.hi.md) **[Infisical](https://infisical.com)** is an open source, end-to-end encrypted secret manager which you can use to centralize your API keys and configs. From Infisical, you can then distribute these secrets across your whole development lifecycle - from development to production . It's designed to be simple and take minutes to get going. diff --git a/backend/src/controllers/v1/secretsFolderController.ts b/backend/src/controllers/v1/secretsFolderController.ts new file mode 100644 index 000000000..2e856c2a4 --- /dev/null +++ b/backend/src/controllers/v1/secretsFolderController.ts @@ -0,0 +1,89 @@ +import { Request, Response } from 'express'; +import { Secret } from '../../models'; +import Folder from '../../models/folder'; +import { BadRequestError } from '../../utils/errors'; +import { ROOT_FOLDER_PATH, getFolderPath, getParentPath, normalizePath, validateFolderName } from '../../utils/folder'; +import { ADMIN, MEMBER } from '../../variables'; +import { validateMembership } from '../../helpers/membership'; + +// TODO +// verify workspace id/environment +export const createFolder = async (req: Request, res: Response) => { + const { workspaceId, environment, folderName, parentFolderId } = req.body + if (!validateFolderName(folderName)) { + throw BadRequestError({ message: "Folder name cannot contain spaces. Only underscore and dashes" }) + } + + if (parentFolderId) { + const parentFolder = await Folder.find({ environment: environment, workspace: workspaceId, id: parentFolderId }); + if (!parentFolder) { + throw BadRequestError({ message: "The parent folder doesn't exist" }) + } + } + + let completePath = await getFolderPath(parentFolderId) + if (completePath == ROOT_FOLDER_PATH) { + completePath = "" + } + + const currentFolderPath = completePath + "/" + folderName // construct new path with current folder to be created + const normalizedCurrentPath = normalizePath(currentFolderPath) + const normalizedParentPath = getParentPath(normalizedCurrentPath) + + const existingFolder = await Folder.findOne({ + name: folderName, + workspace: workspaceId, + environment: environment, + parent: parentFolderId, + path: normalizedCurrentPath + }); + + if (existingFolder) { + return res.json(existingFolder) + } + + const newFolder = new Folder({ + name: folderName, + workspace: workspaceId, + environment: environment, + parent: parentFolderId, + path: normalizedCurrentPath, + parentPath: normalizedParentPath + }); + + await newFolder.save(); + + return res.json(newFolder) +} + +export const deleteFolder = async (req: Request, res: Response) => { + const { folderId } = req.params + const queue: any[] = [folderId]; + + const folder = await Folder.findById(folderId); + if (!folder) { + throw BadRequestError({ message: "The folder doesn't exist" }) + } + + // check that user is a member of the workspace + await validateMembership({ + userId: req.user._id.toString(), + workspaceId: folder.workspace as any, + acceptedRoles: [ADMIN, MEMBER] + }); + + while (queue.length > 0) { + const currentFolderId = queue.shift(); + + const childFolders = await Folder.find({ parent: currentFolderId }); + for (const childFolder of childFolders) { + queue.push(childFolder._id); + } + + await Secret.deleteMany({ folder: currentFolderId }); + + await Folder.deleteOne({ _id: currentFolderId }); + } + + res.send() +} \ No newline at end of file diff --git a/backend/src/models/folder.ts b/backend/src/models/folder.ts new file mode 100644 index 000000000..885e320a8 --- /dev/null +++ b/backend/src/models/folder.ts @@ -0,0 +1,36 @@ +import { Schema, Types, model } from 'mongoose'; + +const folderSchema = new Schema({ + name: { + type: String, + required: true, + }, + workspace: { + type: Schema.Types.ObjectId, + ref: 'Workspace', + required: true, + }, + environment: { + type: String, + required: true, + }, + parent: { + type: Schema.Types.ObjectId, + ref: 'Folder', + required: false, // optional for root folders + }, + path: { + type: String, + required: true + }, + parentPath: { + type: String, + required: true, + }, +}, { + timestamps: true +}); + +const Folder = model('Folder', folderSchema); + +export default Folder; \ No newline at end of file diff --git a/backend/src/utils/folder.ts b/backend/src/utils/folder.ts new file mode 100644 index 000000000..f12845339 --- /dev/null +++ b/backend/src/utils/folder.ts @@ -0,0 +1,87 @@ +import Folder from "../models/folder"; + +export const ROOT_FOLDER_PATH = "/" + +export const getFolderPath = async (folderId: string) => { + let currentFolder = await Folder.findById(folderId); + const pathSegments = []; + + while (currentFolder) { + pathSegments.unshift(currentFolder.name); + currentFolder = currentFolder.parent ? await Folder.findById(currentFolder.parent) : null; + } + + return '/' + pathSegments.join('/'); +}; + +/** + Returns the folder ID associated with the specified secret path in the given workspace and environment. + @param workspaceId - The ID of the workspace to search in. + @param environment - The environment to search in. + @param secretPath - The secret path to search for. + @returns The folder ID associated with the specified secret path, or undefined if the path is at the root folder level. + @throws Error if the specified secret path is not found. +*/ +export const getFolderIdFromPath = async (workspaceId: string, environment: string, secretPath: string) => { + const secretPathParts = secretPath.split("/").filter(path => path != "") + if (secretPathParts.length <= 1) { + return undefined // root folder, so no folder id + } + + const folderId = await Folder.find({ path: secretPath, workspace: workspaceId, environment: environment }) + if (!folderId) { + throw Error("Secret path not found") + } + + return folderId +} + +/** + * Cleans up a path by removing empty parts, duplicate slashes, + * and ensuring it starts with ROOT_FOLDER_PATH. + * @param path - The input path to clean up. + * @returns The cleaned-up path string. + */ +export const normalizePath = (path: string) => { + if (path == undefined || path == "" || path == ROOT_FOLDER_PATH) { + return ROOT_FOLDER_PATH + } + + const pathParts = path.split("/").filter(part => part != "") + const cleanPathString = ROOT_FOLDER_PATH + pathParts.join("/") + + return cleanPathString +} + +export const getFoldersInDirectory = async (workspaceId: string, environment: string, pathString: string) => { + const normalizedPath = normalizePath(pathString) + const foldersInDirectory = await Folder.find({ + workspace: workspaceId, + environment: environment, + parentPath: normalizedPath, + }); + + return foldersInDirectory; +} + +/** + * Returns the parent path of the given path. + * @param path - The input path. + * @returns The parent path string. + */ +export const getParentPath = (path: string) => { + const normalizedPath = normalizePath(path); + const folderParts = normalizedPath.split('/').filter(part => part !== ''); + + let folderParent = ROOT_FOLDER_PATH; + if (folderParts.length > 1) { + folderParent = ROOT_FOLDER_PATH + folderParts.slice(0, folderParts.length - 1).join('/'); + } + + return folderParent; +} + +export const validateFolderName = (folderName: string) => { + const validNameRegex = /^[a-zA-Z0-9-_]+$/; + return validNameRegex.test(folderName); +} \ No newline at end of file diff --git a/cli/packages/util/secrets.go b/cli/packages/util/secrets.go index e749e20c9..42a535b5a 100644 --- a/cli/packages/util/secrets.go +++ b/cli/packages/util/secrets.go @@ -131,7 +131,7 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters) ([]models isConnected := CheckIsConnectedToInternet() var secretsToReturn []models.SingleEnvironmentVariable - var serviceTokenDetails api.GetServiceTokenDetailsResponse + // var serviceTokenDetails api.GetServiceTokenDetailsResponse var errorToReturn error if infisicalToken == "" { @@ -183,11 +183,11 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters) ([]models } else { log.Debug("Trying to fetch secrets using service token") - secretsToReturn, serviceTokenDetails, errorToReturn = GetPlainTextSecretsViaServiceToken(infisicalToken) + secretsToReturn, _, errorToReturn = GetPlainTextSecretsViaServiceToken(infisicalToken) - if serviceTokenDetails.Environment != params.Environment { - PrintErrorMessageAndExit(fmt.Sprintf("Fetch secrets failed: token allows [%s] environment access, not [%s]. Service tokens are environment-specific; no need for --env flag.", params.Environment, serviceTokenDetails.Environment)) - } + // if serviceTokenDetails.Environment != params.Environment { + // PrintErrorMessageAndExit(fmt.Sprintf("Fetch secrets failed: token allows [%s] environment access, not [%s]. Service tokens are environment-specific; no need for --env flag.", params.Environment, serviceTokenDetails.Environment)) + // } } return secretsToReturn, errorToReturn diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 384f3a31d..75db3beb5 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,5 +1,5 @@ { - "name": "frontend", + "name": "npm-proj-1682405486465-0.42385611556033065msLhaJ", "lockfileVersion": 2, "requires": true, "packages": { @@ -75,7 +75,7 @@ "tweetnacl-util": "^0.15.1", "uuid": "^8.3.2", "uuidv4": "^6.2.13", - "yaml": "^2.2.0", + "yaml": "^2.2.2", "yup": "^0.32.11" }, "devDependencies": { @@ -22405,9 +22405,9 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/yaml": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.1.tgz", - "integrity": "sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz", + "integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==", "engines": { "node": ">= 14" } @@ -38856,9 +38856,9 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "yaml": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.1.tgz", - "integrity": "sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw==" + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz", + "integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==" }, "yargs": { "version": "16.2.0", diff --git a/frontend/package.json b/frontend/package.json index 0e1d3a907..f80df6819 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -82,7 +82,7 @@ "tweetnacl-util": "^0.15.1", "uuid": "^8.3.2", "uuidv4": "^6.2.13", - "yaml": "^2.2.0", + "yaml": "^2.2.2", "yup": "^0.32.11" }, "devDependencies": { diff --git a/frontend/src/pages/dashboard/[id].tsx b/frontend/src/pages/dashboard/[id].tsx index 418c038f5..449182ed8 100644 --- a/frontend/src/pages/dashboard/[id].tsx +++ b/frontend/src/pages/dashboard/[id].tsx @@ -803,8 +803,6 @@ export default function Dashboard() { isReadDenied: false }; - console.log(124, envSlug, selectedWorkspaceEnv) - if (selectedWorkspaceEnv) { if (snapshotData) setSelectedSnapshotEnv(selectedWorkspaceEnv); else setSelectedEnv(selectedWorkspaceEnv); diff --git a/frontend/src/views/DashboardPage/DashboardEnvOverview.tsx b/frontend/src/views/DashboardPage/DashboardEnvOverview.tsx index b71e0118f..f4efa8583 100644 --- a/frontend/src/views/DashboardPage/DashboardEnvOverview.tsx +++ b/frontend/src/views/DashboardPage/DashboardEnvOverview.tsx @@ -125,7 +125,7 @@ export const DashboardEnvOverview = ({onEnvChange}: {onEnvChange: any;}) => { if (isSecretsLoading || isEnvListLoading) { return ( -
+
loading animation
); @@ -234,14 +234,14 @@ export const DashboardEnvOverview = ({onEnvChange}: {onEnvChange: any;}) => {
*/} -
+
0
0
{userAvailableEnvs?.map(env => { - return
+ return
diff --git a/i18n/README.hi.md b/i18n/README.hi.md new file mode 100644 index 000000000..c9840b428 --- /dev/null +++ b/i18n/README.hi.md @@ -0,0 +1,380 @@ +

+ infisical + infisical +

+

+

आपकी टीम, उपकरणों और बुनियादी ढांचे में रहस्यों और कॉन्फ़िगरेशन को प्रबंधित करने के लिए ओपन-सोर्स, एंड-टू-एंड एन्क्रिप्टेड टूल।

+

+ +

+ स्लैक | + Infisical Cloud | + Self-Hosting | + डॉक्स | + वेबसाइट +

+ +

+ + Infisical is released under the MIT license. + + + PRs welcome! + + + git commit activity + + + Cloudsmith downloads + + + Slack community channel + + + Infisical Twitter + +

+ +Dashboard + +**इसे अन्य भाषाओं में पढ़ें**: [English language](i18n/README.en.md) +[Spanish language](i18n/README.es.md) +[German language](i18n/README.de.md) +[Korean language](i18n/README.ko.md) +[Turkish language](i18n/README.tr.md) +[Bahasa Indonesia language](i18n/README.id.md) +[Portuguese - Brazil](i18n/README.pt-br.md) +[Japanese language](i18n/README.ja.md) +[Italian language](i18n/README.it.md) +[Hindi language](i18n/README.hi.md) + +**[Infisical](https://infisical.com)** एक ओपन सोर्स, एंड-टू-एंड एन्क्रिप्टेड गुप्त प्रबंधक है जिसका उपयोग आप अपनी एपीआई कुंजी और कॉन्फ़िगरेशन को केंद्रीकृत करने के लिए कर सकते हैं। Infisical से, फिर आप इन रहस्यों को अपने संपूर्ण विकास जीवनचक्र में वितरित कर सकते हैं - विकास से लेकर उत्पादन तक। इसे सरल होने और चलने में कुछ मिनट लगने के लिए डिज़ाइन किया गया है. + +- **[उपयोगकर्ता के अनुकूल डैशबोर्ड](https://infisical.com/docs/getting-started/dashboard/project)** परियोजनाओं के भीतर अपनी टीम के रहस्यों और कॉन्फ़िगरेशन को प्रबंधित करने के लिए +- **[भाषा-अज्ञेयवादी सीएलआई](https://infisical.com/docs/cli/overview)** जो आपके स्थानीय कार्यप्रवाह में रहस्य और विन्यास को खींचता है और इंजेक्ट करता है +- **[अपने डेटा पर पूर्ण नियंत्रण](https://infisical.com/docs/self-hosting/overview)** - इसे किसी भी बुनियादी ढाँचे पर स्वयं होस्ट करें +- **एकाधिक वातावरण नेविगेट करें** प्रति परियोजना (जैसे विकास, मंचन, उत्पादन, आदि) +- **निजी ओवरराइड** रहस्य और कॉन्फ़िगरेशन के लिए +- **[एकीकरण](https://infisical.com/docs/integrations/overview)** सीआई/सीडी और उत्पादन बुनियादी ढांचे के साथ +- **[इंफिसिकल एपीआई](https://infisical.com/docs/api-reference/overview/introduction)** - प्लेटफ़ॉर्म पर HTTPS अनुरोधों के माध्यम से रहस्य प्रबंधित करें +- **[गुप्त संस्करण](https://infisical.com/docs/getting-started/dashboard/versioning)** किसी भी रहस्य के परिवर्तन इतिहास को देखने के लिए +- **[ऑडिट लॉग](https://infisical.com/docs/getting-started/dashboard/audit-logs)** एक परियोजना में की गई हर कार्रवाई को रिकॉर्ड करने के लिए +- **[Point-in-time Secrets Recovery](https://infisical.com/docs/getting-started/dashboard/pit-recovery)** पॉइंट-इन-टाइम सीक्रेट रिकवरी +- **भूमिका-आधारित अभिगम नियंत्रण** प्रति पर्यावरण +- **2FA** (अधिक विकल्प जल्द ही आ रहे हैं) +- **स्मार्ट सुरक्षा अलर्ट** +- 🔜 **1-क्लिक डिप्लॉय** टू एडब्ल्यूएस +- 🔜 **स्वचालित गुप्त रोटेशन** +- 🔜 **स्लैक और एमएस टीम्स** संयोजनाएँ + +और अधिक। + +## 🚀 शुरू हो जाओ? + +और ताकि आप त्वरित रूप से शुरू हो सकें, हमारे [शुरू हो जाओ गाइड] पर जाएं।(https://infisical.com/docs/getting-started/introduction). + +

+ + +

+ +## 🔥 इसके बारे में क्या अच्छा है? + +Infisical गुप्त प्रबंधन को सरल और डिफ़ॉल्ट रूप से एंड-टू-एंड एन्क्रिप्टेड बनाता है। हम इसे केवल सुरक्षा टीमों के लिए ही नहीं, सभी डेवलपरों के लिए अधिक सुलभ बनाने के मिशन पर हैं. + +एक के अनुसार [प्रतिवेदन](https://www.ekransystem.com/en/blog/secrets-management), कुछ हद तक डिजिटल रहस्यों का उपयोग करने के बावजूद केवल 10% संगठन गुप्त प्रबंधन समाधानों का उपयोग करते हैं। + +यदि आप कार्यकुशलता और सुरक्षा की परवाह करते हैं, तो Infisical आपके लिए सही है. + +फ़िलहाल हम Infisical को और व्यापक बनाने के लिए कड़ी मेहनत कर रहे हैं। किसी एकीकरण की आवश्यकता है या कोई नई सुविधा चाहिए? करने के लिए स्वतंत्र महसूस[एक मुद्दा बनाएँ](https://github.com/Infisical/infisical/issues) या [योगदान](https://infisical.com/docs/contributing/overview) सीधे रिपॉजिटरी में. + +## 🔌 एकीकरण + +वर्तमान में हम नींव और निर्माण कर रहे हैं[एकीकरण](https://infisical.com/docs/integrations/overview) इसलिए रहस्यों को हर जगह सिंक किया जा सकता है। किसी भी मदद का स्वागत है! :) + + + + + + + + + + +
प्लेटफार्म फ्रेमवर्क
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + ✔️ Docker + + + + ✔️ Docker Compose + + + + ✔️ Heroku + +
+ + ✔️ Vercel + + + + ✔️ Kubernetes + + + + ✔️ Fly.io + +
+ + ✔️ Supabase + + + + ✔️ GitHub Actions + + + + ✔️ Railway + +
+ 🔜 GCP SM (https://github.com/Infisical/infisical/issues/285) + + + ✔️ GitLab CI/CD + + + + ✔️ CircleCI + +
+ 🔜 Jenkins + + 🔜 Digital Ocean + + + ✔️ Azure Key Vault + +
+ + ✔️ Travis CI + + + + ✔️ AWS Secrets Manager + + + 🔜 Forge +
+ 🔜 Bitbucket + + + ✔️ AWS Parameter Store + + + + ✔️ Render + +
+ 🔜 BuddyCI + + 🔜 Serverless + + + ✔️ Netlify + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + ✔️ React + + + + ✔️ Express + +
+ + ✔️ Gatsby + + + + ✔️ Flask + +
+ + ✔️ Django + + + + ✔️ Laravel + +
+ + ✔️ NestJS + + + + ✔️ Remix + +
+ + ✔️ Next.js + + + + ✔️ Vite + +
+ + ✔️ Vue + + + + ✔️ Ruby on Rails + +
+ + ✔️ Fiber + + + + ✔️ Nuxt + +
+ + ✔️ .NET + + + And more... +
+ +
+ +## 💚 समुदाय का समर्थन + +- [स्लैक](https://join.slack.com/t/infisical-users/shared_invite/zt-1kdbk07ro-RtoyEt_9E~fyzGo_xQYP6g) (समुदाय और Infisical टीम के साथ लाइव चर्चा के लिए) +- [गिटहब चर्चाएँ](https://github.com/Infisical/infisical/discussions) (सुविधाओं के निर्माण और गहन बातचीत में मदद के लिए) +- [गिटहब मुद्दे](https://github.com/Infisical/infisical-cli/issues) (Infisical का उपयोग करके आपके सामने आने वाली किसी भी बग और त्रुटि के लिए) +- [ट्विटर](https://twitter.com/infisical) (समाचार तेजी से प्राप्त करें) + +## 🏘 ओपन-सोर्स बनाम पेड + +यह रिपो बिल्कुल MIT लाइसेंस से है, केवल `ee` निर्देशिका को छोड़कर, जिसमें भविष्य में एक इंफिसिकल लाइसेंस की आवश्यकता होगी जो प्रीमियम एंटरप्राइज सुविधाओं को समर्थित करेगा। हम वर्तमान में गैर-एंटरप्राइज प्रस्ताव विकसित करने पर केंद्रित हैं जो अधिकांश उपयोग मामलों के लिए उपयुक्त होने चाहिए। + +## 🛡 सुरक्षा + +सुरक्षा भेद्यता की रिपोर्ट करना चाहते हैं? कृपया इसके बारे में GitHub अंक में पोस्ट न करें। इसके बजाय, हमारी [SECURITY.md](./SECURITY.md) फ़ाइल देखें। + +## 🚨 अद्यतन रहना + +Infisical को आधिकारिक तौर पर 21 नवंबर, 2022 को v.1.0 के रूप में लॉन्च किया गया। बहुत सी नई सुविधाएँ बहुत बार आ रही हैं। भविष्य के अपडेट के बारे में सूचित करने के लिए इस संग्रह की **रिलीज़** देखें: + +![infisical-star-github](https://github.com/Infisical/infisical/blob/main/.github/images/star-infisical.gif?raw=true) + +## 🌱 योगदान देना + +चाहे वह बड़ा हो या छोटा, हमें योगदान पसंद है ❤️ कैसे [आरंभ करें](https://infisical.com/docs/contributing/overview) देखने के लिए हमारी मार्गदर्शिका देखें . + +सुनिश्चित नहीं हैं कि कहां से प्रारंभ करें? तुम कर सकते हो: + +- [हमारे एक साथी के साथ एक मुफ्त, गैर-दबाव जोड़ी सत्र बुक करें](mailto:tony@infisical.com?subject=Pairing%20session&body=I'd%20like%20to%20do%20a%20pairing%20session!)! +- हमारे साथ शामिल हों स्लैक पर, और वहां हमसे कोई प्रश्न पूछें. + +## 🦸 योगदानकर्ताओं + +[//]: contributor-faces + + + + + + + +## 🌎 अनुवाद + +Infisical वर्तमान में अंग्रेजी, कोरियाई, फ्रेंच, हिंदी और पुर्तगाली (ब्राजील) में उपलब्ध है। Infisical को अपनी भाषा में अनुवाद करने में हमारी मदद करें! + +आप में सभी जानकारी प्राप्त कर सकते हैं [यह मुद्दा](https://github.com/Infisical/infisical/issues/181).