From 3e102fee3d40dec8ea5e8c4df395686261083ba3 Mon Sep 17 00:00:00 2001 From: Gabriellopes232 Date: Fri, 6 Jan 2023 11:40:21 -0300 Subject: [PATCH 01/65] chore: add language locale --- frontend/next-i18next.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/next-i18next.config.js b/frontend/next-i18next.config.js index 5de9c97bc..f95f23c89 100644 --- a/frontend/next-i18next.config.js +++ b/frontend/next-i18next.config.js @@ -8,7 +8,7 @@ module.exports = { debug: process.env.NODE_ENV === "development", i18n: { defaultLocale: "en", - locales: ["en", "ko"], + locales: ["en", "ko", "pt-BR"], }, fallbackLng: { default: ["en"], From e6c631586a2c0bfcf2d95b5983a4b89a5c3fb7f1 Mon Sep 17 00:00:00 2001 From: Gabriellopes232 Date: Fri, 6 Jan 2023 11:40:51 -0300 Subject: [PATCH 02/65] refactor: add "pt-BR" option --- frontend/pages/login.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/pages/login.tsx b/frontend/pages/login.tsx index e72aa3b25..9addfb4cf 100644 --- a/frontend/pages/login.tsx +++ b/frontend/pages/login.tsx @@ -160,7 +160,7 @@ export default function Login() { From c0d7b4ea88318e12f5fdd9868035b594d6eff2db Mon Sep 17 00:00:00 2001 From: Gabriellopes232 Date: Fri, 6 Jan 2023 11:41:32 -0300 Subject: [PATCH 03/65] feat: add "boilerplate" json --- frontend/public/locales/pt-br/activity.json | 8 +++++ frontend/public/locales/pt-br/billing.json | 28 +++++++++++++++++ frontend/public/locales/pt-br/common.json | 26 ++++++++++++++++ frontend/public/locales/pt-br/dashboard.json | 30 +++++++++++++++++++ .../public/locales/pt-br/integrations.json | 16 ++++++++++ frontend/public/locales/pt-br/login.json | 8 +++++ frontend/public/locales/pt-br/nav.json | 22 ++++++++++++++ .../locales/pt-br/section-incident.json | 11 +++++++ .../public/locales/pt-br/section-members.json | 14 +++++++++ .../locales/pt-br/section-password.json | 11 +++++++ .../public/locales/pt-br/section-token.json | 13 ++++++++ .../locales/pt-br/settings-members.json | 4 +++ .../public/locales/pt-br/settings-org.json | 4 +++ .../locales/pt-br/settings-personal.json | 11 +++++++ .../locales/pt-br/settings-project.json | 13 ++++++++ frontend/public/locales/pt-br/signup.json | 21 +++++++++++++ 16 files changed, 240 insertions(+) create mode 100644 frontend/public/locales/pt-br/activity.json create mode 100644 frontend/public/locales/pt-br/billing.json create mode 100644 frontend/public/locales/pt-br/common.json create mode 100644 frontend/public/locales/pt-br/dashboard.json create mode 100644 frontend/public/locales/pt-br/integrations.json create mode 100644 frontend/public/locales/pt-br/login.json create mode 100644 frontend/public/locales/pt-br/nav.json create mode 100644 frontend/public/locales/pt-br/section-incident.json create mode 100644 frontend/public/locales/pt-br/section-members.json create mode 100644 frontend/public/locales/pt-br/section-password.json create mode 100644 frontend/public/locales/pt-br/section-token.json create mode 100644 frontend/public/locales/pt-br/settings-members.json create mode 100644 frontend/public/locales/pt-br/settings-org.json create mode 100644 frontend/public/locales/pt-br/settings-personal.json create mode 100644 frontend/public/locales/pt-br/settings-project.json create mode 100644 frontend/public/locales/pt-br/signup.json diff --git a/frontend/public/locales/pt-br/activity.json b/frontend/public/locales/pt-br/activity.json new file mode 100644 index 000000000..dfc5821ae --- /dev/null +++ b/frontend/public/locales/pt-br/activity.json @@ -0,0 +1,8 @@ +{ + "event": { + "readSecrets": "Segredos Visualizados", + "updateSecrets": "Segredos Atualizados", + "addSecrets": "Segredos Adicionados", + "deleteSecrets": "Segredos Excluídos" + } +} \ No newline at end of file diff --git a/frontend/public/locales/pt-br/billing.json b/frontend/public/locales/pt-br/billing.json new file mode 100644 index 000000000..92c963cfc --- /dev/null +++ b/frontend/public/locales/pt-br/billing.json @@ -0,0 +1,28 @@ +{ + "title": "Uso & Faturamento", + "description": "Visualize e gerencie a assinatura da sua organização aqui", + "subscription": "Inscrição", + "starter": { + "name": "Iniciante", + "price-explanation": "up to 5 team members", + "text": "Manage any project with 5 members for free!", + "subtext": "$5 per member/month afterwards." + }, + "professional": { + "name": "Professional", + "price-explanation": "/member/month", + "subtext": "Includes unlimited projects & members.", + "text": "Keep up with key management as you grow." + }, + "enterprise": { + "name": "Enterprise", + "text": "Keep up with key management as you grow." + }, + "current-usage": "Current Usage", + "free": "Free", + "downgrade": "Downgrade", + "upgrade": "Upgrade", + "learn-more": "Learn More", + "custom-pricing": "Custom Pricing", + "schedule-demo": "Schedule a Demo" +} \ No newline at end of file diff --git a/frontend/public/locales/pt-br/common.json b/frontend/public/locales/pt-br/common.json new file mode 100644 index 000000000..badb4e8ad --- /dev/null +++ b/frontend/public/locales/pt-br/common.json @@ -0,0 +1,26 @@ +{ + "head-title": "{{title}} | Infiscal", + "error_project-already-exists": "A project with this name already exists.", + "no-mobile": " To use Infisical, please log in through a device with larger dimensions. ", + "email": "Email", + "password": "Password", + "first-name": "First Name", + "last-name": "Last Name", + "logout": "Log Out", + "validate-required": "Please input your {{name}}", + "maintenance-alert": "We are experiencing minor technical difficulties. We are working on solving it right now. Please come back in a few minutes.", + "click-to-copy": "Click to copy", + "project-id": "Project ID", + "save-changes": "Save Changes", + "saved": "Saved", + "drop-zone": "Drag and drop your .env file here.", + "drop-zone-keys": "Drag and drop your .env file here to add more keys.", + "role": "Role", + "role_admin": "admin", + "display-name": "Display Name", + "environment": "Environment", + "expired-in": "Expires in", + "language": "Language", + "search": "Search...", + "note": "Note" +} diff --git a/frontend/public/locales/pt-br/dashboard.json b/frontend/public/locales/pt-br/dashboard.json new file mode 100644 index 000000000..b64fdbd64 --- /dev/null +++ b/frontend/public/locales/pt-br/dashboard.json @@ -0,0 +1,30 @@ +{ + "title": "Secrets", + "og-title": "Manage your .env files in seconds", + "og-description": "Infisical a simple end-to-end encrypted platform that enables teams to sync and manage their .env files.", + "search-keys": "Search keys...", + "add-key": "Add Key", + "personal": "Personal", + "personal-description": "Personal keys are only visible to you", + "shared": "Shared", + "shared-description": "Shared keys are visible to your whole team", + "make-shared": "Make Shared", + "make-personal": "Make Personal", + "check-docs": { + "button": "Check Docs", + "title": "Good job!", + "line1": "Congrats on adding more secrets.", + "line2": "Here is how to connect them to your codebase." + }, + "sidebar": { + "secret": "Secret", + "key": "Key", + "value": "Value", + "override": "Override value with a personal value", + "version-history": "Version History", + "comments": "Comments & Notes", + "personal-explanation": "This secret is personal. It is not shared with any of your teammates.", + "generate-random-hex": "Generate Random Hex", + "digits": "digits" + } +} diff --git a/frontend/public/locales/pt-br/integrations.json b/frontend/public/locales/pt-br/integrations.json new file mode 100644 index 000000000..b72f9d87b --- /dev/null +++ b/frontend/public/locales/pt-br/integrations.json @@ -0,0 +1,16 @@ +{ + "title": "Project Integrations", + "description": "Manage your integrations of Infisical with third-party services.", + "no-integrations1": "You don't have any integrations set up yet. When you do, they will appear here.", + "no-integrations2": "To start, click on any of the options below. It takes 5 clicks to set up.", + "available": "Platform & Cloud Integrations", + "available-text1": "Click on the itegration you want to connect. This will let your environment variables flow automatically into selected third-party services.", + "available-text2": "Note: during an integration with Heroku, for security reasons, it is impossible to maintain end-to-end encryption. In theory, this lets Infisical decrypt yor environment variables. In practice, we can assure you that this will never be done, and it allows us to protect your secrets from bad actors online. The core Infisical service will always stay end-to-end encrypted. With any questions, reach out support@infisical.com.", + "cloud-integrations": "Cloud Integrations", + "framework-integrations": "Framework Integrations", + "click-to-start": "Click on an integration to begin syncing secrets to it.", + "click-to-setup": "Click on a framework to get the setup instructions.", + "grant-access-to-secrets": "Grant Infisical access to your secrets", + "why-infisical-needs-access": "Most cloud integrations require Infisical to be able to decrypt your secrets so they can be forwarded over.", + "grant-access-button": "Grant access" +} diff --git a/frontend/public/locales/pt-br/login.json b/frontend/public/locales/pt-br/login.json new file mode 100644 index 000000000..ee097fd92 --- /dev/null +++ b/frontend/public/locales/pt-br/login.json @@ -0,0 +1,8 @@ +{ + "title": "Login", + "og-title": "Log In to Infisical", + "og-description": "Infisical a simple end-to-end encrypted platform that enables teams to sync and manage their .env files.", + "login": "Log In", + "need-account": "Need an Infisical account?", + "create-account": "Create an account" +} diff --git a/frontend/public/locales/pt-br/nav.json b/frontend/public/locales/pt-br/nav.json new file mode 100644 index 000000000..de5218619 --- /dev/null +++ b/frontend/public/locales/pt-br/nav.json @@ -0,0 +1,22 @@ +{ + "support": { + "slack": "[NEW] Join Slack Forum", + "docs": "Read Docs", + "issue": "Open a Github Issue", + "email": "Send us an Email" + }, + "user": { + "signed-in-as": "SIGNED IN AS", + "current-organization": "CURRENT ORGANIZATION", + "usage-billing": "Usage & Billing", + "invite": "Invite Members", + "other-organizations": "OTHER ORGANIZATION" + }, + "menu": { + "project": "PROJECT", + "secrets": "Secrets", + "members": "Members", + "integrations": "Integrations", + "project-settings": "Project Settings" + } +} diff --git a/frontend/public/locales/pt-br/section-incident.json b/frontend/public/locales/pt-br/section-incident.json new file mode 100644 index 000000000..de8f3e7b7 --- /dev/null +++ b/frontend/public/locales/pt-br/section-incident.json @@ -0,0 +1,11 @@ +{ + "incident-contacts": "Incident Contacts", + "incident-contacts-description": "These contacts will be notified in the unlikely event of a severe incident.", + "no-incident-contacts": "No incident contacts found.", + "add-contact": "Add Contact", + "add-dialog": { + "title": "Add an Incident Contact", + "description": "This contact will be notified in the unlikely event of a severe incident.", + "add-incident": "Add Incident Contact" + } +} diff --git a/frontend/public/locales/pt-br/section-members.json b/frontend/public/locales/pt-br/section-members.json new file mode 100644 index 000000000..89dbd2fea --- /dev/null +++ b/frontend/public/locales/pt-br/section-members.json @@ -0,0 +1,14 @@ +{ + "add-member": "Add Member", + "org-members": "Organization Members", + "org-members-description": "Manage members of your organization. These users could afterwards be formed into projects.", + "search-members": "Search members...", + "add-dialog": { + "add-member-to-project": "Add a member to your project", + "already-all-invited": "All the users in your organization are already invited.", + "add-user-org-first": "Add more users to the organization first.", + "user-will-email": "The user will receive an email with the instructions.", + "looking-add": "<0>If you are looking to add users to your org,<1>click here", + "add-user-to-org": "Add Users to Organization" + } +} diff --git a/frontend/public/locales/pt-br/section-password.json b/frontend/public/locales/pt-br/section-password.json new file mode 100644 index 000000000..9c0410d6c --- /dev/null +++ b/frontend/public/locales/pt-br/section-password.json @@ -0,0 +1,11 @@ +{ + "password": "Password", + "change": "Change password", + "current": "Current password", + "current-wrong": "The current password may be wrong", + "new": "New password", + "validate-base": "Password should contain at least:", + "validate-length": "14 characters", + "validate-case": "1 lowercase character", + "validate-number": "1 number" +} diff --git a/frontend/public/locales/pt-br/section-token.json b/frontend/public/locales/pt-br/section-token.json new file mode 100644 index 000000000..d12bd202c --- /dev/null +++ b/frontend/public/locales/pt-br/section-token.json @@ -0,0 +1,13 @@ +{ + "service-tokens": "Service Tokens", + "service-tokens-description": "Every service token is specific to you, a certain project and a certain environment within this project.", + "add-new": "Add New Token", + "add-dialog": { + "title": "Add a service token for {{target}}", + "description": "Specify the name, environment, and expiry period. When a token is generated, you will only be able to see it once before it disappears. Make sure to save it somewhere.", + "name": "Service Token Name", + "add": "Add Service Token", + "copy-service-token": "Copy your service token", + "copy-service-token-description": "Once you close this popup, you will never see your service token again" + } +} diff --git a/frontend/public/locales/pt-br/settings-members.json b/frontend/public/locales/pt-br/settings-members.json new file mode 100644 index 000000000..91f4686e7 --- /dev/null +++ b/frontend/public/locales/pt-br/settings-members.json @@ -0,0 +1,4 @@ +{ + "title": "Project Members", + "description": "This pages shows the members of the selected project." +} diff --git a/frontend/public/locales/pt-br/settings-org.json b/frontend/public/locales/pt-br/settings-org.json new file mode 100644 index 000000000..d17baa7cd --- /dev/null +++ b/frontend/public/locales/pt-br/settings-org.json @@ -0,0 +1,4 @@ +{ + "title": "Organization Settings", + "description": "Manage members of your organization. These users could afterwards be formed into projects." +} diff --git a/frontend/public/locales/pt-br/settings-personal.json b/frontend/public/locales/pt-br/settings-personal.json new file mode 100644 index 000000000..8759c20f0 --- /dev/null +++ b/frontend/public/locales/pt-br/settings-personal.json @@ -0,0 +1,11 @@ +{ + "title": "Personal Settings", + "description": "View and manage your personal information here.", + "emergency": { + "name": "Emergency Kit", + "text1": "Your Emergency Kit contains the information you’ll need to sign in to your Infisical account.", + "text2": "Only the latest issued Emergency Kit remains valid. To get a new Emergency Kit, verify your password.", + "download": "Download Emergency Kit" + }, + "change-language": "Change Language" +} diff --git a/frontend/public/locales/pt-br/settings-project.json b/frontend/public/locales/pt-br/settings-project.json new file mode 100644 index 000000000..03e4b1459 --- /dev/null +++ b/frontend/public/locales/pt-br/settings-project.json @@ -0,0 +1,13 @@ +{ + "title": "Project Settings", + "description": "These settings only apply to the currently selected Project.", + "danger-zone": "Danger Zone", + "delete-project": "Delete Project", + "project-to-delete": "Project to be Deleted", + "danger-zone-note": "As soon as you delete this project, you will not be able to undo it. This will immediately remove all the keys. If you still want to do that, please enter the name of the project below.", + "delete-project-note": "Note: You can only delete a project in case you have more than one", + "project-id-description": "To integrate Infisical into your code base and get automatic injection of environmental variables, you should use the following Project ID.", + "project-id-description2": "For more guidance, including code snipets for various languages and frameworks, see ", + "auto-generated": "This is your project's auto-generated unique identifier. It can't be changed.", + "docs": "Infisical Docs" +} diff --git a/frontend/public/locales/pt-br/signup.json b/frontend/public/locales/pt-br/signup.json new file mode 100644 index 000000000..509520d56 --- /dev/null +++ b/frontend/public/locales/pt-br/signup.json @@ -0,0 +1,21 @@ +{ + "title": "Sign Up", + "og-title": "Replace .env files with 1 line of code. Sign Up for Infisical in 3 minutes.", + "og-description": "Infisical a simple end-to-end encrypted platform that enables teams to sync and manage API-keys and environemntal variables. Works with Node.js, Next.js, Gatsby, Nest.js...", + "signup": "Sign Up", + "already-have-account": "Have an account? Log in", + "forgot-password": "Forgot your password?", + "verify": "Verify", + "step1-start": "Let's get started", + "step1-privacy": "By creating an account, you agree to our Terms and have read and acknowledged the Privacy Policy.", + "step1-submit": "Get Started", + "step2-message": "We've sent a verification email to{{email}}", + "step2-code-error": "Oops. Your code is wrong. Please try again.", + "step2-spam-alert": "Make sure to check your spam inbox.", + "step3-message": "Almost there!", + "step4-message": "Save your Emergency Kit", + "step4-description1": "If you get locked out of your account, your Emergency Kit is the only way to sign in.", + "step4-description2": "We recommend you download it and keep it somewhere safe.", + "step4-description3": "It contains your Secret Key which we cannot access or recover for you if you lose it.", + "step4-download": "Download PDF" +} From 4c32f3dfd0b3bbdabda87605ce29f839e3a9cee8 Mon Sep 17 00:00:00 2001 From: Gabriellopes232 Date: Fri, 6 Jan 2023 12:08:20 -0300 Subject: [PATCH 04/65] refactor: adding translate pt-br billing archive --- frontend/public/locales/pt-br/billing.json | 32 +++++++++++----------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/frontend/public/locales/pt-br/billing.json b/frontend/public/locales/pt-br/billing.json index 92c963cfc..1c920c053 100644 --- a/frontend/public/locales/pt-br/billing.json +++ b/frontend/public/locales/pt-br/billing.json @@ -4,25 +4,25 @@ "subscription": "Inscrição", "starter": { "name": "Iniciante", - "price-explanation": "up to 5 team members", - "text": "Manage any project with 5 members for free!", - "subtext": "$5 per member/month afterwards." + "price-explanation": "Até 5 membros da equipe", + "text": "Gerencie qualquer projeto com 5 membros gratuitamente!", + "subtext": "$5 por membro / mês depois." }, "professional": { - "name": "Professional", - "price-explanation": "/member/month", - "subtext": "Includes unlimited projects & members.", - "text": "Keep up with key management as you grow." + "name": "Profissional", + "price-explanation": "/membro/mês", + "subtext": "Inclui projetos e membros ilimitados.", + "text": "Acompanhe o gerenciamento de chaves à medida que você cresce." }, "enterprise": { - "name": "Enterprise", - "text": "Keep up with key management as you grow." + "name": "Empreendimento", + "text": "Acompanhe o gerenciamento de chaves à medida que você cresce." }, - "current-usage": "Current Usage", - "free": "Free", - "downgrade": "Downgrade", - "upgrade": "Upgrade", - "learn-more": "Learn More", - "custom-pricing": "Custom Pricing", - "schedule-demo": "Schedule a Demo" + "current-usage": "Uso atual", + "free": "Grátis", + "downgrade": "Reduzir", + "upgrade": "Melhoria", + "learn-more": "Saber Mais", + "custom-pricing": "Preço Personalizado", + "schedule-demo": "Agende uma Demonstração" } \ No newline at end of file From 15553e972a458f74c62fc0db5b26e534297a830b Mon Sep 17 00:00:00 2001 From: Gabriellopes232 Date: Sat, 7 Jan 2023 12:09:43 -0300 Subject: [PATCH 05/65] refactor: adding translate pt-br in common.json archive --- frontend/public/locales/pt-br/common.json | 40 +++++++++++------------ 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/frontend/public/locales/pt-br/common.json b/frontend/public/locales/pt-br/common.json index badb4e8ad..12e657d7c 100644 --- a/frontend/public/locales/pt-br/common.json +++ b/frontend/public/locales/pt-br/common.json @@ -1,26 +1,26 @@ { "head-title": "{{title}} | Infiscal", - "error_project-already-exists": "A project with this name already exists.", - "no-mobile": " To use Infisical, please log in through a device with larger dimensions. ", + "error_project-already-exists": "Já exite um projeto com este nome.", + "no-mobile": "Para usar o Infisical, faça o login através de um dispositivo com dimensões maiores.", "email": "Email", - "password": "Password", - "first-name": "First Name", - "last-name": "Last Name", - "logout": "Log Out", - "validate-required": "Please input your {{name}}", - "maintenance-alert": "We are experiencing minor technical difficulties. We are working on solving it right now. Please come back in a few minutes.", - "click-to-copy": "Click to copy", - "project-id": "Project ID", - "save-changes": "Save Changes", - "saved": "Saved", - "drop-zone": "Drag and drop your .env file here.", - "drop-zone-keys": "Drag and drop your .env file here to add more keys.", + "password": "Senha", + "first-name": "Primeiro Nome", + "last-name": "Ultimo Nome", + "logout": "Sair", + "validate-required": "Por favor insira o seu {{name}}", + "maintenance-alert": "Estamos passando por pequenas dificuldades técnicas. Estamos trabalhando para resolvê-lo agora. Por favor, volte em alguns minutos.", + "click-to-copy": "Clique para copiar", + "project-id": "ID do Projeto", + "save-changes": "Salvar Alterações", + "saved": "Salvou", + "drop-zone": "Arraste e solte seu arquivo .env aqui.", + "drop-zone-keys": "Arraste e solte seu arquivo .env aqui para adicionar mais chaves.", "role": "Role", "role_admin": "admin", - "display-name": "Display Name", - "environment": "Environment", - "expired-in": "Expires in", - "language": "Language", - "search": "Search...", + "display-name": "Nome de exibição", + "environment": "Ambiente", + "expired-in": "Expira em", + "language": "Linguagem", + "search": "Procurar...", "note": "Note" -} +} \ No newline at end of file From 3e56fe95d20051602c7f35eb484a45c422b86091 Mon Sep 17 00:00:00 2001 From: Gabriellopes232 Date: Sat, 7 Jan 2023 13:50:51 -0300 Subject: [PATCH 06/65] refactor: adding translate pt-br in dashboard.json archive --- frontend/public/locales/pt-br/dashboard.json | 50 ++++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/frontend/public/locales/pt-br/dashboard.json b/frontend/public/locales/pt-br/dashboard.json index b64fdbd64..3edeacc89 100644 --- a/frontend/public/locales/pt-br/dashboard.json +++ b/frontend/public/locales/pt-br/dashboard.json @@ -1,30 +1,30 @@ { - "title": "Secrets", - "og-title": "Manage your .env files in seconds", - "og-description": "Infisical a simple end-to-end encrypted platform that enables teams to sync and manage their .env files.", - "search-keys": "Search keys...", - "add-key": "Add Key", - "personal": "Personal", - "personal-description": "Personal keys are only visible to you", - "shared": "Shared", - "shared-description": "Shared keys are visible to your whole team", - "make-shared": "Make Shared", - "make-personal": "Make Personal", + "title": "Segredos", + "og-title": "Gerencie seus arquivos .env em segundos", + "og-description": "Infisical é uma plataforma simples e criptografada de ponta a ponta que permite que as equipes sincronizem e gerenciem seus arquivos .env.", + "search-keys": "Pesquisar chaves...", + "add-key": "Adicionar Chave", + "personal": "Pessoal", + "personal-description": "As chaves pessoais são visíveis apenas para você", + "shared": "Compartilhado", + "shared-description": "As chaves compartilhadas ficam visíveis para toda a sua equipe", + "make-shared": "Tornar Compartilhado", + "make-personal": "Tornar individual", "check-docs": { - "button": "Check Docs", - "title": "Good job!", - "line1": "Congrats on adding more secrets.", - "line2": "Here is how to connect them to your codebase." + "button": "Checkar Documentação", + "title": "Bom trabalho!!", + "line1": "Parabéns por adicionar mais segredos.", + "line2": "Veja como conectá-los à sua base de código." }, "sidebar": { - "secret": "Secret", - "key": "Key", - "value": "Value", - "override": "Override value with a personal value", - "version-history": "Version History", - "comments": "Comments & Notes", - "personal-explanation": "This secret is personal. It is not shared with any of your teammates.", - "generate-random-hex": "Generate Random Hex", - "digits": "digits" + "secret": "Segredo", + "key": "Chave", + "value": "Valor", + "override": "Substitua o valor por um valor pessoal", + "version-history": "Histórico da versão", + "comments": "Comentários e Notas", + "personal-explanation": "Este segredo é pessoal. Não é compartilhado com nenhum de seus colegas de equipe.", + "generate-random-hex": "Gerar Hex Aleatório", + "digits": "Digitos" } -} +} \ No newline at end of file From 3f1eaa8d42d31f1fd2cb67421f9fd9d3ed33bd00 Mon Sep 17 00:00:00 2001 From: Gabriellopes232 Date: Sat, 7 Jan 2023 13:51:04 -0300 Subject: [PATCH 07/65] refactor: adding translate pt-br in integrations.json archive --- .../public/locales/pt-br/integrations.json | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/frontend/public/locales/pt-br/integrations.json b/frontend/public/locales/pt-br/integrations.json index b72f9d87b..3e1533582 100644 --- a/frontend/public/locales/pt-br/integrations.json +++ b/frontend/public/locales/pt-br/integrations.json @@ -1,16 +1,16 @@ { - "title": "Project Integrations", - "description": "Manage your integrations of Infisical with third-party services.", - "no-integrations1": "You don't have any integrations set up yet. When you do, they will appear here.", - "no-integrations2": "To start, click on any of the options below. It takes 5 clicks to set up.", - "available": "Platform & Cloud Integrations", - "available-text1": "Click on the itegration you want to connect. This will let your environment variables flow automatically into selected third-party services.", - "available-text2": "Note: during an integration with Heroku, for security reasons, it is impossible to maintain end-to-end encryption. In theory, this lets Infisical decrypt yor environment variables. In practice, we can assure you that this will never be done, and it allows us to protect your secrets from bad actors online. The core Infisical service will always stay end-to-end encrypted. With any questions, reach out support@infisical.com.", - "cloud-integrations": "Cloud Integrations", - "framework-integrations": "Framework Integrations", - "click-to-start": "Click on an integration to begin syncing secrets to it.", - "click-to-setup": "Click on a framework to get the setup instructions.", - "grant-access-to-secrets": "Grant Infisical access to your secrets", - "why-infisical-needs-access": "Most cloud integrations require Infisical to be able to decrypt your secrets so they can be forwarded over.", - "grant-access-button": "Grant access" -} + "title": "Integrações de Projetos", + "description": "Gerencie suas integrações da Infisical com serviços de terceiros.", + "no-integrations1": "Você ainda não tem integrações configuradas. Quando você fizer isso, eles aparecerão aqui.", + "no-integrations2": "Para começar, clique em qualquer uma das opções abaixo. Leva 5 cliques para configurar.", + "available": "Integrações de Plataforma e Nuvem", + "available-text1": "Clique na integração que deseja conectar. Isso permitirá que suas variáveis de ambiente fluam automaticamente para serviços de terceiros selecionados.", + "available-text2": "Observação: durante uma integração com o Heroku, por questões de segurança, é impossível manter a criptografia de ponta a ponta. Em teoria, isso permite que o Infisical descriptografe suas variáveis de ambiente. Na prática, podemos garantir que isso nunca será feito e nos permite proteger seus segredos de pessoas mal-intencionadas online. O serviço básico da Infisical sempre permanecerá criptografado de ponta a ponta. Em caso de dúvidas, entre em contato com support@infisical.com.", + "cloud-integrations": "Integrações na Nuvem", + "framework-integrations": "Integrações de framework", + "click-to-start": "Clique em uma integração para começar a sincronizar segredos com ela.", + "click-to-setup": "Clique em uma estrutura para obter as instruções de configuração.", + "grant-access-to-secrets": "Conceda acesso Infisical aos seus segredos", + "why-infisical-needs-access": "A maioria das integrações em nuvem exige que o Infisical seja capaz de descriptografar seus segredos para que possam ser encaminhados.", + "grant-access-button": "Garantir acesso" +} \ No newline at end of file From 96b254d7c3e707718b6468d18e302437ab369b4e Mon Sep 17 00:00:00 2001 From: Gabriellopes232 Date: Sat, 7 Jan 2023 13:51:16 -0300 Subject: [PATCH 08/65] refactor: adding translate pt-br in login.json archive --- frontend/public/locales/pt-br/login.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/public/locales/pt-br/login.json b/frontend/public/locales/pt-br/login.json index ee097fd92..1798839a0 100644 --- a/frontend/public/locales/pt-br/login.json +++ b/frontend/public/locales/pt-br/login.json @@ -1,8 +1,8 @@ { "title": "Login", - "og-title": "Log In to Infisical", - "og-description": "Infisical a simple end-to-end encrypted platform that enables teams to sync and manage their .env files.", - "login": "Log In", - "need-account": "Need an Infisical account?", - "create-account": "Create an account" -} + "og-title": "Entrar no Infisical", + "og-description": "Infisical é uma plataforma simples e criptografada de ponta a ponta que permite que as equipes sincronizem e gerenciem seus arquivos .env.", + "login": "Entrar", + "need-account": "Precisa de uma conta Infisical?", + "create-account": "Criar uma conta" +} \ No newline at end of file From 553be71ddfa5d469204716a31ca6561c04581074 Mon Sep 17 00:00:00 2001 From: Gabriellopes232 Date: Sun, 8 Jan 2023 09:23:17 -0300 Subject: [PATCH 09/65] refactor: translate pt-br in login.json archive --- frontend/public/locales/pt-br/login.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/public/locales/pt-br/login.json b/frontend/public/locales/pt-br/login.json index 1798839a0..9510d1a22 100644 --- a/frontend/public/locales/pt-br/login.json +++ b/frontend/public/locales/pt-br/login.json @@ -1,5 +1,5 @@ { - "title": "Login", + "title": "Entrar", "og-title": "Entrar no Infisical", "og-description": "Infisical é uma plataforma simples e criptografada de ponta a ponta que permite que as equipes sincronizem e gerenciem seus arquivos .env.", "login": "Entrar", From 2b147fce6eea8a081e48c9d7df2787b8ddbd5c8f Mon Sep 17 00:00:00 2001 From: Gabriellopes232 Date: Sun, 8 Jan 2023 09:23:31 -0300 Subject: [PATCH 10/65] refactor: adding translate pt-br in nav.json archive --- frontend/public/locales/pt-br/nav.json | 30 +++++++++++++------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/frontend/public/locales/pt-br/nav.json b/frontend/public/locales/pt-br/nav.json index de5218619..7922312dd 100644 --- a/frontend/public/locales/pt-br/nav.json +++ b/frontend/public/locales/pt-br/nav.json @@ -1,22 +1,22 @@ { "support": { - "slack": "[NEW] Join Slack Forum", - "docs": "Read Docs", - "issue": "Open a Github Issue", - "email": "Send us an Email" + "slack": "[NEW] Participe do fórum do Slack", + "docs": "Leia a Documentação", + "issue": "Abra uma Issue no Github", + "email": "Envie-nos um e-mail" }, "user": { - "signed-in-as": "SIGNED IN AS", - "current-organization": "CURRENT ORGANIZATION", - "usage-billing": "Usage & Billing", - "invite": "Invite Members", - "other-organizations": "OTHER ORGANIZATION" + "signed-in-as": "ASSINADO COMO", + "current-organization": "ORGANIZAÇÃO ATUAL", + "usage-billing": "Uso & Faturamento", + "invite": "Convide Membros", + "other-organizations": "OUTRA ORGANIZAÇÃO" }, "menu": { - "project": "PROJECT", - "secrets": "Secrets", - "members": "Members", - "integrations": "Integrations", - "project-settings": "Project Settings" + "project": "PROJETO", + "secrets": "Segredos", + "members": "Membros", + "integrations": "Integrações", + "project-settings": "Configurações do Projeto" } -} +} \ No newline at end of file From 556858d1a862ae472694d712d56f62e40559b673 Mon Sep 17 00:00:00 2001 From: Gabriellopes232 Date: Sun, 8 Jan 2023 09:24:12 -0300 Subject: [PATCH 11/65] refactor: adding translate pt-br in section-incident.json archive --- .../public/locales/pt-br/section-incident.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/frontend/public/locales/pt-br/section-incident.json b/frontend/public/locales/pt-br/section-incident.json index de8f3e7b7..bfa55d2ee 100644 --- a/frontend/public/locales/pt-br/section-incident.json +++ b/frontend/public/locales/pt-br/section-incident.json @@ -1,11 +1,11 @@ { - "incident-contacts": "Incident Contacts", - "incident-contacts-description": "These contacts will be notified in the unlikely event of a severe incident.", - "no-incident-contacts": "No incident contacts found.", - "add-contact": "Add Contact", + "incident-contacts": "Contatos do Incidente", + "incident-contacts-description": "Esses contatos serão notificados no caso improvável de um incidente grave.", + "no-incident-contacts": "Nenhum contato de incidente encontrado.", + "add-contact": "Adicionar contato", "add-dialog": { - "title": "Add an Incident Contact", - "description": "This contact will be notified in the unlikely event of a severe incident.", - "add-incident": "Add Incident Contact" + "title": "Adicionar um contato de incidente", + "description": "Este contato será notificado no caso improvável de um incidente grave.", + "add-incident": "Adicionar contato de incidente" } -} +} \ No newline at end of file From 8c0046be879a26a6f99040e87807d8fe0ea1eb99 Mon Sep 17 00:00:00 2001 From: Gabriellopes232 Date: Sun, 8 Jan 2023 09:24:29 -0300 Subject: [PATCH 12/65] refactor: adding translate pt-br in section-members.json archive --- .../public/locales/pt-br/section-members.json | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/frontend/public/locales/pt-br/section-members.json b/frontend/public/locales/pt-br/section-members.json index 89dbd2fea..67ef10ff0 100644 --- a/frontend/public/locales/pt-br/section-members.json +++ b/frontend/public/locales/pt-br/section-members.json @@ -1,14 +1,14 @@ { - "add-member": "Add Member", - "org-members": "Organization Members", - "org-members-description": "Manage members of your organization. These users could afterwards be formed into projects.", - "search-members": "Search members...", + "add-member": "Adicionar membro", + "org-members": "Membros da Organização", + "org-members-description": "Gerencie os membros da sua organização. Esses usuários poderiam posteriormente ser formados em projetos.", + "search-members": "Pesquisar membros...", "add-dialog": { - "add-member-to-project": "Add a member to your project", - "already-all-invited": "All the users in your organization are already invited.", - "add-user-org-first": "Add more users to the organization first.", - "user-will-email": "The user will receive an email with the instructions.", - "looking-add": "<0>If you are looking to add users to your org,<1>click here", - "add-user-to-org": "Add Users to Organization" + "add-member-to-project": "Adicionar um membro ao seu projeto", + "already-all-invited": "Todos os usuários da sua organização já foram convidados.", + "add-user-org-first": "Adicione mais usuários à organização primeiro.", + "user-will-email": "O usuário receberá um e-mail com as instruções.", + "looking-add": "<0>Se você deseja adicionar usuários à sua organização,<1>clique aqui", + "add-user-to-org": "Adicionar usuários à organização" } -} +} \ No newline at end of file From f2372bb26535ca32b2f6b566cfe8942453362337 Mon Sep 17 00:00:00 2001 From: Gabriellopes232 Date: Sun, 8 Jan 2023 09:24:44 -0300 Subject: [PATCH 13/65] refactor: adding translate pt-br in section-password.json archive --- .../locales/pt-br/section-password.json | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/frontend/public/locales/pt-br/section-password.json b/frontend/public/locales/pt-br/section-password.json index 9c0410d6c..771581768 100644 --- a/frontend/public/locales/pt-br/section-password.json +++ b/frontend/public/locales/pt-br/section-password.json @@ -1,11 +1,11 @@ { - "password": "Password", - "change": "Change password", - "current": "Current password", - "current-wrong": "The current password may be wrong", - "new": "New password", - "validate-base": "Password should contain at least:", - "validate-length": "14 characters", - "validate-case": "1 lowercase character", - "validate-number": "1 number" -} + "password": "Senha", + "change": "Mudar senha", + "current": "Senha atual", + "current-wrong": "A senha atual pode estar errada", + "new": "Nova Senha", + "validate-base": "A senha deve conter pelo menos:", + "validate-length": "14 caracteres", + "validate-case": "1 caractere minúsculo", + "validate-number": "1 número" +} \ No newline at end of file From a990a5ee7dde6119b917501c7bc654e983574a56 Mon Sep 17 00:00:00 2001 From: Gabriellopes232 Date: Sun, 8 Jan 2023 09:24:55 -0300 Subject: [PATCH 14/65] refactor: adding translate pt-br in section-token.json archive --- .../public/locales/pt-br/section-token.json | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/frontend/public/locales/pt-br/section-token.json b/frontend/public/locales/pt-br/section-token.json index d12bd202c..1713f8118 100644 --- a/frontend/public/locales/pt-br/section-token.json +++ b/frontend/public/locales/pt-br/section-token.json @@ -1,13 +1,13 @@ { - "service-tokens": "Service Tokens", - "service-tokens-description": "Every service token is specific to you, a certain project and a certain environment within this project.", - "add-new": "Add New Token", + "service-tokens": "Tokens de Serviço", + "service-tokens-description": "Cada token de serviço é específico para você, um determinado projeto e um determinado ambiente dentro deste projeto.", + "add-new": "Adicionar novo token", "add-dialog": { - "title": "Add a service token for {{target}}", - "description": "Specify the name, environment, and expiry period. When a token is generated, you will only be able to see it once before it disappears. Make sure to save it somewhere.", - "name": "Service Token Name", - "add": "Add Service Token", - "copy-service-token": "Copy your service token", - "copy-service-token-description": "Once you close this popup, you will never see your service token again" + "title": "Adicione um token de serviço para {{target}}", + "description": "Especifique o nome, o ambiente e o período de expiração. Quando um token é gerado, você só poderá vê-lo uma vez antes que ele desapareça. Certifique-se de salvá-lo em algum lugar.", + "name": "Nome do token de serviço", + "add": "Adicionar token de serviço", + "copy-service-token": "Copie seu token de serviço", + "copy-service-token-description": "Depois de fechar este pop-up, você nunca mais verá seu token de serviço" } -} +} \ No newline at end of file From 36525325fd5bbc60929c6dc027cfefe28990b76e Mon Sep 17 00:00:00 2001 From: Gabriellopes232 Date: Sun, 8 Jan 2023 09:25:16 -0300 Subject: [PATCH 15/65] refactor: adding translate pt-br in setting-members.json archive --- frontend/public/locales/pt-br/settings-members.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/public/locales/pt-br/settings-members.json b/frontend/public/locales/pt-br/settings-members.json index 91f4686e7..f48913547 100644 --- a/frontend/public/locales/pt-br/settings-members.json +++ b/frontend/public/locales/pt-br/settings-members.json @@ -1,4 +1,4 @@ { - "title": "Project Members", - "description": "This pages shows the members of the selected project." -} + "title": "Membros do Projeto", + "description": "Esta página mostra os membros do projeto selecionado." +} \ No newline at end of file From 2f5186634c7b36154533634ec87d8c2b976ea6da Mon Sep 17 00:00:00 2001 From: Gabriellopes232 Date: Sun, 8 Jan 2023 09:25:27 -0300 Subject: [PATCH 16/65] refactor: adding translate pt-br in setting-org.json archive --- frontend/public/locales/pt-br/settings-org.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/public/locales/pt-br/settings-org.json b/frontend/public/locales/pt-br/settings-org.json index d17baa7cd..e017c2aee 100644 --- a/frontend/public/locales/pt-br/settings-org.json +++ b/frontend/public/locales/pt-br/settings-org.json @@ -1,4 +1,4 @@ { - "title": "Organization Settings", - "description": "Manage members of your organization. These users could afterwards be formed into projects." -} + "title": "Configurações da organização", + "description": "Gerencie os membros da sua organização. Esses usuários poderiam posteriormente ser formados em projetos." +} \ No newline at end of file From c0a0252cf5e4bb49a95ba6595dfad29fc960e3c0 Mon Sep 17 00:00:00 2001 From: Gabriellopes232 Date: Sun, 8 Jan 2023 09:25:38 -0300 Subject: [PATCH 17/65] refactor: adding translate pt-br in setting-personal.json archive --- .../public/locales/pt-br/settings-personal.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/frontend/public/locales/pt-br/settings-personal.json b/frontend/public/locales/pt-br/settings-personal.json index 8759c20f0..9d5dd7f50 100644 --- a/frontend/public/locales/pt-br/settings-personal.json +++ b/frontend/public/locales/pt-br/settings-personal.json @@ -1,11 +1,11 @@ { - "title": "Personal Settings", - "description": "View and manage your personal information here.", + "title": "Configurações Pessoais", + "description": "Visualize e gerencie suas informações pessoais aqui.", "emergency": { - "name": "Emergency Kit", - "text1": "Your Emergency Kit contains the information you’ll need to sign in to your Infisical account.", - "text2": "Only the latest issued Emergency Kit remains valid. To get a new Emergency Kit, verify your password.", - "download": "Download Emergency Kit" + "name": "Kit de emergência", + "text1": "Seu Kit de Emergência contém as informações necessárias para acessar sua conta Infisical.", + "text2": "Apenas o último kit de emergência emitido permanece válido. Para obter um novo Kit de emergência, verifique sua senha.", + "download": "Baixe o kit de emergência" }, - "change-language": "Change Language" -} + "change-language": "Mudar idioma" +} \ No newline at end of file From d870ecc62ae29525f46c647399a8eab761e08bb2 Mon Sep 17 00:00:00 2001 From: Gabriellopes232 Date: Sun, 8 Jan 2023 09:25:52 -0300 Subject: [PATCH 18/65] refactor: adding translate pt-br in setting-project.json archive --- .../locales/pt-br/settings-project.json | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/frontend/public/locales/pt-br/settings-project.json b/frontend/public/locales/pt-br/settings-project.json index 03e4b1459..f25af8fd4 100644 --- a/frontend/public/locales/pt-br/settings-project.json +++ b/frontend/public/locales/pt-br/settings-project.json @@ -1,13 +1,13 @@ { - "title": "Project Settings", - "description": "These settings only apply to the currently selected Project.", - "danger-zone": "Danger Zone", - "delete-project": "Delete Project", - "project-to-delete": "Project to be Deleted", - "danger-zone-note": "As soon as you delete this project, you will not be able to undo it. This will immediately remove all the keys. If you still want to do that, please enter the name of the project below.", - "delete-project-note": "Note: You can only delete a project in case you have more than one", - "project-id-description": "To integrate Infisical into your code base and get automatic injection of environmental variables, you should use the following Project ID.", - "project-id-description2": "For more guidance, including code snipets for various languages and frameworks, see ", - "auto-generated": "This is your project's auto-generated unique identifier. It can't be changed.", - "docs": "Infisical Docs" -} + "title": "Configurações do Projeto", + "description": "Essas configurações se aplicam apenas ao projeto atualmente selecionado.", + "danger-zone": "Zona de perigo", + "delete-project": "Excluir projeto", + "project-to-delete": "Projeto a ser deletado", + "danger-zone-note": "Assim que você excluir este projeto, não poderá desfazê-lo. Isso removerá imediatamente todas as chaves. Se você ainda quiser fazer isso, digite o nome do projeto abaixo.", + "delete-project-note": "Observação: você só pode excluir um projeto caso tenha mais de um", + "project-id-description": "Para integrar Infisical em sua base de código e obter injeção automática de variáveis ambientais, você deve usar o seguinte ID do projeto.", + "project-id-description2": "Para obter mais orientações, incluindo trechos de código para várias linguagens e estruturas, consulte", + "auto-generated": "Este é o identificador exclusivo gerado automaticamente do seu projeto. Não pode ser alterado.", + "docs": "Documentação do Infisical" +} \ No newline at end of file From 69b819e7c4fb49ef812aca1a43975b94b41bc13c Mon Sep 17 00:00:00 2001 From: Gabriellopes232 Date: Sun, 8 Jan 2023 09:26:04 -0300 Subject: [PATCH 19/65] refactor: adding translate pt-br in signup.json archive --- frontend/public/locales/pt-br/signup.json | 40 +++++++++++------------ 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/frontend/public/locales/pt-br/signup.json b/frontend/public/locales/pt-br/signup.json index 509520d56..b86ac1eff 100644 --- a/frontend/public/locales/pt-br/signup.json +++ b/frontend/public/locales/pt-br/signup.json @@ -1,21 +1,21 @@ { - "title": "Sign Up", - "og-title": "Replace .env files with 1 line of code. Sign Up for Infisical in 3 minutes.", - "og-description": "Infisical a simple end-to-end encrypted platform that enables teams to sync and manage API-keys and environemntal variables. Works with Node.js, Next.js, Gatsby, Nest.js...", - "signup": "Sign Up", - "already-have-account": "Have an account? Log in", - "forgot-password": "Forgot your password?", - "verify": "Verify", - "step1-start": "Let's get started", - "step1-privacy": "By creating an account, you agree to our Terms and have read and acknowledged the Privacy Policy.", - "step1-submit": "Get Started", - "step2-message": "We've sent a verification email to{{email}}", - "step2-code-error": "Oops. Your code is wrong. Please try again.", - "step2-spam-alert": "Make sure to check your spam inbox.", - "step3-message": "Almost there!", - "step4-message": "Save your Emergency Kit", - "step4-description1": "If you get locked out of your account, your Emergency Kit is the only way to sign in.", - "step4-description2": "We recommend you download it and keep it somewhere safe.", - "step4-description3": "It contains your Secret Key which we cannot access or recover for you if you lose it.", - "step4-download": "Download PDF" -} + "title": "Inscrever-se", + "og-title": "Substitua os arquivos .env por 1 linha de código. Cadastre-se no Infisical em 3 minutos.", + "og-description": "Infisical é uma plataforma criptografada de ponta a ponta simples que permite que as equipes sincronizem e gerenciem chaves de API e variáveis ambientais. Funciona com Node.js, Next.js, Gatsby, Nest.js...", + "signup": "Inscrever-se", + "already-have-account": "Possui uma conta? Conecte-se", + "forgot-password": "Esqueceu sua senha?", + "verify": "Verificar", + "step1-start": "Vamos começar", + "step1-privacy": "Ao criar uma conta, você concorda com nossos Termos e leu e reconheceu a Política de Privacidade.", + "step1-submit": "Iniciar", + "step2-message": "Enviamos um e-mail de verificação para{{email}}", + "step2-code-error": "Ops. Seu código está errado. Por favor, tente novamente.", + "step2-spam-alert": "Certifique-se de verificar sua caixa de entrada de spam.", + "step3-message": "Quase lá!", + "step4-message": "Guarde o seu Kit de Emergência", + "step4-description1": "Se sua conta for bloqueada, seu Kit de emergência é a única maneira de fazer login.", + "step4-description2": "Recomendamos que você faça o download e guarde-o em algum lugar seguro.", + "step4-description3": "Ele contém sua chave secreta que não podemos acessar ou recuperar para você se você a perder.", + "step4-download": "Baixar PDF" +} \ No newline at end of file From 0bf8661350436b9540d33cfec8a4d2419d37c47f Mon Sep 17 00:00:00 2001 From: Gabriellopes232 Date: Sun, 8 Jan 2023 16:09:57 -0300 Subject: [PATCH 20/65] fix: pattern folder based on i18next locales --- frontend/public/locales/{pt-br => pt-BR}/activity.json | 0 frontend/public/locales/{pt-br => pt-BR}/billing.json | 0 frontend/public/locales/{pt-br => pt-BR}/common.json | 0 frontend/public/locales/{pt-br => pt-BR}/dashboard.json | 0 frontend/public/locales/{pt-br => pt-BR}/integrations.json | 0 frontend/public/locales/{pt-br => pt-BR}/login.json | 0 frontend/public/locales/{pt-br => pt-BR}/nav.json | 0 frontend/public/locales/{pt-br => pt-BR}/section-incident.json | 0 frontend/public/locales/{pt-br => pt-BR}/section-members.json | 0 frontend/public/locales/{pt-br => pt-BR}/section-password.json | 0 frontend/public/locales/{pt-br => pt-BR}/section-token.json | 0 frontend/public/locales/{pt-br => pt-BR}/settings-members.json | 0 frontend/public/locales/{pt-br => pt-BR}/settings-org.json | 0 frontend/public/locales/{pt-br => pt-BR}/settings-personal.json | 0 frontend/public/locales/{pt-br => pt-BR}/settings-project.json | 0 frontend/public/locales/{pt-br => pt-BR}/signup.json | 0 16 files changed, 0 insertions(+), 0 deletions(-) rename frontend/public/locales/{pt-br => pt-BR}/activity.json (100%) rename frontend/public/locales/{pt-br => pt-BR}/billing.json (100%) rename frontend/public/locales/{pt-br => pt-BR}/common.json (100%) rename frontend/public/locales/{pt-br => pt-BR}/dashboard.json (100%) rename frontend/public/locales/{pt-br => pt-BR}/integrations.json (100%) rename frontend/public/locales/{pt-br => pt-BR}/login.json (100%) rename frontend/public/locales/{pt-br => pt-BR}/nav.json (100%) rename frontend/public/locales/{pt-br => pt-BR}/section-incident.json (100%) rename frontend/public/locales/{pt-br => pt-BR}/section-members.json (100%) rename frontend/public/locales/{pt-br => pt-BR}/section-password.json (100%) rename frontend/public/locales/{pt-br => pt-BR}/section-token.json (100%) rename frontend/public/locales/{pt-br => pt-BR}/settings-members.json (100%) rename frontend/public/locales/{pt-br => pt-BR}/settings-org.json (100%) rename frontend/public/locales/{pt-br => pt-BR}/settings-personal.json (100%) rename frontend/public/locales/{pt-br => pt-BR}/settings-project.json (100%) rename frontend/public/locales/{pt-br => pt-BR}/signup.json (100%) diff --git a/frontend/public/locales/pt-br/activity.json b/frontend/public/locales/pt-BR/activity.json similarity index 100% rename from frontend/public/locales/pt-br/activity.json rename to frontend/public/locales/pt-BR/activity.json diff --git a/frontend/public/locales/pt-br/billing.json b/frontend/public/locales/pt-BR/billing.json similarity index 100% rename from frontend/public/locales/pt-br/billing.json rename to frontend/public/locales/pt-BR/billing.json diff --git a/frontend/public/locales/pt-br/common.json b/frontend/public/locales/pt-BR/common.json similarity index 100% rename from frontend/public/locales/pt-br/common.json rename to frontend/public/locales/pt-BR/common.json diff --git a/frontend/public/locales/pt-br/dashboard.json b/frontend/public/locales/pt-BR/dashboard.json similarity index 100% rename from frontend/public/locales/pt-br/dashboard.json rename to frontend/public/locales/pt-BR/dashboard.json diff --git a/frontend/public/locales/pt-br/integrations.json b/frontend/public/locales/pt-BR/integrations.json similarity index 100% rename from frontend/public/locales/pt-br/integrations.json rename to frontend/public/locales/pt-BR/integrations.json diff --git a/frontend/public/locales/pt-br/login.json b/frontend/public/locales/pt-BR/login.json similarity index 100% rename from frontend/public/locales/pt-br/login.json rename to frontend/public/locales/pt-BR/login.json diff --git a/frontend/public/locales/pt-br/nav.json b/frontend/public/locales/pt-BR/nav.json similarity index 100% rename from frontend/public/locales/pt-br/nav.json rename to frontend/public/locales/pt-BR/nav.json diff --git a/frontend/public/locales/pt-br/section-incident.json b/frontend/public/locales/pt-BR/section-incident.json similarity index 100% rename from frontend/public/locales/pt-br/section-incident.json rename to frontend/public/locales/pt-BR/section-incident.json diff --git a/frontend/public/locales/pt-br/section-members.json b/frontend/public/locales/pt-BR/section-members.json similarity index 100% rename from frontend/public/locales/pt-br/section-members.json rename to frontend/public/locales/pt-BR/section-members.json diff --git a/frontend/public/locales/pt-br/section-password.json b/frontend/public/locales/pt-BR/section-password.json similarity index 100% rename from frontend/public/locales/pt-br/section-password.json rename to frontend/public/locales/pt-BR/section-password.json diff --git a/frontend/public/locales/pt-br/section-token.json b/frontend/public/locales/pt-BR/section-token.json similarity index 100% rename from frontend/public/locales/pt-br/section-token.json rename to frontend/public/locales/pt-BR/section-token.json diff --git a/frontend/public/locales/pt-br/settings-members.json b/frontend/public/locales/pt-BR/settings-members.json similarity index 100% rename from frontend/public/locales/pt-br/settings-members.json rename to frontend/public/locales/pt-BR/settings-members.json diff --git a/frontend/public/locales/pt-br/settings-org.json b/frontend/public/locales/pt-BR/settings-org.json similarity index 100% rename from frontend/public/locales/pt-br/settings-org.json rename to frontend/public/locales/pt-BR/settings-org.json diff --git a/frontend/public/locales/pt-br/settings-personal.json b/frontend/public/locales/pt-BR/settings-personal.json similarity index 100% rename from frontend/public/locales/pt-br/settings-personal.json rename to frontend/public/locales/pt-BR/settings-personal.json diff --git a/frontend/public/locales/pt-br/settings-project.json b/frontend/public/locales/pt-BR/settings-project.json similarity index 100% rename from frontend/public/locales/pt-br/settings-project.json rename to frontend/public/locales/pt-BR/settings-project.json diff --git a/frontend/public/locales/pt-br/signup.json b/frontend/public/locales/pt-BR/signup.json similarity index 100% rename from frontend/public/locales/pt-br/signup.json rename to frontend/public/locales/pt-BR/signup.json From 9ff0b7bc1885e2d05e64bda2947903154488cb26 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Wed, 11 Jan 2023 10:03:40 +0700 Subject: [PATCH 21/65] Minor changes to README --- README.md | 10 +- .../endpoints/secrets/create.mdx | 4 + .../endpoints/secrets/delete.mdx | 4 + docs/api-reference/endpoints/secrets/read.mdx | 4 + .../endpoints/secrets/update.mdx | 4 + .../api-reference/overview/authentication.mdx | 3 + docs/api-reference/overview/introduction.mdx | 3 + docs/mint.json | 26 + docs/spec.yaml | 1690 +++++++++++++++++ 9 files changed, 1743 insertions(+), 5 deletions(-) create mode 100644 docs/api-reference/endpoints/secrets/create.mdx create mode 100644 docs/api-reference/endpoints/secrets/delete.mdx create mode 100644 docs/api-reference/endpoints/secrets/read.mdx create mode 100644 docs/api-reference/endpoints/secrets/update.mdx create mode 100644 docs/api-reference/overview/authentication.mdx create mode 100644 docs/api-reference/overview/introduction.mdx create mode 100644 docs/spec.yaml diff --git a/README.md b/README.md index aaafca43a..0312669b7 100644 --- a/README.md +++ b/README.md @@ -42,9 +42,9 @@ - **Navigate Multiple Environments** per project (e.g. development, staging, production, etc.) - **Personal overrides** for environment variables - **[Integrations](https://infisical.com/docs/integrations/overview)** with CI/CD and production infrastructure -- **[Secret Versioning](https://infisical.com/docs/getting-started/dashboard/versioning)** - check the history of change for any secret -- **[Activity Logs](https://infisical.com/docs/getting-started/dashboard/audit-logs)** - check what user in the project is performing what actions with secrets -- **[Point-in-time Secrets Recovery](https://infisical.com/docs/getting-started/dashboard/pit-recovery)** - roll back to any snapshot of you secrets +- **[Secret Versioning](https://infisical.com/docs/getting-started/dashboard/versioning)** to view the change history for any secret +- **[Activity Logs](https://infisical.com/docs/getting-started/dashboard/audit-logs)** to record every action taken in a project. +- **[Point-in-time Secrets Recovery](https://infisical.com/docs/getting-started/dashboard/pit-recovery)** for rolling back to any snapshot of your secrets - 🔜 **1-Click Deploy** to Digital Ocean and Heroku - 🔜 **Authentication/Authorization** for projects (read/write controls soon) - 🔜 **Automatic Secret Rotation** @@ -337,6 +337,6 @@ Infisical officially launched as v.1.0 on November 21st, 2022. There are a lot o ## 🌎 Translations -Infisical is currently aviable in English and Korean. Help us translate Infisical to your language! +Infisical is currently aviable in English and Korean. Help us translate Infisical to your language! -You can find all the info in [this issue](https://github.com/Infisical/infisical/issues/181). \ No newline at end of file +You can find all the info in [this issue](https://github.com/Infisical/infisical/issues/181). diff --git a/docs/api-reference/endpoints/secrets/create.mdx b/docs/api-reference/endpoints/secrets/create.mdx new file mode 100644 index 000000000..27c167f76 --- /dev/null +++ b/docs/api-reference/endpoints/secrets/create.mdx @@ -0,0 +1,4 @@ +--- +title: "Create" +openapi: "POST /api/v2/secrets/" +--- diff --git a/docs/api-reference/endpoints/secrets/delete.mdx b/docs/api-reference/endpoints/secrets/delete.mdx new file mode 100644 index 000000000..af87e7120 --- /dev/null +++ b/docs/api-reference/endpoints/secrets/delete.mdx @@ -0,0 +1,4 @@ +--- +title: "Delete" +openapi: "DELETE /api/v2/secrets/" +--- diff --git a/docs/api-reference/endpoints/secrets/read.mdx b/docs/api-reference/endpoints/secrets/read.mdx new file mode 100644 index 000000000..4305f192c --- /dev/null +++ b/docs/api-reference/endpoints/secrets/read.mdx @@ -0,0 +1,4 @@ +--- +title: "Read" +openapi: "GET /api/v2/secrets/" +--- diff --git a/docs/api-reference/endpoints/secrets/update.mdx b/docs/api-reference/endpoints/secrets/update.mdx new file mode 100644 index 000000000..2193fccc3 --- /dev/null +++ b/docs/api-reference/endpoints/secrets/update.mdx @@ -0,0 +1,4 @@ +--- +title: "Update" +openapi: "PATCH /api/v2/secrets/" +--- diff --git a/docs/api-reference/overview/authentication.mdx b/docs/api-reference/overview/authentication.mdx new file mode 100644 index 000000000..27a2dc134 --- /dev/null +++ b/docs/api-reference/overview/authentication.mdx @@ -0,0 +1,3 @@ +--- +title: "Authentication" +--- diff --git a/docs/api-reference/overview/introduction.mdx b/docs/api-reference/overview/introduction.mdx new file mode 100644 index 000000000..9632e3788 --- /dev/null +++ b/docs/api-reference/overview/introduction.mdx @@ -0,0 +1,3 @@ +--- +title: "Introduction" +--- diff --git a/docs/mint.json b/docs/mint.json index 4b83b5c17..363b27a3e 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -39,6 +39,11 @@ "icon": "server", "url": "self-hosting" }, + { + "name": "API Reference", + "icon": "cloud", + "url": "api-reference" + }, { "name": "Integrations", "icon": "plug", @@ -125,6 +130,27 @@ "self-hosting/configuration/email" ] }, + { + "group": "Overview", + "pages": [ + "api-reference/overview/introduction", + "api-reference/overview/authentication" + ] + }, + { + "group": "Endpoints", + "pages": [ + { + "group": "Secrets", + "pages": [ + "api-reference/endpoints/secrets/create", + "api-reference/endpoints/secrets/read", + "api-reference/endpoints/secrets/update", + "api-reference/endpoints/secrets/delete" + ] + } + ] + }, { "group": "Integrations", "pages": ["integrations/overview"] diff --git a/docs/spec.yaml b/docs/spec.yaml new file mode 100644 index 000000000..a0271395b --- /dev/null +++ b/docs/spec.yaml @@ -0,0 +1,1690 @@ +openapi: 3.0.0 +info: + title: Infisical API + description: List of all available APIs that can be consumed + version: 1.0.0 +servers: + - url: http://localhost:8080 +paths: + /api/v1/secret/{secretId}/secret-versions: + get: + description: '' + parameters: + - name: secretId + in: path + required: true + schema: + type: string + - name: offset + in: query + schema: + type: string + - name: limit + in: query + schema: + type: string + responses: + '200': + description: OK + '400': + description: Bad Request + /api/v1/secret/{secretId}/secret-versions/rollback: + post: + description: '' + parameters: + - name: secretId + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + '400': + description: Bad Request + requestBody: + content: + application/json: + schema: + type: object + properties: + version: + example: any + /api/v1/secret-snapshot/{secretSnapshotId}: + get: + description: '' + parameters: + - name: secretSnapshotId + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + '400': + description: Bad Request + /api/v1/workspace/{workspaceId}/secret-snapshots: + get: + description: '' + parameters: + - name: workspaceId + in: path + required: true + schema: + type: string + - name: offset + in: query + schema: + type: string + - name: limit + in: query + schema: + type: string + responses: + '200': + description: OK + '400': + description: Bad Request + /api/v1/workspace/{workspaceId}/secret-snapshots/count: + get: + description: '' + parameters: + - name: workspaceId + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + '400': + description: Bad Request + /api/v1/workspace/{workspaceId}/secret-snapshots/rollback: + post: + description: '' + parameters: + - name: workspaceId + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + '400': + description: Bad Request + requestBody: + content: + application/json: + schema: + type: object + properties: + version: + example: any + /api/v1/workspace/{workspaceId}/logs: + get: + description: '' + parameters: + - name: workspaceId + in: path + required: true + schema: + type: string + - name: offset + in: query + schema: + type: string + - name: limit + in: query + schema: + type: string + - name: sortBy + in: query + schema: + type: string + - name: userId + in: query + schema: + type: string + - name: actionNames + in: query + schema: + type: string + responses: + '200': + description: OK + '400': + description: Bad Request + /api/v1/action/{actionId}: + get: + description: '' + parameters: + - name: actionId + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + /api/v1/signup/email/signup: + post: + description: '' + parameters: [] + responses: + '200': + description: OK + '400': + description: Bad Request + '403': + description: Forbidden + requestBody: + content: + application/json: + schema: + type: object + properties: + email: + example: any + /api/v1/signup/email/verify: + post: + description: '' + parameters: [] + responses: + '200': + description: OK + '400': + description: Bad Request + '403': + description: Forbidden + requestBody: + content: + application/json: + schema: + type: object + properties: + email: + example: any + code: + example: any + /api/v1/signup/complete-account/signup: + post: + description: '' + parameters: [] + responses: + '200': + description: OK + '400': + description: Bad Request + '403': + description: Forbidden + requestBody: + content: + application/json: + schema: + type: object + properties: + email: + example: any + firstName: + example: any + lastName: + example: any + publicKey: + example: any + encryptedPrivateKey: + example: any + iv: + example: any + tag: + example: any + salt: + example: any + verifier: + example: any + organizationName: + example: any + /api/v1/signup/complete-account/invite: + post: + description: '' + parameters: [] + responses: + '200': + description: OK + '400': + description: Bad Request + '403': + description: Forbidden + requestBody: + content: + application/json: + schema: + type: object + properties: + email: + example: any + firstName: + example: any + lastName: + example: any + publicKey: + example: any + encryptedPrivateKey: + example: any + iv: + example: any + tag: + example: any + salt: + example: any + verifier: + example: any + /api/v1/auth/token: + post: + description: '' + parameters: [] + responses: + '200': + description: OK + '400': + description: Bad Request + /api/v1/auth/login1: + post: + description: '' + parameters: [] + responses: + '200': + description: OK + '400': + description: Bad Request + requestBody: + content: + application/json: + schema: + type: object + properties: + email: + example: any + clientPublicKey: + example: any + /api/v1/auth/login2: + post: + description: '' + parameters: [] + responses: + '200': + description: OK + '400': + description: Bad Request + requestBody: + content: + application/json: + schema: + type: object + properties: + email: + example: any + clientProof: + example: any + /api/v1/auth/logout: + post: + description: '' + parameters: [] + responses: + '200': + description: OK + '400': + description: Bad Request + /api/v1/auth/checkAuth: + post: + description: '' + parameters: [] + responses: + '200': + description: OK + /api/v1/bot/{workspaceId}: + get: + description: '' + parameters: + - name: workspaceId + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + '400': + description: Bad Request + /api/v1/bot/{botId}/active: + patch: + description: '' + parameters: + - name: botId + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + '400': + description: Bad Request + requestBody: + content: + application/json: + schema: + type: object + properties: + isActive: + example: any + botKey: + example: any + /api/v1/user/: + get: + description: '' + parameters: [] + responses: + '200': + description: OK + /api/v1/user-action/: + post: + description: '' + parameters: [] + responses: + '200': + description: OK + '400': + description: Bad Request + requestBody: + content: + application/json: + schema: + type: object + properties: + action: + example: any + get: + description: '' + parameters: + - name: action + in: query + schema: + type: string + responses: + '200': + description: OK + '400': + description: Bad Request + /api/v1/organization/: + get: + description: '' + parameters: [] + responses: + '200': + description: OK + '400': + description: Bad Request + post: + description: '' + parameters: [] + responses: + '200': + description: OK + '400': + description: Bad Request + requestBody: + content: + application/json: + schema: + type: object + properties: + organizationName: + example: any + /api/v1/organization/{organizationId}: + get: + description: '' + parameters: + - name: organizationId + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + '400': + description: Bad Request + /api/v1/organization/{organizationId}/users: + get: + description: '' + parameters: + - name: organizationId + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + '400': + description: Bad Request + /api/v1/organization/{organizationId}/my-workspaces: + get: + description: '' + parameters: + - name: organizationId + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + '400': + description: Bad Request + /api/v1/organization/{organizationId}/name: + patch: + description: '' + parameters: + - name: organizationId + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + '400': + description: Bad Request + requestBody: + content: + application/json: + schema: + type: object + properties: + name: + example: any + /api/v1/organization/{organizationId}/incidentContactOrg: + get: + description: '' + parameters: + - name: organizationId + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + '400': + description: Bad Request + post: + description: '' + parameters: + - name: organizationId + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + '400': + description: Bad Request + requestBody: + content: + application/json: + schema: + type: object + properties: + email: + example: any + delete: + description: '' + parameters: + - name: organizationId + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + '400': + description: Bad Request + requestBody: + content: + application/json: + schema: + type: object + properties: + email: + example: any + /api/v1/organization/{organizationId}/customer-portal-session: + post: + description: '' + parameters: + - name: organizationId + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + '400': + description: Bad Request + /api/v1/organization/{organizationId}/subscriptions: + get: + description: '' + parameters: + - name: organizationId + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + '400': + description: Bad Request + /api/v1/workspace/{workspaceId}/keys: + get: + description: '' + parameters: + - name: workspaceId + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + '400': + description: Bad Request + /api/v1/workspace/{workspaceId}/users: + get: + description: '' + parameters: + - name: workspaceId + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + '400': + description: Bad Request + /api/v1/workspace/: + get: + description: '' + parameters: [] + responses: + '200': + description: OK + '400': + description: Bad Request + post: + description: '' + parameters: [] + responses: + '200': + description: OK + '400': + description: Bad Request + requestBody: + content: + application/json: + schema: + type: object + properties: + workspaceName: + example: any + organizationId: + example: any + /api/v1/workspace/{workspaceId}: + get: + description: '' + parameters: + - name: workspaceId + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + '400': + description: Bad Request + delete: + description: '' + parameters: + - name: workspaceId + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + '400': + description: Bad Request + /api/v1/workspace/{workspaceId}/name: + post: + description: '' + parameters: + - name: workspaceId + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + '400': + description: Bad Request + requestBody: + content: + application/json: + schema: + type: object + properties: + name: + example: any + /api/v1/workspace/{workspaceId}/invite-signup: + post: + description: '' + parameters: + - name: workspaceId + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + '400': + description: Bad Request + requestBody: + content: + application/json: + schema: + type: object + properties: + email: + example: any + /api/v1/workspace/{workspaceId}/integrations: + get: + description: '' + parameters: + - name: workspaceId + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + '400': + description: Bad Request + /api/v1/workspace/{workspaceId}/authorizations: + get: + description: '' + parameters: + - name: workspaceId + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + '400': + description: Bad Request + /api/v1/workspace/{workspaceId}/service-tokens: + get: + description: '' + parameters: + - name: workspaceId + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + '400': + description: Bad Request + /api/v1/membership-org/membershipOrg/{membershipOrgId}/change-role: + post: + description: '' + parameters: + - name: membershipOrgId + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + /api/v1/membership-org/{membershipOrgId}: + delete: + description: '' + parameters: + - name: membershipOrgId + in: path + required: true + schema: + type: string + responses: + '400': + description: Bad Request + /api/v1/membership/{workspaceId}/connect: + get: + description: '' + parameters: + - name: workspaceId + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + '400': + description: Bad Request + /api/v1/membership/{membershipId}: + delete: + description: '' + parameters: + - name: membershipId + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + '400': + description: Bad Request + /api/v1/membership/{membershipId}/change-role: + post: + description: '' + parameters: + - name: membershipId + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + '400': + description: Bad Request + requestBody: + content: + application/json: + schema: + type: object + properties: + role: + example: any + /api/v1/key/{workspaceId}: + post: + description: '' + parameters: + - name: workspaceId + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + '400': + description: Bad Request + requestBody: + content: + application/json: + schema: + type: object + properties: + key: + example: any + /api/v1/key/{workspaceId}/latest: + get: + description: '' + parameters: + - name: workspaceId + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + '400': + description: Bad Request + /api/v1/invite-org/signup: + post: + description: '' + parameters: [] + responses: + '200': + description: OK + '400': + description: Bad Request + requestBody: + content: + application/json: + schema: + type: object + properties: + organizationId: + example: any + inviteeEmail: + example: any + /api/v1/invite-org/verify: + post: + description: '' + parameters: [] + responses: + '200': + description: OK + '400': + description: Bad Request + requestBody: + content: + application/json: + schema: + type: object + properties: + email: + example: any + code: + example: any + /api/v1/secret/{workspaceId}: + post: + description: '' + parameters: + - name: workspaceId + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + '400': + description: Bad Request + requestBody: + content: + application/json: + schema: + type: object + properties: + secrets: + example: any + keys: + example: any + environment: + example: any + channel: + example: any + get: + description: '' + parameters: + - name: workspaceId + in: path + required: true + schema: + type: string + - name: environment + in: query + schema: + type: string + - name: channel + in: query + schema: + type: string + responses: + '200': + description: OK + '400': + description: Bad Request + /api/v1/secret/{workspaceId}/service-token: + get: + description: '' + parameters: + - name: workspaceId + in: path + required: true + schema: + type: string + - name: environment + in: query + schema: + type: string + - name: channel + in: query + schema: + type: string + responses: + '200': + description: OK + '400': + description: Bad Request + /api/v1/service-token/: + get: + description: '' + parameters: [] + responses: + '200': + description: OK + post: + description: '' + parameters: [] + responses: + '200': + description: OK + '400': + description: Bad Request + requestBody: + content: + application/json: + schema: + type: object + properties: + name: + example: any + workspaceId: + example: any + environment: + example: any + expiresIn: + example: any + publicKey: + example: any + encryptedKey: + example: any + nonce: + example: any + /api/v1/password/srp1: + post: + description: '' + parameters: [] + responses: + '200': + description: OK + '400': + description: Bad Request + requestBody: + content: + application/json: + schema: + type: object + properties: + clientPublicKey: + example: any + /api/v1/password/change-password: + post: + description: '' + parameters: [] + responses: + '200': + description: OK + '400': + description: Bad Request + requestBody: + content: + application/json: + schema: + type: object + properties: + clientProof: + example: any + encryptedPrivateKey: + example: any + iv: + example: any + tag: + example: any + salt: + example: any + verifier: + example: any + /api/v1/password/email/password-reset: + post: + description: '' + parameters: [] + responses: + '200': + description: OK + '400': + description: Bad Request + '403': + description: Forbidden + requestBody: + content: + application/json: + schema: + type: object + properties: + email: + example: any + /api/v1/password/email/password-reset-verify: + post: + description: '' + parameters: [] + responses: + '200': + description: OK + '400': + description: Bad Request + '403': + description: Forbidden + requestBody: + content: + application/json: + schema: + type: object + properties: + email: + example: any + code: + example: any + /api/v1/password/backup-private-key: + get: + description: '' + parameters: [] + responses: + '200': + description: OK + '400': + description: Bad Request + post: + description: '' + parameters: [] + responses: + '200': + description: OK + '400': + description: Bad Request + requestBody: + content: + application/json: + schema: + type: object + properties: + clientProof: + example: any + encryptedPrivateKey: + example: any + iv: + example: any + tag: + example: any + salt: + example: any + verifier: + example: any + /api/v1/password/password-reset: + post: + description: '' + parameters: [] + responses: + '200': + description: OK + '400': + description: Bad Request + requestBody: + content: + application/json: + schema: + type: object + properties: + encryptedPrivateKey: + example: any + iv: + example: any + tag: + example: any + salt: + example: any + verifier: + example: any + /api/v1/stripe/webhook: + post: + description: '' + parameters: + - name: stripe-signature + in: header + schema: + type: string + responses: + '200': + description: OK + '400': + description: Bad Request + /api/v1/integration/{integrationId}: + patch: + description: '' + parameters: + - name: integrationId + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + '400': + description: Bad Request + requestBody: + content: + application/json: + schema: + type: object + properties: + app: + example: any + environment: + example: any + isActive: + example: any + target: + example: any + context: + example: any + siteId: + example: any + delete: + description: '' + parameters: + - name: integrationId + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + '400': + description: Bad Request + /api/v1/integration-auth/integration-options: + get: + description: '' + parameters: [] + responses: + '200': + description: OK + /api/v1/integration-auth/oauth-token: + post: + description: '' + parameters: [] + responses: + '200': + description: OK + '400': + description: Bad Request + requestBody: + content: + application/json: + schema: + type: object + properties: + workspaceId: + example: any + code: + example: any + integration: + example: any + /api/v1/integration-auth/{integrationAuthId}/apps: + get: + description: '' + parameters: + - name: integrationAuthId + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + '400': + description: Bad Request + /api/v1/integration-auth/{integrationAuthId}: + delete: + description: '' + parameters: + - name: integrationAuthId + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + '400': + description: Bad Request + /api/v2/workspace/{workspaceId}/secrets: + post: + description: '' + parameters: + - name: workspaceId + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + '400': + description: Bad Request + requestBody: + content: + application/json: + schema: + type: object + properties: + secrets: + example: any + keys: + example: any + environment: + example: any + channel: + example: any + get: + description: '' + parameters: + - name: workspaceId + in: path + required: true + schema: + type: string + - name: environment + in: query + schema: + type: string + - name: channel + in: query + schema: + type: string + responses: + '200': + description: OK + '400': + description: Bad Request + /api/v2/workspace/{workspaceId}/encrypted-key: + get: + description: '' + parameters: + - name: workspaceId + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + '400': + description: Bad Request + /api/v2/workspace/{workspaceId}/service-token-data: + get: + description: '' + parameters: + - name: workspaceId + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + '400': + description: Bad Request + /api/v2/secret/batch-create/workspace/{workspaceId}/environment/{environment}: + post: + description: '' + parameters: + - name: workspaceId + in: path + required: true + schema: + type: string + - name: environment + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + requestBody: + content: + application/json: + schema: + type: object + properties: + secrets: + example: any + /api/v2/secret/workspace/{workspaceId}/environment/{environment}: + post: + description: '' + parameters: + - name: workspaceId + in: path + required: true + schema: + type: string + - name: environment + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + requestBody: + content: + application/json: + schema: + type: object + properties: + secret: + example: any + /api/v2/secret/workspace/{workspaceId}: + get: + description: '' + parameters: + - name: workspaceId + in: path + required: true + schema: + type: string + - name: environment + in: query + schema: + type: string + responses: + '200': + description: OK + /api/v2/secret/{secretId}: + get: + description: '' + parameters: + - name: secretId + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + delete: + description: '' + parameters: + - name: secretId + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + /api/v2/secret/batch/workspace/{workspaceId}/environment/{environmentName}: + delete: + description: '' + parameters: + - name: workspaceId + in: path + required: true + schema: + type: string + - name: environmentName + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + requestBody: + content: + application/json: + schema: + type: object + properties: + secretIds: + example: any + /api/v2/secret/batch-modify/workspace/{workspaceId}/environment/{environmentName}: + patch: + description: '' + parameters: + - name: workspaceId + in: path + required: true + schema: + type: string + - name: environmentName + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + requestBody: + content: + application/json: + schema: + type: object + properties: + secrets: + example: any + /api/v2/secret/workspace/{workspaceId}/environment/{environmentName}: + patch: + description: '' + parameters: + - name: workspaceId + in: path + required: true + schema: + type: string + - name: environmentName + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + requestBody: + content: + application/json: + schema: + type: object + properties: + secret: + example: any + /api/v2/secrets/: + post: + description: '' + parameters: [] + responses: + '200': + description: OK + requestBody: + content: + application/json: + schema: + type: object + properties: + secrets: + example: any + workspaceId: + example: any + environment: + example: any + get: + description: '' + parameters: + - name: workspaceId + in: query + schema: + type: string + - name: environment + in: query + schema: + type: string + responses: + '200': + description: OK + patch: + description: '' + parameters: [] + responses: + '200': + description: OK + requestBody: + content: + application/json: + schema: + type: object + properties: + secrets: + example: any + delete: + description: '' + parameters: [] + responses: + '200': + description: OK + /api/v2/service-token/: + get: + description: '' + parameters: [] + responses: + '200': + description: OK + '400': + description: Bad Request + post: + description: '' + parameters: [] + responses: + '200': + description: OK + '400': + description: Bad Request + requestBody: + content: + application/json: + schema: + type: object + properties: + name: + example: any + workspaceId: + example: any + environment: + example: any + encryptedKey: + example: any + iv: + example: any + tag: + example: any + expiresIn: + example: any + /api/v2/service-token/{serviceTokenDataId}: + delete: + description: '' + parameters: + - name: serviceTokenDataId + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + '400': + description: Bad Request + /api/v2/api-key-data/: + get: + description: '' + parameters: [] + responses: + '200': + description: OK + '400': + description: Bad Request + post: + description: '' + parameters: [] + responses: + '200': + description: OK + '400': + description: Bad Request + requestBody: + content: + application/json: + schema: + type: object + properties: + name: + example: any + expiresIn: + example: any + /api/v2/api-key-data/{apiKeyDataId}: + delete: + description: '' + parameters: + - name: apiKeyDataId + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + '400': + description: Bad Request + /api/status: + get: + description: '' + parameters: [] + responses: + '200': + description: OK +components: + schemas: + secret: + type: object + properties: + type: + type: string + example: object + properties: + type: object + properties: + test: + type: object + properties: + type: + type: string + example: integer + description: + type: string + example: '123' + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT From 3ad3e19bcf202de57c812555a902204f02aa6a0f Mon Sep 17 00:00:00 2001 From: akhilmhdh Date: Wed, 28 Dec 2022 23:42:17 +0530 Subject: [PATCH 22/65] feat(#31): implemented api for environment crud operations --- backend/src/app.ts | 2 + .../v1/integrationAuthController.ts | 8 +- .../src/controllers/v1/secretController.ts | 10 +- .../controllers/v1/serviceTokenController.ts | 4 +- .../controllers/v2/environmentController.ts | 167 ++++++++++++++++++ backend/src/controllers/v2/index.ts | 4 +- .../src/controllers/v2/workspaceController.ts | 9 +- backend/src/helpers/integration.ts | 8 +- backend/src/models/integration.ts | 5 - backend/src/models/secret.ts | 5 - backend/src/models/serviceToken.ts | 4 - backend/src/models/workspace.ts | 32 +++- backend/src/routes/v2/environment.ts | 54 ++++++ backend/src/routes/v2/index.ts | 6 +- backend/src/services/IntegrationService.ts | 12 +- 15 files changed, 293 insertions(+), 37 deletions(-) create mode 100644 backend/src/controllers/v2/environmentController.ts create mode 100644 backend/src/routes/v2/environment.ts diff --git a/backend/src/app.ts b/backend/src/app.ts index aa7ac9b28..571b84cd4 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -46,6 +46,7 @@ import { workspace as v2WorkspaceRouter, serviceTokenData as v2ServiceTokenDataRouter, apiKeyData as v2APIKeyDataRouter, + environment as v2EnvironmentRouter, } from './routes/v2'; import { healthCheck } from './routes/status'; @@ -108,6 +109,7 @@ app.use('/api/v2/secret', v2SecretRouter); // stop supporting, TODO: revise app.use('/api/v2/secrets', v2SecretsRouter); app.use('/api/v2/service-token', v2ServiceTokenDataRouter); // TODO: turn into plural route app.use('/api/v2/api-key-data', v2APIKeyDataRouter); +app.use('/api/v2/environments', v2EnvironmentRouter); // api docs app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerFile)) diff --git a/backend/src/controllers/v1/integrationAuthController.ts b/backend/src/controllers/v1/integrationAuthController.ts index 95d0066ae..b2f93a8c2 100644 --- a/backend/src/controllers/v1/integrationAuthController.ts +++ b/backend/src/controllers/v1/integrationAuthController.ts @@ -31,11 +31,17 @@ export const oAuthExchange = async ( if (!INTEGRATION_SET.has(integration)) throw new Error('Failed to validate integration'); + + const environments = req.membership.workspace?.environments || []; + if(environments.length === 0){ + throw new Error("Failed to get environments") + } await IntegrationService.handleOAuthExchange({ workspaceId, integration, - code + code, + environment: environments[0].slug, }); } catch (err) { Sentry.setUser(null); diff --git a/backend/src/controllers/v1/secretController.ts b/backend/src/controllers/v1/secretController.ts index 1b756ecc7..c76e5e883 100644 --- a/backend/src/controllers/v1/secretController.ts +++ b/backend/src/controllers/v1/secretController.ts @@ -9,7 +9,6 @@ import { import { pushKeys } from '../../helpers/key'; import { eventPushSecrets } from '../../events'; import { EventService } from '../../services'; -import { ENV_SET } from '../../variables'; import { postHogClient } from '../../services'; interface PushSecret { @@ -44,7 +43,8 @@ export const pushSecrets = async (req: Request, res: Response) => { const { workspaceId } = req.params; // validate environment - if (!ENV_SET.has(environment)) { + const workspaceEnvs = req.membership.workspace.environments; + if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) { throw new Error('Failed to validate environment'); } @@ -116,7 +116,8 @@ export const pullSecrets = async (req: Request, res: Response) => { const { workspaceId } = req.params; // validate environment - if (!ENV_SET.has(environment)) { + const workspaceEnvs = req.membership.workspace.environments; + if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) { throw new Error('Failed to validate environment'); } @@ -183,7 +184,8 @@ export const pullSecretsServiceToken = async (req: Request, res: Response) => { const { workspaceId } = req.params; // validate environment - if (!ENV_SET.has(environment)) { + const workspaceEnvs = req.membership.workspace.environments; + if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) { throw new Error('Failed to validate environment'); } diff --git a/backend/src/controllers/v1/serviceTokenController.ts b/backend/src/controllers/v1/serviceTokenController.ts index 244a58783..3fafb9043 100644 --- a/backend/src/controllers/v1/serviceTokenController.ts +++ b/backend/src/controllers/v1/serviceTokenController.ts @@ -1,7 +1,6 @@ import { Request, Response } from 'express'; import { ServiceToken } from '../../models'; import { createToken } from '../../helpers/auth'; -import { ENV_SET } from '../../variables'; import { JWT_SERVICE_SECRET } from '../../config'; /** @@ -36,7 +35,8 @@ export const createServiceToken = async (req: Request, res: Response) => { } = req.body; // validate environment - if (!ENV_SET.has(environment)) { + const workspaceEnvs = req.membership.workspace.environments; + if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) { throw new Error('Failed to validate environment'); } diff --git a/backend/src/controllers/v2/environmentController.ts b/backend/src/controllers/v2/environmentController.ts new file mode 100644 index 000000000..77c4fcb59 --- /dev/null +++ b/backend/src/controllers/v2/environmentController.ts @@ -0,0 +1,167 @@ +import { Request, Response } from 'express'; +import * as Sentry from '@sentry/node'; +import { Secret, ServiceToken, Workspace, Integration } from '../../models'; + +/** + * Create new workspace environment named [environmentName] under workspace with id + * @param req + * @param res + * @returns + */ +export const createWorkspaceEnvironment = async ( + req: Request, + res: Response +) => { + const { workspaceId, environmentName, environmentSlug } = req.body; + try { + // atomic create the environment + const workspace = await Workspace.findOneAndUpdate( + { + _id: workspaceId, + 'environments.slug': { $ne: environmentSlug }, + 'environments.name': { $ne: environmentName }, + }, + { + $addToSet: { + environments: { name: environmentName, slug: environmentSlug }, + }, + } + ); + + if (!workspace) { + throw new Error('Failed to update workspace environment'); + } + } catch (err) { + Sentry.setUser({ email: req.user.email }); + Sentry.captureException(err); + return res.status(400).send({ + message: 'Failed to create new workspace environment', + }); + } + + return res.status(200).send({ + message: 'Successfully created new environment', + workspace: workspaceId, + environment: { + name: environmentName, + slug: environmentSlug, + }, + }); +}; + +/** + * Rename workspace environment with new name and slug of a workspace with [workspaceId] + * Old slug [oldEnvironmentSlug] must be provided + * @param req + * @param res + * @returns + */ +export const renameWorkspaceEnvironment = async ( + req: Request, + res: Response +) => { + const { workspaceId, environmentName, environmentSlug, oldEnvironmentSlug } = + req.body; + try { + // user should pass both new slug and env name + if (!environmentSlug || !environmentName) { + throw new Error('Invalid environment given.'); + } + + // atomic update the env to avoid conflict + const workspace = await Workspace.findOneAndUpdate( + { _id: workspaceId, 'environments.slug': oldEnvironmentSlug }, + { + 'environments.$.name': environmentName, + 'environments.$.slug': environmentSlug, + } + ); + if (!workspace) { + throw new Error('Failed to update workspace'); + } + + await Secret.updateMany( + { workspace: workspaceId, environment: oldEnvironmentSlug }, + { environment: environmentSlug } + ); + await ServiceToken.updateMany( + { workspace: workspaceId, environment: oldEnvironmentSlug }, + { environment: environmentSlug } + ); + await Integration.updateMany( + { workspace: workspaceId, environment: oldEnvironmentSlug }, + { environment: environmentSlug } + ); + } catch (err) { + Sentry.setUser({ email: req.user.email }); + Sentry.captureException(err); + return res.status(400).send({ + message: 'Failed to update workspace environment', + }); + } + + return res.status(200).send({ + message: 'Successfully update environment', + workspace: workspaceId, + environment: { + name: environmentName, + slug: environmentSlug, + }, + }); +}; + +/** + * Delete workspace environment by [environmentSlug] of workspace [workspaceId] and do the clean up + * @param req + * @param res + * @returns + */ +export const deleteWorkspaceEnvironment = async ( + req: Request, + res: Response +) => { + const { workspaceId, environmentSlug } = req.body; + try { + // atomic delete the env in the workspacce + const workspace = await Workspace.findOneAndUpdate( + { _id: workspaceId }, + { + $pull: { + environments: { + slug: environmentSlug, + }, + }, + } + ); + if (!workspace) { + throw new Error('Failed to delete workspace environment'); + } + + // clean up + await Secret.deleteMany({ + workspace: workspaceId, + environment: environmentSlug, + }); + await ServiceToken.deleteMany({ + workspace: workspaceId, + environment: environmentSlug, + }); + await Integration.deleteMany({ + workspace: workspaceId, + environment: environmentSlug, + }); + + } catch (err) { + Sentry.setUser({ email: req.user.email }); + Sentry.captureException(err); + return res.status(400).send({ + message: 'Failed to delete workspace environment', + }); + } + + return res.status(200).send({ + message: 'Successfully deleted environment', + workspace: workspaceId, + environment: environmentSlug, + }); +}; diff --git a/backend/src/controllers/v2/index.ts b/backend/src/controllers/v2/index.ts index 1651c09ee..601314a2a 100644 --- a/backend/src/controllers/v2/index.ts +++ b/backend/src/controllers/v2/index.ts @@ -3,11 +3,13 @@ import * as serviceTokenDataController from './serviceTokenDataController'; import * as apiKeyDataController from './apiKeyDataController'; import * as secretController from './secretController'; import * as secretsController from './secretsController'; +import * as environmentController from './environmentController'; export { workspaceController, serviceTokenDataController, apiKeyDataController, secretController, - secretsController + secretsController, + environmentController } diff --git a/backend/src/controllers/v2/workspaceController.ts b/backend/src/controllers/v2/workspaceController.ts index 0dbdfa076..efac159a5 100644 --- a/backend/src/controllers/v2/workspaceController.ts +++ b/backend/src/controllers/v2/workspaceController.ts @@ -19,7 +19,6 @@ import { import { pushKeys } from '../../helpers/key'; import { postHogClient, EventService } from '../../services'; import { eventPushSecrets } from '../../events'; -import { ENV_SET } from '../../variables'; interface V2PushSecret { type: string; // personal or shared @@ -52,7 +51,8 @@ export const pushWorkspaceSecrets = async (req: Request, res: Response) => { const { workspaceId } = req.params; // validate environment - if (!ENV_SET.has(environment)) { + const workspaceEnvs = req.membership.workspace.environments; + if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) { throw new Error('Failed to validate environment'); } @@ -129,6 +129,11 @@ export const pullSecrets = async (req: Request, res: Response) => { } else if (req.serviceTokenData) { userId = req.serviceTokenData.user._id } + // validate environment + const workspaceEnvs = req.membership.workspace.environments; + if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) { + throw new Error('Failed to validate environment'); + } secrets = await pull({ userId, diff --git a/backend/src/helpers/integration.ts b/backend/src/helpers/integration.ts index d92156ece..f602f1729 100644 --- a/backend/src/helpers/integration.ts +++ b/backend/src/helpers/integration.ts @@ -7,8 +7,6 @@ import { import { exchangeCode, exchangeRefresh, syncSecrets } from '../integrations'; import { BotService } from '../services'; import { - ENV_DEV, - EVENT_PUSH_SECRETS, INTEGRATION_VERCEL, INTEGRATION_NETLIFY } from '../variables'; @@ -36,11 +34,13 @@ interface Update { const handleOAuthExchangeHelper = async ({ workspaceId, integration, - code + code, + environment }: { workspaceId: string; integration: string; code: string; + environment: string; }) => { let action; let integrationAuth; @@ -102,9 +102,9 @@ const handleOAuthExchangeHelper = async ({ // initialize new integration after exchange await new Integration({ workspace: workspaceId, - environment: ENV_DEV, isActive: false, app: null, + environment, integration, integrationAuth: integrationAuth._id }).save(); diff --git a/backend/src/models/integration.ts b/backend/src/models/integration.ts index 6da699216..0b184ed67 100644 --- a/backend/src/models/integration.ts +++ b/backend/src/models/integration.ts @@ -1,9 +1,5 @@ import { Schema, model, Types } from 'mongoose'; import { - ENV_DEV, - ENV_TESTING, - ENV_STAGING, - ENV_PROD, INTEGRATION_HEROKU, INTEGRATION_VERCEL, INTEGRATION_NETLIFY, @@ -32,7 +28,6 @@ const integrationSchema = new Schema( }, environment: { type: String, - enum: [ENV_DEV, ENV_TESTING, ENV_STAGING, ENV_PROD], required: true }, isActive: { diff --git a/backend/src/models/secret.ts b/backend/src/models/secret.ts index a01b92d80..6887c8b0f 100644 --- a/backend/src/models/secret.ts +++ b/backend/src/models/secret.ts @@ -2,10 +2,6 @@ import { Schema, model, Types } from 'mongoose'; import { SECRET_SHARED, SECRET_PERSONAL, - ENV_DEV, - ENV_TESTING, - ENV_STAGING, - ENV_PROD } from '../variables'; export interface ISecret { @@ -53,7 +49,6 @@ const secretSchema = new Schema( }, environment: { type: String, - enum: [ENV_DEV, ENV_TESTING, ENV_STAGING, ENV_PROD], required: true }, secretKeyCiphertext: { diff --git a/backend/src/models/serviceToken.ts b/backend/src/models/serviceToken.ts index b5a2f4ec9..9d91b076e 100644 --- a/backend/src/models/serviceToken.ts +++ b/backend/src/models/serviceToken.ts @@ -1,7 +1,4 @@ import { Schema, model, Types } from 'mongoose'; -import { ENV_DEV, ENV_TESTING, ENV_STAGING, ENV_PROD } from '../variables'; - -// TODO: deprecate export interface IServiceToken { _id: Types.ObjectId; name: string; @@ -33,7 +30,6 @@ const serviceTokenSchema = new Schema( }, environment: { type: String, - enum: [ENV_DEV, ENV_TESTING, ENV_STAGING, ENV_PROD], required: true }, expiresAt: { diff --git a/backend/src/models/workspace.ts b/backend/src/models/workspace.ts index 1e886c523..fa7dbc8b5 100644 --- a/backend/src/models/workspace.ts +++ b/backend/src/models/workspace.ts @@ -4,6 +4,10 @@ export interface IWorkspace { _id: Types.ObjectId; name: string; organization: Types.ObjectId; + environments: Array<{ + name: string; + slug: string; + }>; } const workspaceSchema = new Schema({ @@ -15,7 +19,33 @@ const workspaceSchema = new Schema({ type: Schema.Types.ObjectId, ref: 'Organization', required: true - } + }, + environments: { + type: [ + { + name: String, + slug: String, + }, + ], + default: [ + { + name: "development", + slug: "dev" + }, + { + name: "test", + slug: "test" + }, + { + name: "staging", + slug: "staging" + }, + { + name: "production", + slug: "prod" + } + ], + }, }); const Workspace = model('Workspace', workspaceSchema); diff --git a/backend/src/routes/v2/environment.ts b/backend/src/routes/v2/environment.ts new file mode 100644 index 000000000..592a51248 --- /dev/null +++ b/backend/src/routes/v2/environment.ts @@ -0,0 +1,54 @@ +import express from 'express'; +const router = express.Router(); +import { body, param } from 'express-validator'; +import { environmentController } from '../../controllers/v2'; +import { + requireAuth, + requireWorkspaceAuth, + validateRequest, +} from '../../middleware'; +import { ADMIN, MEMBER, COMPLETED, GRANTED } from '../../variables'; + +router.post( + '/:workspaceId', + requireAuth, + requireWorkspaceAuth({ + acceptedRoles: [ADMIN, MEMBER], + acceptedStatuses: [COMPLETED, GRANTED], + }), + param('workspaceId').exists().trim(), + body('environmentSlug').exists().trim(), + body('environmentName').exists().trim(), + validateRequest, + environmentController.createWorkspaceEnvironment +); + +router.put( + '/:workspaceId', + requireAuth, + requireWorkspaceAuth({ + acceptedRoles: [ADMIN, MEMBER], + acceptedStatuses: [COMPLETED, GRANTED], + }), + param('workspaceId').exists().trim(), + body('environmentSlug').exists().trim(), + body('environmentName').exists().trim(), + body('oldEnvironmentSlug').exists().trim(), + validateRequest, + environmentController.renameWorkspaceEnvironment +); + +router.delete( + '/:workspaceId', + requireAuth, + requireWorkspaceAuth({ + acceptedRoles: [ADMIN], + acceptedStatuses: [GRANTED], + }), + param('workspaceId').exists().trim(), + body('environmentSlug').exists().trim(), + validateRequest, + environmentController.deleteWorkspaceEnvironment +); + +export default router; diff --git a/backend/src/routes/v2/index.ts b/backend/src/routes/v2/index.ts index 8bea42620..2740c3530 100644 --- a/backend/src/routes/v2/index.ts +++ b/backend/src/routes/v2/index.ts @@ -3,11 +3,13 @@ import secrets from './secrets'; import workspace from './workspace'; import serviceTokenData from './serviceTokenData'; import apiKeyData from './apiKeyData'; +import environment from "./environment" export { secret, secrets, workspace, serviceTokenData, - apiKeyData -} + apiKeyData, + environment +} \ No newline at end of file diff --git a/backend/src/services/IntegrationService.ts b/backend/src/services/IntegrationService.ts index 32f5f5a88..43746aee4 100644 --- a/backend/src/services/IntegrationService.ts +++ b/backend/src/services/IntegrationService.ts @@ -11,10 +11,6 @@ import { setIntegrationAuthAccessHelper, } from '../helpers/integration'; import { exchangeCode } from '../integrations'; -import { - ENV_DEV, - EVENT_PUSH_SECRETS -} from '../variables'; // should sync stuff be here too? Probably. // TODO: move bot functions to IntegrationService. @@ -32,22 +28,26 @@ class IntegrationService { * - Create bot sequence for integration * @param {Object} obj * @param {String} obj.workspaceId - id of workspace + * @param {String} obj.environment - workspace environment * @param {String} obj.integration - name of integration * @param {String} obj.code - code */ static async handleOAuthExchange({ workspaceId, integration, - code + code, + environment }: { workspaceId: string; integration: string; code: string; + environment: string; }) { await handleOAuthExchangeHelper({ workspaceId, integration, - code + code, + environment }); } From 9116bf33441422deee8fe1d2b9b0a33f1cc36c8b Mon Sep 17 00:00:00 2001 From: akhilmhdh Date: Tue, 10 Jan 2023 19:39:19 +0530 Subject: [PATCH 23/65] feat(ui): implemented ui for env management table --- .../basic/dialog/AddEnvironmentDialog.tsx | 127 +++++++++ .../basic/dialog/DeleteActionModal.tsx | 100 +++++++ .../basic/table/EnvironmentsTable.tsx | 118 +++++++++ frontend/hooks/index.ts | 1 + frontend/hooks/usePopUp.tsx | 69 +++++ frontend/pages/settings/project/[id].tsx | 245 ++++++++++++++++++ frontend/tsconfig.json | 1 + 7 files changed, 661 insertions(+) create mode 100644 frontend/components/basic/dialog/AddEnvironmentDialog.tsx create mode 100644 frontend/components/basic/dialog/DeleteActionModal.tsx create mode 100644 frontend/components/basic/table/EnvironmentsTable.tsx create mode 100644 frontend/hooks/index.ts create mode 100644 frontend/hooks/usePopUp.tsx create mode 100644 frontend/pages/settings/project/[id].tsx diff --git a/frontend/components/basic/dialog/AddEnvironmentDialog.tsx b/frontend/components/basic/dialog/AddEnvironmentDialog.tsx new file mode 100644 index 000000000..83b578d81 --- /dev/null +++ b/frontend/components/basic/dialog/AddEnvironmentDialog.tsx @@ -0,0 +1,127 @@ +import { FormEventHandler, Fragment, useEffect, useState } from 'react'; +import { Dialog, Transition } from '@headlessui/react'; + +import Button from '../buttons/Button'; +import InputField from '../InputField'; + +type FormFields = { name: string; slug: string }; + +type Props = { + isOpen?: boolean; + isEditMode?: boolean; + // on edit mode load up initial values + initialValues?: FormFields; + onClose: () => void; + onSubmit: (envName: string, envSlug: string) => void; +}; + +/** + * The dialog modal for when the user wants to create a new workspace + * @param {*} param0 + * @returns + */ +export const AddEnvironmentDialog = ({ isOpen, onClose, onSubmit, initialValues, isEditMode }: Props) => { + const [formInput, setFormInput] = useState({ + name: '', + slug: '', + }); + + // This use effect can be removed when the unmount is happening from outside the component + // When unmount happens outside state gets unmounted also + useEffect(() => { + setFormInput(initialValues || { name: '', slug: '' }); + }, [isOpen]); + + // REFACTOR: Move to react-hook-form with yup for better form management + const onInputChange = (fieldName: string, fieldValue: string) => { + setFormInput((state) => ({ ...state, [fieldName]: fieldValue })); + }; + + const onFormSubmit: FormEventHandler = (e) => { + e.preventDefault(); + console.log(formInput); + }; + + return ( +
+ + + +
+ + +
+
+ + + + {isEditMode + ? 'Update environment' + : 'Create a new environment'} + +
+
+ onInputChange('name', val)} + type='varName' + value={formInput.name} + placeholder='' + isRequired + // error={error.length > 0} + // errorText={error} + /> +
+
+ onInputChange('slug', val)} + type='varName' + value={formInput.slug} + placeholder='' + isRequired + // error={error.length > 0} + // errorText={error} + /> +
+

+ Slugs are shorthands used in cli to access environment +

+
+
+
+
+
+
+
+
+
+
+ ); +}; diff --git a/frontend/components/basic/dialog/DeleteActionModal.tsx b/frontend/components/basic/dialog/DeleteActionModal.tsx new file mode 100644 index 000000000..cafd832ae --- /dev/null +++ b/frontend/components/basic/dialog/DeleteActionModal.tsx @@ -0,0 +1,100 @@ +import { Fragment, useState } from 'react'; +import { Dialog, Transition } from '@headlessui/react'; + +import InputField from '../InputField'; + +// REFACTOR: Move all these modals into one reusable one +type Props = { + isOpen?: boolean; + onClose: ()=>void; + title: string; + onSubmit:()=>void; + deleteKey?:string; +} + +const DeleteActionModal = ({ + isOpen, + onClose, + title, + onSubmit, + deleteKey +}:Props) => { + const [deleteInputField, setDeleteInputField] = useState("") + + return ( +
+ + + +
+ +
+
+ + + + {title} + +
+

+ This action is irrevertible. +

+
+
+ setDeleteInputField(val)} + value={deleteInputField} + type='text' + /> +
+
+ + +
+
+
+
+
+
+
+
+ ); +}; + +export default DeleteActionModal; diff --git a/frontend/components/basic/table/EnvironmentsTable.tsx b/frontend/components/basic/table/EnvironmentsTable.tsx new file mode 100644 index 000000000..501b7f9bc --- /dev/null +++ b/frontend/components/basic/table/EnvironmentsTable.tsx @@ -0,0 +1,118 @@ +import { faPencil,faPlus,faX } from '@fortawesome/free-solid-svg-icons'; + +import { usePopUp } from '../../../hooks/usePopUp'; +import Button from '../buttons/Button'; +import {AddEnvironmentDialog} from '../dialog/AddEnvironmentDialog'; +import DeleteActionModal from '../dialog/DeleteActionModal'; + +const EnvironmentTable = ({ data = [] }) => { + const { popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([ + 'createUpdateEnv', + 'deleteEnv', + ] as const); + + return ( + <> +
+
+

Project Environments

+

+ Choose which environments will show up in your dashboard like + development, staging, production +

+

+ Note: the text in slugs shows how these environmant should be + accessed in CLI. +

+
+
+
+
+
+
+ + + + + + + + + + {data?.length > 0 ? ( + data.map(({ name, slug }) => { + return ( + + + + + + ); + }) + ) : ( + + + + )} + +
NameSlug
+ {name} + + {slug} + +
+
+
+
+
+ No environmants found +
+ handlePopUpClose('deleteEnv')} + onSubmit={() => handlePopUpClose('deleteEnv')} + /> + handlePopUpClose('createUpdateEnv')} + onSubmit={() => null} + /> +
+ + ); +}; + +export default EnvironmentTable; diff --git a/frontend/hooks/index.ts b/frontend/hooks/index.ts new file mode 100644 index 000000000..dcea2eb7c --- /dev/null +++ b/frontend/hooks/index.ts @@ -0,0 +1 @@ +export { usePopUp } from './usePopUp'; diff --git a/frontend/hooks/usePopUp.tsx b/frontend/hooks/usePopUp.tsx new file mode 100644 index 000000000..eb0835b49 --- /dev/null +++ b/frontend/hooks/usePopUp.tsx @@ -0,0 +1,69 @@ +import { useCallback, useState } from 'react'; + +interface usePopUpProps { + name: Readonly; + isOpen: boolean; +} + +/** + * to provide better intellisense + * checks which type of inputProps were given and converts them into key-names + * SIDENOTE: On inputting give it as const and not string with (as const) + */ +type usePopUpState | usePopUpProps[]> = { + [P in T extends usePopUpProps[] ? T[number]['name'] : T[number]]: { + isOpen: boolean; + data?: unknown; + }; +}; + +interface usePopUpReturn | usePopUpProps[]> { + popUp: usePopUpState; + handlePopUpOpen: (popUpName: keyof usePopUpState, data?: unknown) => void; + handlePopUpClose: (popUpName: keyof usePopUpState) => void; + handlePopUpToggle: (popUpName: keyof usePopUpState) => void; +} + +/** + * This hook is used to manage multiple popUps/modal/dialog in a page + * Provides api to open,close,toggle and also store temporary data for the popUp + * @param popUpNames: the names of popUp containers eg: ["popUp1","second"] or [{name:"popUp2",isOpen:bool}] + */ +export const usePopUp = | usePopUpProps[]>( + popUpNames: T +): usePopUpReturn => { + const [popUp, setPopUp] = useState>( + Object.fromEntries( + popUpNames.map((popUpName) => + typeof popUpName === 'string' + ? [popUpName, { isOpen: false }] + : [popUpName.name, { isOpen: popUpName.isOpen }] + ) // convert into an array of [[popUpName,state]] then into Object + ) as usePopUpState // to override generic string return type of the function + ); + + const handlePopUpOpen = useCallback( + (popUpName: keyof usePopUpState, data?: unknown) => { + setPopUp((popUp) => ({ ...popUp, [popUpName]: { isOpen: true, data } })); + }, + [] + ); + + const handlePopUpClose = useCallback((popUpName: keyof usePopUpState) => { + setPopUp((popUp) => ({ ...popUp, [popUpName]: { isOpen: false } })); + }, []); + + const handlePopUpToggle = useCallback((popUpName: keyof usePopUpState) => { + setPopUp((popUp) => ({ + ...popUp, + [popUpName]: { isOpen: !popUp[popUpName].isOpen }, + })); + }, []); + + return { + popUp, + handlePopUpOpen, + handlePopUpClose, + handlePopUpToggle, + }; +}; diff --git a/frontend/pages/settings/project/[id].tsx b/frontend/pages/settings/project/[id].tsx new file mode 100644 index 000000000..4a64c3e65 --- /dev/null +++ b/frontend/pages/settings/project/[id].tsx @@ -0,0 +1,245 @@ +import { useEffect, useRef, useState } from "react"; +import Head from "next/head"; +import { useRouter } from "next/router"; +import { useTranslation } from "next-i18next"; +import { faCheck, faPlus } from "@fortawesome/free-solid-svg-icons"; + +import Button from "~/components/basic/buttons/Button"; +import AddServiceTokenDialog from "~/components/basic/dialog/AddServiceTokenDialog"; +import InputField from "~/components/basic/InputField"; +import EnvironmentTable from '~/components/basic/table/EnvironmentsTable'; +import ServiceTokenTable from "~/components/basic/table/ServiceTokenTable"; +import NavHeader from "~/components/navigation/NavHeader"; +import { getTranslatedServerSideProps } from "~/utilities/withTranslateProps"; + +import getServiceTokens from "../../api/serviceToken/getServiceTokens"; +import deleteWorkspace from "../../api/workspace/deleteWorkspace"; +import getWorkspaces from "../../api/workspace/getWorkspaces"; +import renameWorkspace from "../../api/workspace/renameWorkspace"; + +export default function SettingsBasic() { + const [buttonReady, setButtonReady] = useState(false); + const router = useRouter(); + const [workspaceName, setWorkspaceName] = useState(""); + const [serviceTokens, setServiceTokens] = useState([]); + const [environments,setEnvironments] = useState([]); + const [workspaceToBeDeletedName, setWorkspaceToBeDeletedName] = useState(""); + const [isAddOpen, setIsAddOpen] = useState(false); + const [isAddServiceTokenDialogOpen, setIsAddServiceTokenDialogOpen] = useState(false); + + const { t } = useTranslation(); + + useEffect(async () => { + const userWorkspaces = await getWorkspaces(); + userWorkspaces.forEach((userWorkspace) => { + if (userWorkspace._id == router.query.id) { + setWorkspaceName(userWorkspace.name); + setEnvironments(userWorkspace.environments); + } + }); + const tempServiceTokens = await getServiceTokens({ + workspaceId: router.query.id, + }); + setServiceTokens(tempServiceTokens); + }, []); + + const modifyWorkspaceName = (newName) => { + setButtonReady(true); + setWorkspaceName(newName); + }; + + const submitChanges = (newWorkspaceName) => { + renameWorkspace(router.query.id, newWorkspaceName); + setButtonReady(false); + }; + + const closeAddServiceTokenModal = () => { + setIsAddServiceTokenDialogOpen(false); + }; + + /** + * This function deleted a workspace. + * It first checks if there is more than one workspace aviable. Otherwise, it doesn't delete + * It then checks if the name of the workspace to be deleted is correct. Otherwise, it doesn't delete. + * It then deletes the workspace and forwards the user to another aviable workspace. + */ + const executeDeletingWorkspace = async () => { + const userWorkspaces = await getWorkspaces(); + + if (userWorkspaces.length > 1) { + if ( + userWorkspaces.filter( + (workspace) => workspace._id == router.query.id + )[0].name == workspaceToBeDeletedName + ) { + await deleteWorkspace(router.query.id); + const userWorkspaces = await getWorkspaces(); + router.push("/dashboard/" + userWorkspaces[0]._id); + } + } + }; + + return ( +
+ + + {t('common:head-title', { title: t('settings-project:title') })} + + + + +
+
+ +
+
+

+ {t('settings-project:title')} +

+

+ {t('settings-project:description')} +

+
+
+
+
+
+
+

+ {t('common:display-name')} +

+
+ +
+
+
+
+
+
+
+

+ {t('common:project-id')} +

+

+ {t('settings-project:project-id-description')} +

+

+ {t('settings-project:project-id-description2')} + {/* eslint-disable-next-line react/jsx-no-target-blank */} + + {t('settings-project:docs')} + +

+
+ +
+
+
+
+
+

+ {t('section-token:service-tokens')} +

+

+ {t('section-token:service-tokens-description')} +

+
+
+
+
+ +
+
+ +
+
+
+
+

+ {t('settings-project:danger-zone')} +

+

+ {t('settings-project:danger-zone-note')} +

+
+ +
+ +

+ {t('settings-project:delete-project-note')} +

+
+
+
+
+
+ ); +} + +SettingsBasic.requireAuth = true; + +export const getServerSideProps = getTranslatedServerSideProps([ + "settings", + "settings-project", + "section-token", +]); diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index e936a69a2..f51378ec3 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -3,6 +3,7 @@ "baseUrl": ".", "paths": { "~/components/*": ["components/*"], + "~/hooks/*": ["hooks/*"], "~/utilities/*": ["components/utilities/*"], "~/*": ["const"], "~/pages/*": ["pages/*"] From 84700308f5201b70c3680727de0d93eedf7ab32d Mon Sep 17 00:00:00 2001 From: akhilmhdh Date: Wed, 11 Jan 2023 23:09:34 +0530 Subject: [PATCH 24/65] feat(#31): implemented ui for multi env and integrated api with backend fix(#31): fixed all v2 release conflict --- backend/src/app.ts | 2 +- .../v1/integrationAuthController.ts | 2 +- .../controllers/v2/environmentController.ts | 121 ++- backend/src/ee/models/secretVersion.ts | 5 - backend/src/models/integration.ts | 2 +- backend/src/routes/v2/environment.ts | 25 +- backend/src/routes/v2/secrets.ts | 4 +- frontend/components/basic/Listbox.tsx | 2 +- .../basic/dialog/AddServiceTokenDialog.js | 164 ++-- ...log.tsx => AddUpdateEnvironmentDialog.tsx} | 26 +- .../basic/dialog/DeleteActionModal.tsx | 6 +- .../basic/table/EnvironmentsTable.tsx | 73 +- .../basic/table/ServiceTokenTable.tsx | 3 +- .../components/integrations/Integration.tsx | 10 +- .../utilities/secrets/downloadDotEnv.ts | 3 +- .../utilities/secrets/getSecretsForProject.ts | 6 +- .../api/environments/createEnvironment.ts | 29 + .../api/environments/deleteEnvironment.ts | 26 + .../api/environments/updateEnvironment.ts | 33 + frontend/pages/api/workspace/getWorkspaces.ts | 1 + frontend/pages/dashboard/[id].tsx | 819 +++++++++++------- frontend/pages/settings/project/[id].js | 306 ------- frontend/pages/settings/project/[id].tsx | 234 +++-- 23 files changed, 1051 insertions(+), 851 deletions(-) rename frontend/components/basic/dialog/{AddEnvironmentDialog.tsx => AddUpdateEnvironmentDialog.tsx} (87%) create mode 100644 frontend/pages/api/environments/createEnvironment.ts create mode 100644 frontend/pages/api/environments/deleteEnvironment.ts create mode 100644 frontend/pages/api/environments/updateEnvironment.ts delete mode 100644 frontend/pages/settings/project/[id].js diff --git a/backend/src/app.ts b/backend/src/app.ts index 571b84cd4..82275b9c9 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -104,12 +104,12 @@ app.use('/api/v1/integration', v1IntegrationRouter); app.use('/api/v1/integration-auth', v1IntegrationAuthRouter); // v2 routes +app.use('/api/v2/workspace', v2EnvironmentRouter); app.use('/api/v2/workspace', v2WorkspaceRouter); // TODO: turn into plural route app.use('/api/v2/secret', v2SecretRouter); // stop supporting, TODO: revise app.use('/api/v2/secrets', v2SecretsRouter); app.use('/api/v2/service-token', v2ServiceTokenDataRouter); // TODO: turn into plural route app.use('/api/v2/api-key-data', v2APIKeyDataRouter); -app.use('/api/v2/environments', v2EnvironmentRouter); // api docs app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerFile)) diff --git a/backend/src/controllers/v1/integrationAuthController.ts b/backend/src/controllers/v1/integrationAuthController.ts index b2f93a8c2..5df8b4e91 100644 --- a/backend/src/controllers/v1/integrationAuthController.ts +++ b/backend/src/controllers/v1/integrationAuthController.ts @@ -3,7 +3,7 @@ import * as Sentry from '@sentry/node'; import axios from 'axios'; import { readFileSync } from 'fs'; import { IntegrationAuth, Integration } from '../../models'; -import { INTEGRATION_SET, INTEGRATION_OPTIONS, ENV_DEV } from '../../variables'; +import { INTEGRATION_SET, INTEGRATION_OPTIONS } from '../../variables'; import { IntegrationService } from '../../services'; import { getApps, revokeAccess } from '../../integrations'; diff --git a/backend/src/controllers/v2/environmentController.ts b/backend/src/controllers/v2/environmentController.ts index 77c4fcb59..1d1ffb6a6 100644 --- a/backend/src/controllers/v2/environmentController.ts +++ b/backend/src/controllers/v2/environmentController.ts @@ -1,6 +1,13 @@ import { Request, Response } from 'express'; import * as Sentry from '@sentry/node'; -import { Secret, ServiceToken, Workspace, Integration } from '../../models'; +import { + Secret, + ServiceToken, + Workspace, + Integration, + ServiceTokenData, +} from '../../models'; +import { SecretVersion } from '../../ee/models'; /** * Create new workspace environment named [environmentName] under workspace with id @@ -12,25 +19,24 @@ export const createWorkspaceEnvironment = async ( req: Request, res: Response ) => { - const { workspaceId, environmentName, environmentSlug } = req.body; + const { workspaceId } = req.params; + const { environmentName, environmentSlug } = req.body; try { - // atomic create the environment - const workspace = await Workspace.findOneAndUpdate( - { - _id: workspaceId, - 'environments.slug': { $ne: environmentSlug }, - 'environments.name': { $ne: environmentName }, - }, - { - $addToSet: { - environments: { name: environmentName, slug: environmentSlug }, - }, - } - ); - - if (!workspace) { - throw new Error('Failed to update workspace environment'); + const workspace = await Workspace.findById(workspaceId).exec(); + if ( + !workspace || + workspace?.environments.find( + ({ name, slug }) => slug === environmentSlug || environmentName === name + ) + ) { + throw new Error('Failed to create workspace environment'); } + + workspace?.environments.push({ + name: environmentName.toLowerCase(), + slug: environmentSlug.toLowerCase(), + }); + await workspace.save(); } catch (err) { Sentry.setUser({ email: req.user.email }); Sentry.captureException(err); @@ -60,8 +66,8 @@ export const renameWorkspaceEnvironment = async ( req: Request, res: Response ) => { - const { workspaceId, environmentName, environmentSlug, oldEnvironmentSlug } = - req.body; + const { workspaceId } = req.params; + const { environmentName, environmentSlug, oldEnvironmentSlug } = req.body; try { // user should pass both new slug and env name if (!environmentSlug || !environmentName) { @@ -69,25 +75,47 @@ export const renameWorkspaceEnvironment = async ( } // atomic update the env to avoid conflict - const workspace = await Workspace.findOneAndUpdate( - { _id: workspaceId, 'environments.slug': oldEnvironmentSlug }, - { - 'environments.$.name': environmentName, - 'environments.$.slug': environmentSlug, - } - ); + const workspace = await Workspace.findById(workspaceId).exec(); if (!workspace) { - throw new Error('Failed to update workspace'); + throw new Error('Failed to create workspace environment'); } + const isEnvExist = workspace.environments.some( + ({ name, slug }) => + slug !== oldEnvironmentSlug && + (name === environmentName || slug === environmentSlug) + ); + if (isEnvExist) { + throw new Error('Invalid environment given'); + } + + const envIndex = workspace?.environments.findIndex( + ({ slug }) => slug === oldEnvironmentSlug + ); + if (envIndex === -1) { + throw new Error('Invalid environment given'); + } + + workspace.environments[envIndex].name = environmentName.toLowerCase(); + workspace.environments[envIndex].slug = environmentSlug.toLowerCase(); + + await workspace.save(); await Secret.updateMany( { workspace: workspaceId, environment: oldEnvironmentSlug }, { environment: environmentSlug } ); + await SecretVersion.updateMany( + { workspace: workspaceId, environment: oldEnvironmentSlug }, + { environment: environmentSlug } + ); await ServiceToken.updateMany( { workspace: workspaceId, environment: oldEnvironmentSlug }, { environment: environmentSlug } ); + await ServiceTokenData.updateMany( + { workspace: workspaceId, environment: oldEnvironmentSlug }, + { environment: environmentSlug } + ); await Integration.updateMany( { workspace: workspaceId, environment: oldEnvironmentSlug }, { environment: environmentSlug } @@ -120,37 +148,46 @@ export const deleteWorkspaceEnvironment = async ( req: Request, res: Response ) => { - const { workspaceId, environmentSlug } = req.body; + const { workspaceId } = req.params; + const { environmentSlug } = req.body; try { - // atomic delete the env in the workspacce - const workspace = await Workspace.findOneAndUpdate( - { _id: workspaceId }, - { - $pull: { - environments: { - slug: environmentSlug, - }, - }, - } - ); + // atomic update the env to avoid conflict + const workspace = await Workspace.findById(workspaceId).exec(); if (!workspace) { - throw new Error('Failed to delete workspace environment'); + throw new Error('Failed to create workspace environment'); } + const envIndex = workspace?.environments.findIndex( + ({ slug }) => slug === environmentSlug + ); + if (envIndex === -1) { + throw new Error('Invalid environment given'); + } + + workspace.environments.splice(envIndex, 1); + await workspace.save(); + // clean up await Secret.deleteMany({ workspace: workspaceId, environment: environmentSlug, }); + await SecretVersion.deleteMany({ + workspace: workspaceId, + environment: environmentSlug, + }); await ServiceToken.deleteMany({ workspace: workspaceId, environment: environmentSlug, }); + await ServiceTokenData.deleteMany({ + workspace: workspaceId, + environment: environmentSlug, + }); await Integration.deleteMany({ workspace: workspaceId, environment: environmentSlug, }); - } catch (err) { Sentry.setUser({ email: req.user.email }); Sentry.captureException(err); diff --git a/backend/src/ee/models/secretVersion.ts b/backend/src/ee/models/secretVersion.ts index 616d44fbd..a4fec0a39 100644 --- a/backend/src/ee/models/secretVersion.ts +++ b/backend/src/ee/models/secretVersion.ts @@ -2,10 +2,6 @@ import { Schema, model, Types } from 'mongoose'; import { SECRET_SHARED, SECRET_PERSONAL, - ENV_DEV, - ENV_TESTING, - ENV_STAGING, - ENV_PROD } from '../../variables'; export interface ISecretVersion { @@ -56,7 +52,6 @@ const secretVersionSchema = new Schema( }, environment: { type: String, - enum: [ENV_DEV, ENV_TESTING, ENV_STAGING, ENV_PROD], required: true }, isDeleted: { // consider removing field diff --git a/backend/src/models/integration.ts b/backend/src/models/integration.ts index 0b184ed67..98e4934d9 100644 --- a/backend/src/models/integration.ts +++ b/backend/src/models/integration.ts @@ -9,7 +9,7 @@ import { export interface IIntegration { _id: Types.ObjectId; workspace: Types.ObjectId; - environment: 'dev' | 'test' | 'staging' | 'prod'; + environment: string; isActive: boolean; app: string; target: string; diff --git a/backend/src/routes/v2/environment.ts b/backend/src/routes/v2/environment.ts index 592a51248..924db18fc 100644 --- a/backend/src/routes/v2/environment.ts +++ b/backend/src/routes/v2/environment.ts @@ -1,4 +1,4 @@ -import express from 'express'; +import express, { Response, Request } from 'express'; const router = express.Router(); import { body, param } from 'express-validator'; import { environmentController } from '../../controllers/v2'; @@ -7,14 +7,15 @@ import { requireWorkspaceAuth, validateRequest, } from '../../middleware'; -import { ADMIN, MEMBER, COMPLETED, GRANTED } from '../../variables'; +import { ADMIN, MEMBER } from '../../variables'; router.post( - '/:workspaceId', - requireAuth, + '/:workspaceId/environments', + requireAuth({ + acceptedAuthModes: ['jwt'], + }), requireWorkspaceAuth({ acceptedRoles: [ADMIN, MEMBER], - acceptedStatuses: [COMPLETED, GRANTED], }), param('workspaceId').exists().trim(), body('environmentSlug').exists().trim(), @@ -24,11 +25,12 @@ router.post( ); router.put( - '/:workspaceId', - requireAuth, + '/:workspaceId/environments', + requireAuth({ + acceptedAuthModes: ['jwt'], + }), requireWorkspaceAuth({ acceptedRoles: [ADMIN, MEMBER], - acceptedStatuses: [COMPLETED, GRANTED], }), param('workspaceId').exists().trim(), body('environmentSlug').exists().trim(), @@ -39,11 +41,12 @@ router.put( ); router.delete( - '/:workspaceId', - requireAuth, + '/:workspaceId/environments', + requireAuth({ + acceptedAuthModes: ['jwt'], + }), requireWorkspaceAuth({ acceptedRoles: [ADMIN], - acceptedStatuses: [GRANTED], }), param('workspaceId').exists().trim(), body('environmentSlug').exists().trim(), diff --git a/backend/src/routes/v2/secrets.ts b/backend/src/routes/v2/secrets.ts index 29eac042f..4196cd819 100644 --- a/backend/src/routes/v2/secrets.ts +++ b/backend/src/routes/v2/secrets.ts @@ -18,7 +18,7 @@ import { router.post( '/', body('workspaceId').exists().isString().trim(), - body('environment').exists().isString().trim().isIn(['dev', 'staging', 'prod', 'test']), + body('environment').exists().isString().trim(), body('secrets') .exists() .custom((value) => { @@ -73,7 +73,7 @@ router.post( router.get( '/', query('workspaceId').exists().trim(), - query('environment').exists().trim().isIn(['dev', 'staging', 'prod', 'test']), + query('environment').exists().trim(), validateRequest, requireAuth({ acceptedAuthModes: ['jwt', 'serviceToken'] diff --git a/frontend/components/basic/Listbox.tsx b/frontend/components/basic/Listbox.tsx index 95e3f33c6..d315df2e6 100644 --- a/frontend/components/basic/Listbox.tsx +++ b/frontend/components/basic/Listbox.tsx @@ -69,7 +69,7 @@ export default function ListBox({ - `my-0.5 relative cursor-default select-none py-2 pl-10 pr-4 rounded-md ${ + `my-0.5 relative cursor-default select-none py-2 pl-10 pr-4 rounded-md capitalize ${ selected ? 'bg-white/10 text-gray-400 font-bold' : '' } ${ active && !selected diff --git a/frontend/components/basic/dialog/AddServiceTokenDialog.js b/frontend/components/basic/dialog/AddServiceTokenDialog.js index 177911973..a8c5c15a7 100644 --- a/frontend/components/basic/dialog/AddServiceTokenDialog.js +++ b/frontend/components/basic/dialog/AddServiceTokenDialog.js @@ -8,7 +8,6 @@ import nacl from "tweetnacl"; import addServiceToken from "~/pages/api/serviceToken/addServiceToken"; import getLatestFileKey from "~/pages/api/workspace/getLatestFileKey"; -import { envMapping } from "../../../public/data/frequentConstants"; import { decryptAssymmetric, encryptAssymmetric, @@ -34,11 +33,12 @@ const AddServiceTokenDialog = ({ workspaceId, workspaceName, serviceTokens, + environments, setServiceTokens }) => { const [serviceToken, setServiceToken] = useState(""); const [serviceTokenName, setServiceTokenName] = useState(""); - const [serviceTokenEnv, setServiceTokenEnv] = useState("Development"); + const [selectedServiceTokenEnv, setSelectedServiceTokenEnv] = useState(environments?.[0]); const [serviceTokenExpiresIn, setServiceTokenExpiresIn] = useState("1 day"); const [serviceTokenCopied, setServiceTokenCopied] = useState(false); const { t } = useTranslation(); @@ -66,7 +66,7 @@ const AddServiceTokenDialog = ({ let newServiceToken = await addServiceToken({ name: serviceTokenName, workspaceId, - environment: envMapping[serviceTokenEnv], + environment: selectedServiceTokenEnv.slug, expiresIn: expiryMapping[serviceTokenExpiresIn], encryptedKey: ciphertext, iv, @@ -101,155 +101,159 @@ const AddServiceTokenDialog = ({ }; return ( -
+
- + -
+
-
-
+
+
- {serviceToken == "" ? ( - + {serviceToken == '' ? ( + - {t("section-token:add-dialog.title", { + {t('section-token:add-dialog.title', { target: workspaceName, })} -
-
-

- {t("section-token:add-dialog.description")} +

+
+

+ {t('section-token:add-dialog.description')}

-
+
-
+
name)} + onChange={(envName) => + setSelectedServiceTokenEnv( + environments.find( + ({ name }) => envName === name + ) || { + name: 'unknown', + slug: 'unknown', + } + ) + } isFull={true} - text={`${t("common:environment")}: `} + text={`${t('common:environment')}: `} />
-
+
-
-
+
+
) : ( - + - {t("section-token:add-dialog.copy-service-token")} + {t('section-token:add-dialog.copy-service-token')} -
-
-

+

+
+

{t( - "section-token:add-dialog.copy-service-token-description" + 'section-token:add-dialog.copy-service-token-description' )}

-
-
+
+
-
+
{serviceToken}
-
+
- - {t("common:click-to-copy")} + + {t('common:click-to-copy')}
-
+
diff --git a/frontend/components/basic/dialog/AddEnvironmentDialog.tsx b/frontend/components/basic/dialog/AddUpdateEnvironmentDialog.tsx similarity index 87% rename from frontend/components/basic/dialog/AddEnvironmentDialog.tsx rename to frontend/components/basic/dialog/AddUpdateEnvironmentDialog.tsx index 83b578d81..9476dd8f6 100644 --- a/frontend/components/basic/dialog/AddEnvironmentDialog.tsx +++ b/frontend/components/basic/dialog/AddUpdateEnvironmentDialog.tsx @@ -12,15 +12,24 @@ type Props = { // on edit mode load up initial values initialValues?: FormFields; onClose: () => void; - onSubmit: (envName: string, envSlug: string) => void; + onCreateSubmit: (data: FormFields) => void; + onEditSubmit: (data: FormFields) => void; }; +// TODO: Migrate to better form management and validation. Preferable react-hook-form + yup /** * The dialog modal for when the user wants to create a new workspace * @param {*} param0 * @returns */ -export const AddEnvironmentDialog = ({ isOpen, onClose, onSubmit, initialValues, isEditMode }: Props) => { +export const AddUpdateEnvironmentDialog = ({ + isOpen, + onClose, + onCreateSubmit, + onEditSubmit, + initialValues, + isEditMode, +}: Props) => { const [formInput, setFormInput] = useState({ name: '', slug: '', @@ -39,7 +48,15 @@ export const AddEnvironmentDialog = ({ isOpen, onClose, onSubmit, initialValues, const onFormSubmit: FormEventHandler = (e) => { e.preventDefault(); - console.log(formInput); + const data = { + name: formInput.name.toLowerCase(), + slug: formInput.slug.toLowerCase(), + }; + if (isEditMode) { + onEditSubmit(data); + return; + } + onCreateSubmit(data); }; return ( @@ -81,7 +98,7 @@ export const AddEnvironmentDialog = ({ isOpen, onClose, onSubmit, initialValues,
onInputChange('name', val)} type='varName' value={formInput.name} @@ -112,6 +129,7 @@ export const AddEnvironmentDialog = ({ isOpen, onClose, onSubmit, initialValues, type='submit' color='mineshaft' text={isEditMode ? 'Update' : 'Create'} + active={formInput.name !== '' && formInput.slug !== ''} size='md' />
diff --git a/frontend/components/basic/dialog/DeleteActionModal.tsx b/frontend/components/basic/dialog/DeleteActionModal.tsx index cafd832ae..9a1b7a46a 100644 --- a/frontend/components/basic/dialog/DeleteActionModal.tsx +++ b/frontend/components/basic/dialog/DeleteActionModal.tsx @@ -1,4 +1,4 @@ -import { Fragment, useState } from 'react'; +import { Fragment, useEffect, useState } from 'react'; import { Dialog, Transition } from '@headlessui/react'; import InputField from '../InputField'; @@ -21,6 +21,10 @@ const DeleteActionModal = ({ }:Props) => { const [deleteInputField, setDeleteInputField] = useState("") + useEffect(() => { + setDeleteInputField(""); + }, [isOpen]); + return (
diff --git a/frontend/components/basic/table/EnvironmentsTable.tsx b/frontend/components/basic/table/EnvironmentsTable.tsx index 501b7f9bc..56536b679 100644 --- a/frontend/components/basic/table/EnvironmentsTable.tsx +++ b/frontend/components/basic/table/EnvironmentsTable.tsx @@ -1,15 +1,61 @@ -import { faPencil,faPlus,faX } from '@fortawesome/free-solid-svg-icons'; +import { faPencil, faPlus, faX } from '@fortawesome/free-solid-svg-icons'; -import { usePopUp } from '../../../hooks/usePopUp'; +import { usePopUp } from '../../../hooks/usePopUp'; import Button from '../buttons/Button'; -import {AddEnvironmentDialog} from '../dialog/AddEnvironmentDialog'; +import { AddUpdateEnvironmentDialog } from '../dialog/AddUpdateEnvironmentDialog'; import DeleteActionModal from '../dialog/DeleteActionModal'; -const EnvironmentTable = ({ data = [] }) => { - const { popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([ - 'createUpdateEnv', - 'deleteEnv', - ] as const); +type Env = { name: string; slug: string }; + +type Props = { + data: Env[]; + onCreateEnv: (arg0: Env) => Promise; + onUpdateEnv: (oldSlug: string, arg0: Env) => Promise; + onDeleteEnv: (slug: string) => Promise; +}; + +const EnvironmentTable = ({ + data = [], + onCreateEnv, + onDeleteEnv, + onUpdateEnv, +}: Props) => { + const { popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([ + 'createUpdateEnv', + 'deleteEnv', + ] as const); + + const onEnvCreateCB = async (env: Env) => { + try { + await onCreateEnv(env); + handlePopUpClose('createUpdateEnv'); + } catch (error) { + console.error(error); + } + }; + + const onEnvUpdateCB = async (env: Env) => { + try { + await onUpdateEnv( + (popUp.createUpdateEnv?.data as Pick)?.slug, + env + ); + handlePopUpClose('createUpdateEnv'); + } catch (error) { + console.error(error); + } + }; + + const onEnvDeleteCB = async () => { + try { + await onDeleteEnv( + (popUp.deleteEnv?.data as Pick)?.slug + ); + handlePopUpClose('deleteEnv'); + } catch (error) { + console.error(error); + } + }; return ( <> @@ -62,7 +108,9 @@ const EnvironmentTable = ({ data = [] }) => {
diff --git a/frontend/components/basic/table/ServiceTokenTable.tsx b/frontend/components/basic/table/ServiceTokenTable.tsx index 412a0dbfb..4d30b3013 100644 --- a/frontend/components/basic/table/ServiceTokenTable.tsx +++ b/frontend/components/basic/table/ServiceTokenTable.tsx @@ -3,7 +3,6 @@ import { faX } from '@fortawesome/free-solid-svg-icons'; import { useNotificationContext } from '~/components/context/Notifications/NotificationProvider'; import deleteServiceToken from "../../../pages/api/serviceToken/deleteServiceToken"; -import { reverseEnvMapping } from '../../../public/data/frequentConstants'; import guidGenerator from '../../utilities/randomId'; import Button from '../buttons/Button'; @@ -60,7 +59,7 @@ const ServiceTokenTable = ({ data, workspaceName, setServiceTokens }: ServiceTok {workspaceName} - {reverseEnvMapping[row.environment]} + {row.environment} {new Date(row.expiresAt).toUTCString()} diff --git a/frontend/components/integrations/Integration.tsx b/frontend/components/integrations/Integration.tsx index 11edf7cb7..73d6ee0dc 100644 --- a/frontend/components/integrations/Integration.tsx +++ b/frontend/components/integrations/Integration.tsx @@ -15,9 +15,7 @@ import getIntegrationApps from "../../pages/api/integrations/GetIntegrationApps" import updateIntegration from "../../pages/api/integrations/updateIntegration" import { contextNetlifyMapping, - envMapping, reverseContextNetlifyMapping, - reverseEnvMapping, } from "../../public/data/frequentConstants"; interface Integration { @@ -41,9 +39,7 @@ const Integration = ({ }: { integration: Integration; }) => { - const [integrationEnvironment, setIntegrationEnvironment] = useState( - reverseEnvMapping[integration.environment] - ); + const [integrationEnvironment, setIntegrationEnvironment] = useState(integration.environment); const [fileState, setFileState] = useState([]); const router = useRouter(); const [apps, setApps] = useState([]); // integration app objects @@ -199,9 +195,9 @@ const Integration = ({ const siteApp = apps.find((app) => app.name === integrationApp); // obj or undefined const siteId = siteApp?.siteId ? siteApp.siteId : null; - const result = await updateIntegration({ + await updateIntegration({ integrationId: integration._id, - environment: envMapping[integrationEnvironment], + environment: integrationEnvironment, app: integrationApp, isActive: true, target: integrationTarget ? integrationTarget.toLowerCase() : null, diff --git a/frontend/components/utilities/secrets/downloadDotEnv.ts b/frontend/components/utilities/secrets/downloadDotEnv.ts index dbb29497b..4a84aa17a 100644 --- a/frontend/components/utilities/secrets/downloadDotEnv.ts +++ b/frontend/components/utilities/secrets/downloadDotEnv.ts @@ -1,4 +1,3 @@ -import { envMapping } from "../../../public/data/frequentConstants"; import checkOverrides from './checkOverrides'; @@ -39,7 +38,7 @@ const downloadDotEnv = async ({ data, env }: { data: SecretDataProps[]; env: str const fileDownloadUrl = URL.createObjectURL(blob); const alink = document.createElement('a'); alink.href = fileDownloadUrl; - alink.download = envMapping[env] + '.env'; + alink.download = env + '.env'; alink.click(); } diff --git a/frontend/components/utilities/secrets/getSecretsForProject.ts b/frontend/components/utilities/secrets/getSecretsForProject.ts index 81de27be0..629ac98ee 100644 --- a/frontend/components/utilities/secrets/getSecretsForProject.ts +++ b/frontend/components/utilities/secrets/getSecretsForProject.ts @@ -1,8 +1,6 @@ import getSecrets from '~/pages/api/files/GetSecrets'; import getLatestFileKey from '~/pages/api/workspace/getLatestFileKey'; -import { envMapping } from '../../../public/data/frequentConstants'; - const { decryptAssymmetric, decryptSymmetric @@ -35,7 +33,7 @@ interface SecretProps { } interface FunctionProps { - env: keyof typeof envMapping; + env: string; setIsKeyAvailable: any; setData: any; workspaceId: string; @@ -58,7 +56,7 @@ const getSecretsForProject = async ({ try { let encryptedSecrets; try { - encryptedSecrets = await getSecrets(workspaceId, envMapping[env]); + encryptedSecrets = await getSecrets(workspaceId, env); } catch (error) { console.log('ERROR: Not able to access the latest version of secrets'); } diff --git a/frontend/pages/api/environments/createEnvironment.ts b/frontend/pages/api/environments/createEnvironment.ts new file mode 100644 index 000000000..2d0ecfa37 --- /dev/null +++ b/frontend/pages/api/environments/createEnvironment.ts @@ -0,0 +1,29 @@ +import SecurityClient from '~/utilities/SecurityClient'; + +type NewEnvironmentInfo = { + environmentSlug: string; + environmentName: string; +}; + +/** + * This route deletes a specified workspace. + * @param {*} workspaceId + * @returns + */ +const createEnvironment = (workspaceId:string, newEnv: NewEnvironmentInfo) => { + return SecurityClient.fetchCall(`/api/v2/workspace/${workspaceId}/environments`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(newEnv) + }).then(async (res) => { + if (res && res.status == 200) { + return res; + } else { + console.log('Failed to create environment'); + } + }); +}; + +export default createEnvironment; diff --git a/frontend/pages/api/environments/deleteEnvironment.ts b/frontend/pages/api/environments/deleteEnvironment.ts new file mode 100644 index 000000000..de8611532 --- /dev/null +++ b/frontend/pages/api/environments/deleteEnvironment.ts @@ -0,0 +1,26 @@ +import SecurityClient from '~/utilities/SecurityClient'; +/** + * This route deletes a specified env. + * @param {*} workspaceId + * @returns + */ +const deleteEnvironment = (workspaceId: string, environmentSlug: string) => { + return SecurityClient.fetchCall( + `/api/v2/workspace/${workspaceId}/environments`, + { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ environmentSlug }), + } + ).then(async (res) => { + if (res && res.status == 200) { + return res; + } else { + console.log('Failed to delete environment'); + } + }); +}; + +export default deleteEnvironment; diff --git a/frontend/pages/api/environments/updateEnvironment.ts b/frontend/pages/api/environments/updateEnvironment.ts new file mode 100644 index 000000000..65fb449a8 --- /dev/null +++ b/frontend/pages/api/environments/updateEnvironment.ts @@ -0,0 +1,33 @@ +import SecurityClient from '~/utilities/SecurityClient'; + +type EnvironmentInfo = { + oldEnvironmentSlug: string; + environmentSlug: string; + environmentName: string; +}; + +/** + * This route updates a specified environment. + * @param {*} workspaceId + * @returns + */ +const updateEnvironment = (workspaceId: string, env: EnvironmentInfo) => { + return SecurityClient.fetchCall( + `/api/v2/workspace/${workspaceId}/environments`, + { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(env), + } + ).then(async (res) => { + if (res && res.status == 200) { + return res; + } else { + console.log('Failed to update environment'); + } + }); +}; + +export default updateEnvironment; diff --git a/frontend/pages/api/workspace/getWorkspaces.ts b/frontend/pages/api/workspace/getWorkspaces.ts index 1bbb42c7a..d77c4c431 100644 --- a/frontend/pages/api/workspace/getWorkspaces.ts +++ b/frontend/pages/api/workspace/getWorkspaces.ts @@ -5,6 +5,7 @@ interface Workspace { _id: string; name: string; organization: string; + environments: Array<{name:string, slug:string}> } /** diff --git a/frontend/pages/dashboard/[id].tsx b/frontend/pages/dashboard/[id].tsx index 4ce91002d..e20ee73c7 100644 --- a/frontend/pages/dashboard/[id].tsx +++ b/frontend/pages/dashboard/[id].tsx @@ -2,7 +2,7 @@ import { Fragment, useCallback, useEffect, useState } from 'react'; import Head from 'next/head'; import Image from 'next/image'; import { useRouter } from 'next/router'; -import { useTranslation } from "next-i18next"; +import { useTranslation } from 'next-i18next'; import { faArrowDownAZ, faArrowDownZA, @@ -34,7 +34,6 @@ import getSecretsForProject from '~/components/utilities/secrets/getSecretsForPr import { getTranslatedServerSideProps } from '~/components/utilities/withTranslateProps'; import guidGenerator from '~/utilities/randomId'; -import { envMapping, reverseEnvMapping } from '../../public/data/frequentConstants'; import addSecrets from '../api/files/AddSecrets'; import deleteSecrets from '../api/files/DeleteSecrets'; import updateSecrets from '../api/files/UpdateSecrets'; @@ -43,6 +42,10 @@ import checkUserAction from '../api/userActions/checkUserAction'; import registerUserAction from '../api/userActions/registerUserAction'; import getWorkspaces from '../api/workspace/getWorkspaces'; +type WorkspaceEnv = { + name: string; + slug: string; +}; interface SecretDataProps { type: 'personal' | 'shared'; @@ -68,7 +71,7 @@ interface SnapshotProps { secretVersions: { id: string; pos: number; - type: "personal" | "shared"; + type: 'personal' | 'shared'; environment: string; key: string; value: string; @@ -99,14 +102,11 @@ function findDuplicates(arr: any[]) { */ export default function Dashboard() { const [data, setData] = useState(); - const [initialData, setInitialData] = useState([]); + const [initialData, setInitialData] = useState([]); const [buttonReady, setButtonReady] = useState(false); const router = useRouter(); - const [workspaceId, setWorkspaceId] = useState(''); const [blurred, setBlurred] = useState(true); const [isKeyAvailable, setIsKeyAvailable] = useState(true); - const [env, setEnv] = useState('Development'); - const [snapshotEnv, setSnapshotEnv] = useState('Development'); const [isNew, setIsNew] = useState(false); const [isLoading, setIsLoading] = useState(false); const [searchKeys, setSearchKeys] = useState(''); @@ -114,7 +114,7 @@ export default function Dashboard() { const [sortMethod, setSortMethod] = useState('alphabetical'); const [checkDocsPopUpVisible, setCheckDocsPopUpVisible] = useState(false); const [hasUserEverPushed, setHasUserEverPushed] = useState(false); - const [sidebarSecretId, toggleSidebar] = useState("None"); + const [sidebarSecretId, toggleSidebar] = useState('None'); const [PITSidebarOpen, togglePITSidebar] = useState(false); const [sharedToHide, setSharedToHide] = useState([]); const [snapshotData, setSnapshotData] = useState(); @@ -123,6 +123,16 @@ export default function Dashboard() { const { t } = useTranslation(); const { createNotification } = useNotificationContext(); + const workspaceId = router.query.id as string; + const [workspaceEnvs, setWorkspaceEnvs] = useState([]); + + const [selectedSnapshotEnv, setSelectedSnapshotEnv] = + useState(); + const [selectedEnv, setSelectedEnv] = useState({ + name: '', + slug: '', + }); + // #TODO: fix save message for changing reroutes // const beforeRouteHandler = (url) => { // const warningText = @@ -169,25 +179,37 @@ export default function Dashboard() { useEffect(() => { (async () => { try { - const tempNumSnapshots = await getProjectSercetSnapshotsCount({ workspaceId: String(router.query.id) }) + const tempNumSnapshots = await getProjectSercetSnapshotsCount({ + workspaceId, + }); setNumSnapshots(tempNumSnapshots); const userWorkspaces = await getWorkspaces(); - const listWorkspaces = userWorkspaces.map((workspace) => workspace._id); - if ( - !listWorkspaces.includes(router.asPath.split('/')[2]) - ) { - router.push('/dashboard/' + listWorkspaces[0]); + const workspace = userWorkspaces.find( + (workspace) => workspace._id === workspaceId + ); + if (!workspace) { + router.push('/dashboard/' + userWorkspaces?.[0]?._id); } + setWorkspaceEnvs(workspace?.environments || []); + // set env + const env = workspace?.environments?.[0] || { + name: 'unknown', + slug: 'unkown', + }; + setSelectedEnv(env); + setSelectedSnapshotEnv(env); const user = await getUser(); setIsNew( - (Date.parse(String(new Date())) - Date.parse(user.createdAt)) / 60000 < 3 + (Date.parse(String(new Date())) - Date.parse(user.createdAt)) / + 60000 < + 3 ? true : false ); const userAction = await checkUserAction({ - action: 'first_time_secrets_pushed' + action: 'first_time_secrets_pushed', }); setHasUserEverPushed(userAction ? true : false); } catch (error) { @@ -202,13 +224,12 @@ export default function Dashboard() { try { setIsLoading(true); setBlurred(true); - setWorkspaceId(String(router.query.id)); - + // ENV const dataToSort = await getSecretsForProject({ - env, + env: selectedEnv.slug, setIsKeyAvailable, setData, - workspaceId: String(router.query.id) + workspaceId, }); setInitialData(dataToSort); reorderRows(dataToSort); @@ -230,7 +251,7 @@ export default function Dashboard() { } })(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [env]); + }, [selectedEnv]); const addRow = () => { setIsNew(false); @@ -243,17 +264,17 @@ export default function Dashboard() { value: '', type: 'shared', comment: '', - } + }, ]); }; /** * This function add an ovverrided version of a certain secret to the current user - * @param {object} obj + * @param {object} obj * @param {string} obj.id - if of this secret that is about to be overriden * @param {string} obj.keyName - key name of this secret * @param {string} obj.value - value of this secret - * @param {string} obj.pos - position of this secret on the dashboard + * @param {string} obj.pos - position of this secret on the dashboard */ const addOverride = ({ id, keyName, value, pos, comment }: overrideProps) => { setIsNew(false); @@ -265,20 +286,32 @@ export default function Dashboard() { key: keyName, value: value, type: 'personal', - comment: comment - } + comment: comment, + }, ]; - sortValuesHandler(tempdata, sortMethod == "alhpabetical" ? "-alphabetical" : "alphabetical"); + sortValuesHandler( + tempdata, + sortMethod == 'alhpabetical' ? '-alphabetical' : 'alphabetical' + ); }; - const deleteRow = ({ ids, secretName }: { ids: string[]; secretName: string; }) => { + const deleteRow = ({ + ids, + secretName, + }: { + ids: string[]; + secretName: string; + }) => { setButtonReady(true); - toggleSidebar("None"); + toggleSidebar('None'); createNotification({ text: `${secretName} has been deleted. Remember to save changes.`, - type: 'error' + type: 'error', }); - sortValuesHandler(data!.filter((row: SecretDataProps) => !ids.includes(row.id)), sortMethod == "alhpabetical" ? "-alphabetical" : "alphabetical"); + sortValuesHandler( + data!.filter((row: SecretDataProps) => !ids.includes(row.id)), + sortMethod == 'alhpabetical' ? '-alphabetical' : 'alphabetical' + ); }; /** @@ -289,15 +322,30 @@ export default function Dashboard() { setButtonReady(true); // find which shared secret corresponds to the overriden version - const sharedVersionOfOverride = data!.filter(secret => secret.type == "shared" && secret.key == data!.filter(row => row.id == id)[0]?.key)[0]?.id; - + const sharedVersionOfOverride = data!.filter( + (secret) => + secret.type == 'shared' && + secret.key == data!.filter((row) => row.id == id)[0]?.key + )[0]?.id; + // change the sidebar to this shared secret; and unhide it - toggleSidebar(sharedVersionOfOverride) - setSharedToHide(sharedToHide!.filter(tempId => tempId != sharedVersionOfOverride)) + toggleSidebar(sharedVersionOfOverride); + setSharedToHide( + sharedToHide!.filter((tempId) => tempId != sharedVersionOfOverride) + ); // resort secrets - const tempData = data!.filter((row: SecretDataProps) => !(row.key == data!.filter(row => row.id == id)[0]?.key && row.type == 'personal')) - sortValuesHandler(tempData, sortMethod == "alhpabetical" ? "-alphabetical" : "alphabetical") + const tempData = data!.filter( + (row: SecretDataProps) => + !( + row.key == data!.filter((row) => row.id == id)[0]?.key && + row.type == 'personal' + ) + ); + sortValuesHandler( + tempData, + sortMethod == 'alhpabetical' ? '-alphabetical' : 'alphabetical' + ); }; const modifyValue = (value: string, pos: number) => { @@ -351,59 +399,97 @@ export default function Dashboard() { const obj = Object.assign( {}, - ...newData!.map((row: SecretDataProps) => ({ [row.type.charAt(0) + row.key]: [row.value, row.comment ?? ''] })) + ...newData!.map((row: SecretDataProps) => ({ + [row.type.charAt(0) + row.key]: [row.value, row.comment ?? ''], + })) ); // Checking if any of the secret keys start with a number - if so, don't do anything const nameErrors = !Object.keys(obj) .map((key) => !isNaN(Number(key[0].charAt(0)))) .every((v) => v === false); - const duplicatesExist = findDuplicates(data!.map((item: SecretDataProps) => item.key + item.type)).length > 0; + const duplicatesExist = + findDuplicates(data!.map((item: SecretDataProps) => item.key + item.type)) + .length > 0; if (nameErrors) { return createNotification({ text: 'Solve all name errors before saving secrets.', - type: 'error' + type: 'error', }); } if (duplicatesExist) { return createNotification({ text: 'Remove duplicated secret names before saving.', - type: 'error' + type: 'error', }); } // Once "Save changes" is clicked, disable that button setButtonReady(false); - const secretsToBeDeleted - = initialData - .filter(initDataPoint => !newData!.map(newDataPoint => newDataPoint.id).includes(initDataPoint.id)) - .map(secret => secret.id); + const secretsToBeDeleted = initialData + .filter( + (initDataPoint) => + !newData! + .map((newDataPoint) => newDataPoint.id) + .includes(initDataPoint.id) + ) + .map((secret) => secret.id); - const secretsToBeAdded - = newData! - .filter(newDataPoint => !initialData.map(initDataPoint => initDataPoint.id).includes(newDataPoint.id)); + const secretsToBeAdded = newData!.filter( + (newDataPoint) => + !initialData + .map((initDataPoint) => initDataPoint.id) + .includes(newDataPoint.id) + ); + + const secretsToBeUpdated = newData!.filter((newDataPoint) => + initialData + .filter( + (initDataPoint) => + newData! + .map((newDataPoint) => newDataPoint.id) + .includes(initDataPoint.id) && + (newData!.filter( + (newDataPoint) => newDataPoint.id == initDataPoint.id + )[0].value != initDataPoint.value || + newData!.filter( + (newDataPoint) => newDataPoint.id == initDataPoint.id + )[0].key != initDataPoint.key || + newData!.filter( + (newDataPoint) => newDataPoint.id == initDataPoint.id + )[0].comment != initDataPoint.comment) + ) + .map((secret) => secret.id) + .includes(newDataPoint.id) + ); - const secretsToBeUpdated - = newData!.filter(newDataPoint => initialData - .filter(initDataPoint => newData!.map(newDataPoint => newDataPoint.id).includes(initDataPoint.id) - && (newData!.filter(newDataPoint => newDataPoint.id == initDataPoint.id)[0].value != initDataPoint.value - || newData!.filter(newDataPoint => newDataPoint.id == initDataPoint.id)[0].key != initDataPoint.key - || newData!.filter(newDataPoint => newDataPoint.id == initDataPoint.id)[0].comment != initDataPoint.comment)) - .map(secret => secret.id).includes(newDataPoint.id)); - if (secretsToBeDeleted.length > 0) { await deleteSecrets({ secretIds: secretsToBeDeleted }); } + // ENV if (secretsToBeAdded.length > 0) { - const secrets = await encryptSecrets({ secretsToEncrypt: secretsToBeAdded, workspaceId, env: envMapping[env] }) - secrets && await addSecrets({ secrets, env: envMapping[env], workspaceId }); + const secrets = await encryptSecrets({ + secretsToEncrypt: secretsToBeAdded, + workspaceId, + env: selectedEnv.slug, + }); + secrets && + (await addSecrets({ + secrets, + env: selectedEnv.slug, + workspaceId, + })); } if (secretsToBeUpdated.length > 0) { - const secrets = await encryptSecrets({ secretsToEncrypt: secretsToBeUpdated, workspaceId, env: envMapping[env] }) - secrets && await updateSecrets({ secrets }); + const secrets = await encryptSecrets({ + secretsToEncrypt: secretsToBeUpdated, + workspaceId, + env: selectedEnv.slug, + }); + secrets && (await updateSecrets({ secrets })); } // If this user has never saved environment variables before, show them a prompt to read docs @@ -425,299 +511,417 @@ export default function Dashboard() { setBlurred(!blurred); }; - const sortValuesHandler = (dataToSort: SecretDataProps[] | 1, specificSortMethod?: 'alphabetical' | '-alphabetical') => { - const howToSort = specificSortMethod == undefined ? sortMethod : specificSortMethod; + const sortValuesHandler = ( + dataToSort: SecretDataProps[] | 1, + specificSortMethod?: 'alphabetical' | '-alphabetical' + ) => { + const howToSort = + specificSortMethod == undefined ? sortMethod : specificSortMethod; const sortedData = (dataToSort != 1 ? dataToSort : data)! - .sort((a, b) => - howToSort == 'alphabetical' - ? a.key.localeCompare(b.key) - : b.key.localeCompare(a.key) - ) - .map((item: SecretDataProps, index: number) => { - return { - ...item, - pos: index - }; - }); + .sort((a, b) => + howToSort == 'alphabetical' + ? a.key.localeCompare(b.key) + : b.key.localeCompare(a.key) + ) + .map((item: SecretDataProps, index: number) => { + return { + ...item, + pos: index, + }; + }); setData(sortedData); }; - - const deleteCertainRow = ({ ids, secretName }: { ids: string[]; secretName: string; }) => { - deleteRow({ids, secretName}); + + const deleteCertainRow = ({ + ids, + secretName, + }: { + ids: string[]; + secretName: string; + }) => { + deleteRow({ ids, secretName }); }; return data ? ( -
+
- {t("common:head-title", { title: t("dashboard:title") })} - - - - + {t('common:head-title', { title: t('dashboard:title') })} + + + + -
- {sidebarSecretId != "None" && row.key == data.filter(row => row.id == sidebarSecretId)[0]?.key)} - modifyKey={listenChangeKey} - modifyValue={listenChangeValue} - modifyComment={listenChangeComment} - addOverride={addOverride} - deleteOverride={deleteOverride} - buttonReady={buttonReady} - savePush={savePush} - sharedToHide={sharedToHide} - setSharedToHide={setSharedToHide} - deleteRow={deleteCertainRow} - />} - {PITSidebarOpen && } -
- +
+ {sidebarSecretId != 'None' && ( + + row.key == + data.filter((row) => row.id == sidebarSecretId)[0]?.key + )} + modifyKey={listenChangeKey} + modifyValue={listenChangeValue} + modifyComment={listenChangeComment} + addOverride={addOverride} + deleteOverride={deleteOverride} + buttonReady={buttonReady} + savePush={savePush} + sharedToHide={sharedToHide} + setSharedToHide={setSharedToHide} + deleteRow={deleteCertainRow} + /> + )} + {PITSidebarOpen && ( + + )} +
+ {checkDocsPopUpVisible && ( )} -
- {snapshotData && -
-
} -
-
-

{snapshotData ? "Secret Snapshot" : t("dashboard:title")}

- {snapshotData && {new Date(snapshotData.createdAt).toLocaleString()}} +
+ {snapshotData && ( +
+
+ )} +
+
+

{snapshotData ? 'Secret Snapshot' : t('dashboard:title')}

+ {snapshotData && ( + + {new Date(snapshotData.createdAt).toLocaleString()} + + )}
{!snapshotData && data?.length == 0 && ( name)} + onChange={(envName) => + setSelectedEnv( + workspaceEnvs.find(({ name }) => envName === name) || { + name: 'unknown', + slug: 'unknown', + } + ) + } /> )}
-
+
{(data?.length !== 0 || buttonReady) && !snapshotData && (
)} - {snapshotData &&
-
} + setSnapshotData(undefined); + createNotification({ + text: `Rollback has been performed successfully.`, + type: 'success', + }); + }} + color='primary' + size='md' + active={buttonReady} + /> +
+ )}
-
-
-
+
+
+
{(snapshotData || data?.length !== 0) && ( <> - {!snapshotData - ? - : } -
+ {!snapshotData ? ( + name)} + onChange={(envName) => + setSelectedEnv( + workspaceEnvs.find( + ({ name }) => envName === name + ) || { + name: 'unknown', + slug: 'unknown', + } + ) + } + /> + ) : ( + name)} + onChange={(envName) => + setSelectedSnapshotEnv( + workspaceEnvs.find( + ({ name }) => envName === name + ) || { + name: 'unknown', + slug: 'unknown', + } + ) + } + /> + )} +
setSearchKeys(e.target.value)} - placeholder={String(t("dashboard:search-keys"))} + placeholder={String(t('dashboard:search-keys'))} />
- {!snapshotData &&
-
} - {!snapshotData &&
- -
} -
+ {!snapshotData && ( +
+
+ )} + {!snapshotData && ( +
+ +
+ )} +
- {!snapshotData &&
-
} + {!snapshotData && ( +
+
+ )} )}
{isLoading ? ( -
- infisical loading indicator -
- ) : ( - data?.length !== 0 ? ( -
+
+ infisical loading indicator +
+ ) : data?.length !== 0 ? ( +
-
- {!snapshotData && data?.filter(row => row.key?.toUpperCase().includes(searchKeys.toUpperCase())) - .filter(row => !(sharedToHide.includes(row.id) && row.type == 'shared')).map((keyPair) => ( - item.key + item.type) - )?.includes(keyPair.key + keyPair.type)} - toggleSidebar={toggleSidebar} - sidebarSecretId={sidebarSecretId} - isSnapshot={false} - /> - ))} - {snapshotData && snapshotData.secretVersions?.sort((a, b) => a.key.localeCompare(b.key)) - .filter(row => reverseEnvMapping[row.environment] == snapshotEnv) - .filter(row => row.key.toUpperCase().includes(searchKeys.toUpperCase())) - .filter(row => !(snapshotData.secretVersions?.filter(row => (snapshotData.secretVersions - ?.map((item) => item.key) - .filter( - (item, index) => - index !== - snapshotData.secretVersions?.map((item) => item.key).indexOf(item) - ).includes(row.key) && row.type == 'shared'))?.map((item) => item.id).includes(row.id) && row.type == 'shared')).map((keyPair) => ( - item.key + item.type) - )?.includes(keyPair.key + keyPair.type)} - toggleSidebar={toggleSidebar} - sidebarSecretId={sidebarSecretId} - isSnapshot={true} - /> - ))} +
+ {!snapshotData && + data + ?.filter((row) => + row.key + ?.toUpperCase() + .includes(searchKeys.toUpperCase()) + ) + .filter( + (row) => + !( + sharedToHide.includes(row.id) && + row.type == 'shared' + ) + ) + .map((keyPair) => ( + item.key + item.type) + )?.includes(keyPair.key + keyPair.type)} + toggleSidebar={toggleSidebar} + sidebarSecretId={sidebarSecretId} + isSnapshot={false} + /> + ))} + {snapshotData && + snapshotData.secretVersions + ?.sort((a, b) => a.key.localeCompare(b.key)) + .filter( + (row) => + row.environment == selectedSnapshotEnv?.slug + ) + .filter((row) => + row.key + .toUpperCase() + .includes(searchKeys.toUpperCase()) + ) + .filter( + (row) => + !( + snapshotData.secretVersions + ?.filter( + (row) => + snapshotData.secretVersions + ?.map((item) => item.key) + .filter( + (item, index) => + index !== + snapshotData.secretVersions + ?.map((item) => item.key) + .indexOf(item) + ) + .includes(row.key) && row.type == 'shared' + ) + ?.map((item) => item.id) + .includes(row.id) && row.type == 'shared' + ) + ) + .map((keyPair) => ( + item.key + item.type) + )?.includes(keyPair.key + keyPair.type)} + toggleSidebar={toggleSidebar} + sidebarSecretId={sidebarSecretId} + isSnapshot={true} + /> + ))}
- {!snapshotData &&
- -
} + {!snapshotData && ( +
+ +
+ )}
) : ( -
+
{isKeyAvailable && !snapshotData && ( )} - { - (!isKeyAvailable && ( - <> - -

- To view this file, contact your administrator for - permission. -

-

- They need to grant you access in the team tab. -

- - ))} + {!isKeyAvailable && ( + <> + +

+ To view this file, contact your administrator for + permission. +

+

+ They need to grant you access in the team tab. +

+ + )}
- ))} + )}
) : ( -
-
+
+
loading animation
); @@ -766,4 +969,4 @@ export default function Dashboard() { Dashboard.requireAuth = true; -export const getServerSideProps = getTranslatedServerSideProps(["dashboard"]); +export const getServerSideProps = getTranslatedServerSideProps(['dashboard']); diff --git a/frontend/pages/settings/project/[id].js b/frontend/pages/settings/project/[id].js deleted file mode 100644 index 3449df346..000000000 --- a/frontend/pages/settings/project/[id].js +++ /dev/null @@ -1,306 +0,0 @@ -import { useEffect, useRef, useState } from "react"; -import Head from "next/head"; -import { useRouter } from "next/router"; -import { useTranslation } from "next-i18next"; -import { faCheck, faCopy, faPlus } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; - -import Button from "~/components/basic/buttons/Button"; -import AddServiceTokenDialog from "~/components/basic/dialog/AddServiceTokenDialog"; -import InputField from "~/components/basic/InputField"; -import ServiceTokenTable from "~/components/basic/table/ServiceTokenTable.tsx"; -import NavHeader from "~/components/navigation/NavHeader"; -import { getTranslatedServerSideProps } from "~/utilities/withTranslateProps"; - -import getServiceTokens from "../../api/serviceToken/getServiceTokens"; -import deleteWorkspace from "../../api/workspace/deleteWorkspace"; -import getWorkspaces from "../../api/workspace/getWorkspaces"; -import renameWorkspace from "../../api/workspace/renameWorkspace"; - - -export default function SettingsBasic() { - const [buttonReady, setButtonReady] = useState(false); - const router = useRouter(); - const [workspaceName, setWorkspaceName] = useState(""); - const [serviceTokens, setServiceTokens] = useState([]); - const [workspaceToBeDeletedName, setWorkspaceToBeDeletedName] = useState(""); - const [workspaceId, setWorkspaceId] = useState(""); - const [isAddOpen, setIsAddOpen] = useState(false); - let [isAddServiceTokenDialogOpen, setIsAddServiceTokenDialogOpen] = - useState(false); - const [projectIdCopied, setProjectIdCopied] = useState(false); - - const { t } = useTranslation(); - - /** - * This function copies the project id to the clipboard - */ - function copyToClipboard() { - // const copyText = document.getElementById('myInput') as HTMLInputElement; - const copyText = document.getElementById('myInput') - - if (copyText) { - copyText.select(); - copyText.setSelectionRange(0, 99999); // For mobile devices - - navigator.clipboard.writeText(copyText.value); - - setProjectIdCopied(true); - setTimeout(() => setProjectIdCopied(false), 2000); - } - } - - useEffect(async () => { - let userWorkspaces = await getWorkspaces(); - userWorkspaces.map((userWorkspace) => { - if (userWorkspace._id == router.query.id) { - setWorkspaceName(userWorkspace.name); - } - }); - let tempServiceTokens = await getServiceTokens({ - workspaceId: router.query.id, - }); - setServiceTokens(tempServiceTokens); - }, []); - - const modifyWorkspaceName = (newName) => { - setButtonReady(true); - setWorkspaceName(newName); - }; - - const submitChanges = (newWorkspaceName) => { - renameWorkspace(router.query.id, newWorkspaceName); - setButtonReady(false); - }; - - useEffect(async () => { - setWorkspaceId(router.query.id); - }, []); - - function closeAddModal() { - setIsAddOpen(false); - } - - function openAddModal() { - setIsAddOpen(true); - } - - const closeAddServiceTokenModal = () => { - setIsAddServiceTokenDialogOpen(false); - }; - - /** - * This function deleted a workspace. - * It first checks if there is more than one workspace aviable. Otherwise, it doesn't delete - * It then checks if the name of the workspace to be deleted is correct. Otherwise, it doesn't delete. - * It then deletes the workspace and forwards the user to another aviable workspace. - */ - const executeDeletingWorkspace = async () => { - let userWorkspaces = await getWorkspaces(); - - if (userWorkspaces.length > 1) { - if ( - userWorkspaces.filter( - (workspace) => workspace._id == router.query.id - )[0].name == workspaceToBeDeletedName - ) { - await deleteWorkspace(router.query.id); - let userWorkspaces = await getWorkspaces(); - router.push("/dashboard/" + userWorkspaces[0]._id); - } - } - }; - - return ( -
- - - {t("common:head-title", { title: t("settings-project:title") })} - - - - -
-
- -
-
-

- {t("settings-project:title")} -

-

- {t("settings-project:description")} -

-
-
-
-
-
-
-

- {t("common:display-name")} -

-
- -
-
-
-
-
-
-
-

- {t("common:project-id")} -

-

- {t("settings-project:project-id-description")} -

-

- {t("settings-project:project-id-description2")} - {/* eslint-disable-next-line react/jsx-no-target-blank */} - - {t("settings-project:docs")} - -

-

{t("settings-project:auto-generated")}

-
-

{`${t( - "common:project-id" - )}:`}

- -
- - - {t("common:click-to-copy")} - -
-
-
-
-
-
-

- {t("section-token:service-tokens")} -

-

- {t("section-token:service-tokens-description")} -

-

- Please, make sure you are on the - - latest version of CLI - . -

-
-
-
-
- -
-
-
-
-

- {t("settings-project:danger-zone")} -

-

- {t("settings-project:danger-zone-note")} -

-
- -
- -

- {t("settings-project:delete-project-note")} -

-
-
-
-
-
- ); -} - -SettingsBasic.requireAuth = true; - -export const getServerSideProps = getTranslatedServerSideProps([ - "settings", - "settings-project", - "section-token", -]); diff --git a/frontend/pages/settings/project/[id].tsx b/frontend/pages/settings/project/[id].tsx index 4a64c3e65..619ef2e6b 100644 --- a/frontend/pages/settings/project/[id].tsx +++ b/frontend/pages/settings/project/[id].tsx @@ -1,55 +1,87 @@ -import { useEffect, useRef, useState } from "react"; -import Head from "next/head"; -import { useRouter } from "next/router"; -import { useTranslation } from "next-i18next"; -import { faCheck, faPlus } from "@fortawesome/free-solid-svg-icons"; +import { useEffect, useState } from 'react'; +import Head from 'next/head'; +import { useRouter } from 'next/router'; +import { useTranslation } from 'next-i18next'; +import { faCheck, faCopy, faPlus } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import Button from "~/components/basic/buttons/Button"; -import AddServiceTokenDialog from "~/components/basic/dialog/AddServiceTokenDialog"; -import InputField from "~/components/basic/InputField"; +import Button from '~/components/basic/buttons/Button'; +import AddServiceTokenDialog from '~/components/basic/dialog/AddServiceTokenDialog'; +import InputField from '~/components/basic/InputField'; import EnvironmentTable from '~/components/basic/table/EnvironmentsTable'; -import ServiceTokenTable from "~/components/basic/table/ServiceTokenTable"; -import NavHeader from "~/components/navigation/NavHeader"; -import { getTranslatedServerSideProps } from "~/utilities/withTranslateProps"; +import ServiceTokenTable from '~/components/basic/table/ServiceTokenTable'; +import NavHeader from '~/components/navigation/NavHeader'; +import deleteEnvironment from '~/pages/api/environments/deleteEnvironment'; +import updateEnvironment from '~/pages/api/environments/updateEnvironment'; +import { getTranslatedServerSideProps } from '~/utilities/withTranslateProps'; -import getServiceTokens from "../../api/serviceToken/getServiceTokens"; -import deleteWorkspace from "../../api/workspace/deleteWorkspace"; -import getWorkspaces from "../../api/workspace/getWorkspaces"; -import renameWorkspace from "../../api/workspace/renameWorkspace"; +import createEnvironment from '../../api/environments/createEnvironment'; +import getServiceTokens from '../../api/serviceToken/getServiceTokens'; +import deleteWorkspace from '../../api/workspace/deleteWorkspace'; +import getWorkspaces from '../../api/workspace/getWorkspaces'; +import renameWorkspace from '../../api/workspace/renameWorkspace'; +type EnvData = { + name: string; + slug: string; +}; export default function SettingsBasic() { const [buttonReady, setButtonReady] = useState(false); const router = useRouter(); - const [workspaceName, setWorkspaceName] = useState(""); + const [workspaceName, setWorkspaceName] = useState(''); const [serviceTokens, setServiceTokens] = useState([]); - const [environments,setEnvironments] = useState([]); - const [workspaceToBeDeletedName, setWorkspaceToBeDeletedName] = useState(""); + const [environments, setEnvironments] = useState>([]); + const [workspaceToBeDeletedName, setWorkspaceToBeDeletedName] = useState(''); const [isAddOpen, setIsAddOpen] = useState(false); - const [isAddServiceTokenDialogOpen, setIsAddServiceTokenDialogOpen] = useState(false); + const [isAddServiceTokenDialogOpen, setIsAddServiceTokenDialogOpen] = + useState(false); + const [projectIdCopied, setProjectIdCopied] = useState(false); + const workspaceId = router.query.id as string; const { t } = useTranslation(); - useEffect(async () => { - const userWorkspaces = await getWorkspaces(); - userWorkspaces.forEach((userWorkspace) => { - if (userWorkspace._id == router.query.id) { - setWorkspaceName(userWorkspace.name); - setEnvironments(userWorkspace.environments); - } - }); - const tempServiceTokens = await getServiceTokens({ - workspaceId: router.query.id, - }); - setServiceTokens(tempServiceTokens); + /** + * This function copies the project id to the clipboard + */ + function copyToClipboard() { + const copyText = document.getElementById('myInput') as HTMLInputElement; + + if (copyText) { + copyText.select(); + copyText.setSelectionRange(0, 99999); // For mobile devices + + navigator.clipboard.writeText(copyText.value); + + setProjectIdCopied(true); + setTimeout(() => setProjectIdCopied(false), 2000); + } + } + + useEffect(() => { + const load = async () => { + const userWorkspaces = await getWorkspaces(); + userWorkspaces.forEach((userWorkspace) => { + if (userWorkspace._id == workspaceId) { + setWorkspaceName(userWorkspace.name); + setEnvironments(userWorkspace.environments); + } + }); + const tempServiceTokens = await getServiceTokens({ + workspaceId, + }); + setServiceTokens(tempServiceTokens); + }; + + load(); }, []); - const modifyWorkspaceName = (newName) => { + const modifyWorkspaceName = (newName: string) => { setButtonReady(true); setWorkspaceName(newName); }; - const submitChanges = (newWorkspaceName) => { - renameWorkspace(router.query.id, newWorkspaceName); + const submitChanges = (newWorkspaceName: string) => { + renameWorkspace(workspaceId, newWorkspaceName); setButtonReady(false); }; @@ -69,16 +101,54 @@ export default function SettingsBasic() { if (userWorkspaces.length > 1) { if ( userWorkspaces.filter( - (workspace) => workspace._id == router.query.id + (workspace) => workspace._id === workspaceId )[0].name == workspaceToBeDeletedName ) { - await deleteWorkspace(router.query.id); + await deleteWorkspace(workspaceId); const userWorkspaces = await getWorkspaces(); - router.push("/dashboard/" + userWorkspaces[0]._id); + router.push('/dashboard/' + userWorkspaces[0]._id); } } }; + const onCreateEnvironment = async ({ name, slug }: EnvData) => { + const res = await createEnvironment(workspaceId, { + environmentName: name, + environmentSlug: slug, + }); + if (res) { + // TODO: on react-query migration do an api call to resync + setEnvironments((env) => [...env, { name, slug }]); + } + }; + + const onUpdateEnvironment = async ( + oldSlug: string, + { name, slug }: EnvData + ) => { + const res = await updateEnvironment(workspaceId, { + oldEnvironmentSlug: oldSlug, + environmentName: name, + environmentSlug: slug, + }); + // TODO: on react-query migration do an api call to resync + if (res) { + setEnvironments((env) => + env.map((el) => (el.slug === oldSlug ? { name, slug } : el)) + ); + } + }; + + const onDeleteEnvironment = async (slugToBeDelete: string) => { + const res = await deleteEnvironment(workspaceId, slugToBeDelete); + // TODO: on react-query migration do an api call to resync + if (res) { + setEnvironments((env) => + env.filter(({ slug }) => slug !== slugToBeDelete) + ); + } + }; + return (
@@ -89,9 +159,12 @@ export default function SettingsBasic() {
@@ -112,12 +185,13 @@ export default function SettingsBasic() {
-
-

+

+

{t('common:display-name')}

-
+

{t('common:project-id')}

@@ -158,30 +232,70 @@ export default function SettingsBasic() { {t('settings-project:docs')}

-
- +

+ {t('settings-project:auto-generated')} +

+
+

{`${t( + 'common:project-id' + )}:`}

+ +
+ + + {t('common:click-to-copy')} + +
+ +
+

{t('section-token:service-tokens')}

-

+

{t('section-token:service-tokens-description')}

+

+ Please, make sure you are on the + + latest version of CLI + + . +

-
+
-
- -
-
+

{t('settings-project:danger-zone')}

@@ -239,7 +351,7 @@ export default function SettingsBasic() { SettingsBasic.requireAuth = true; export const getServerSideProps = getTranslatedServerSideProps([ - "settings", - "settings-project", - "section-token", + 'settings', + 'settings-project', + 'section-token', ]); From 1ac94ee940bb432deeab6360232c95533e5aa954 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Thu, 12 Jan 2023 16:37:59 -0800 Subject: [PATCH 25/65] selectively get user email from service toke/jwt --- .../src/controllers/v2/secretController.ts | 191 +++++++++--------- .../src/controllers/v2/secretsController.ts | 5 +- 2 files changed, 101 insertions(+), 95 deletions(-) diff --git a/backend/src/controllers/v2/secretController.ts b/backend/src/controllers/v2/secretController.ts index c2cf45f9a..89567e616 100644 --- a/backend/src/controllers/v2/secretController.ts +++ b/backend/src/controllers/v2/secretController.ts @@ -7,7 +7,7 @@ const { ValidationError } = mongoose.Error; import { BadRequestError, InternalServerError, UnauthorizedRequestError, ValidationError as RouteValidationError } from '../../utils/errors'; import { AnyBulkWriteOperation } from 'mongodb'; import { SECRET_PERSONAL, SECRET_SHARED } from "../../variables"; -// import { postHogClient } from '../../services'; +import { postHogClient } from '../../services'; /** * Create secret for workspace with id [workspaceId] and environment [environment] @@ -42,19 +42,19 @@ export const createSecret = async (req: Request, res: Response) => { throw RouteValidationError({ message: error.message, stack: error.stack }) } - // if (postHogClient) { - // postHogClient.capture({ - // event: 'secrets added', - // distinctId: req.user.email, - // properties: { - // numberOfSecrets: 1, - // workspaceId, - // environment, - // channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli', - // userAgent: req.headers?.['user-agent'] - // } - // }); - // } + if (postHogClient) { + postHogClient.capture({ + event: 'secrets added', + distinctId: req.user.email, + properties: { + numberOfSecrets: 1, + workspaceId, + environment, + channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli', + userAgent: req.headers?.['user-agent'] + } + }); + } res.status(200).send({ secret @@ -103,19 +103,19 @@ export const createSecrets = async (req: Request, res: Response) => { throw InternalServerError({ message: "Unable to process your batch create request. Please try again", stack: bulkCreateError.stack }) } - // if (postHogClient) { - // postHogClient.capture({ - // event: 'secrets added', - // distinctId: req.user.email, - // properties: { - // numberOfSecrets: (secretsToCreate ?? []).length, - // workspaceId, - // environment, - // channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli', - // userAgent: req.headers?.['user-agent'] - // } - // }); - // } + if (postHogClient) { + postHogClient.capture({ + event: 'secrets added', + distinctId: req.user.email, + properties: { + numberOfSecrets: (secretsToCreate ?? []).length, + workspaceId, + environment, + channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli', + userAgent: req.headers?.['user-agent'] + } + }); + } res.status(200).send({ secrets @@ -158,19 +158,19 @@ export const deleteSecrets = async (req: Request, res: Response) => { throw InternalServerError() } - // if (postHogClient) { - // postHogClient.capture({ - // event: 'secrets deleted', - // distinctId: req.user.email, - // properties: { - // numberOfSecrets: numSecretsDeleted, - // environment: environmentName, - // workspaceId, - // channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli', - // userAgent: req.headers?.['user-agent'] - // } - // }); - // } + if (postHogClient) { + postHogClient.capture({ + event: 'secrets deleted', + distinctId: req.user.email, + properties: { + numberOfSecrets: numSecretsDeleted, + environment: environmentName, + workspaceId, + channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli', + userAgent: req.headers?.['user-agent'] + } + }); + } res.status(200).send() } @@ -183,19 +183,19 @@ export const deleteSecrets = async (req: Request, res: Response) => { export const deleteSecret = async (req: Request, res: Response) => { await Secret.findByIdAndDelete(req._secret._id) - // if (postHogClient) { - // postHogClient.capture({ - // event: 'secrets deleted', - // distinctId: req.user.email, - // properties: { - // numberOfSecrets: 1, - // workspaceId: req._secret.workspace.toString(), - // environment: req._secret.environment, - // channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli', - // userAgent: req.headers?.['user-agent'] - // } - // }); - // } + if (postHogClient) { + postHogClient.capture({ + event: 'secrets deleted', + distinctId: req.user.email, + properties: { + numberOfSecrets: 1, + workspaceId: req._secret.workspace.toString(), + environment: req._secret.environment, + channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli', + userAgent: req.headers?.['user-agent'] + } + }); + } res.status(200).send({ secret: req._secret @@ -252,19 +252,19 @@ export const updateSecrets = async (req: Request, res: Response) => { throw InternalServerError() } - // if (postHogClient) { - // postHogClient.capture({ - // event: 'secrets modified', - // distinctId: req.user.email, - // properties: { - // numberOfSecrets: (secretsModificationsRequested ?? []).length, - // environment: environmentName, - // workspaceId, - // channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli', - // userAgent: req.headers?.['user-agent'] - // } - // }); - // } + if (postHogClient) { + postHogClient.capture({ + event: 'secrets modified', + distinctId: req.user.email, + properties: { + numberOfSecrets: (secretsModificationsRequested ?? []).length, + environment: environmentName, + workspaceId, + channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli', + userAgent: req.headers?.['user-agent'] + } + }); + } return res.status(200).send() } @@ -304,19 +304,19 @@ export const updateSecret = async (req: Request, res: Response) => { throw RouteValidationError({ message: "Unable to apply modifications, please try again", stack: error.stack }) } - // if (postHogClient) { - // postHogClient.capture({ - // event: 'secrets modified', - // distinctId: req.user.email, - // properties: { - // numberOfSecrets: 1, - // environment: environmentName, - // workspaceId, - // channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli', - // userAgent: req.headers?.['user-agent'] - // } - // }); - // } + if (postHogClient) { + postHogClient.capture({ + event: 'secrets modified', + distinctId: req.user.email, + properties: { + numberOfSecrets: 1, + environment: environmentName, + workspaceId, + channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli', + userAgent: req.headers?.['user-agent'] + } + }); + } return res.status(200).send(singleModificationUpdate) } @@ -332,13 +332,16 @@ export const getSecrets = async (req: Request, res: Response) => { const { environment } = req.query; const { workspaceId } = req.params; - let userId: string | undefined = undefined // used for getting personal secrets for user + let userId: Types.ObjectId | undefined = undefined // used for getting personal secrets for user + let userEmail: Types.ObjectId | undefined = undefined // used for posthog if (req.user) { - userId = req.user._id.toString(); + userId = req.user._id; + userEmail = req.user.email; } if (req.serviceTokenData) { userId = req.serviceTokenData.user._id + userEmail = req.serviceTokenData.user.email; } const [err, secrets] = await to(Secret.find( @@ -354,19 +357,19 @@ export const getSecrets = async (req: Request, res: Response) => { throw RouteValidationError({ message: "Failed to get secrets, please try again", stack: err.stack }) } - // if (postHogClient) { - // postHogClient.capture({ - // event: 'secrets pulled', - // distinctId: req.user.email, - // properties: { - // numberOfSecrets: (secrets ?? []).length, - // environment, - // workspaceId, - // channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli', - // userAgent: req.headers?.['user-agent'] - // } - // }); - // } + if (postHogClient) { + postHogClient.capture({ + event: 'secrets pulled', + distinctId: userEmail, + properties: { + numberOfSecrets: (secrets ?? []).length, + environment, + workspaceId, + channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli', + userAgent: req.headers?.['user-agent'] + } + }); + } return res.json(secrets) } diff --git a/backend/src/controllers/v2/secretsController.ts b/backend/src/controllers/v2/secretsController.ts index f92fc4cee..b5a27874f 100644 --- a/backend/src/controllers/v2/secretsController.ts +++ b/backend/src/controllers/v2/secretsController.ts @@ -162,12 +162,15 @@ export const getSecrets = async (req: Request, res: Response) => { const { workspaceId, environment } = req.query; let userId: Types.ObjectId | undefined = undefined // used for getting personal secrets for user + let userEmail: Types.ObjectId | undefined = undefined // used for posthog if (req.user) { userId = req.user._id; + userEmail = req.user.email; } if (req.serviceTokenData) { userId = req.serviceTokenData.user._id + userEmail = req.serviceTokenData.user.email; } const [err, secrets] = await to(Secret.find( @@ -204,7 +207,7 @@ export const getSecrets = async (req: Request, res: Response) => { if (postHogClient) { postHogClient.capture({ event: 'secrets added', - distinctId: req.user.email, + distinctId: userEmail, properties: { numberOfSecrets: secrets.length, environment, From 35466a7f4a72afe09ba4a5f8060e71841c76675a Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Thu, 12 Jan 2023 21:58:03 -0800 Subject: [PATCH 26/65] Modify get secrets logic --- cli/packages/util/secrets.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cli/packages/util/secrets.go b/cli/packages/util/secrets.go index c0a3ad952..131a8e66d 100644 --- a/cli/packages/util/secrets.go +++ b/cli/packages/util/secrets.go @@ -117,11 +117,9 @@ func GetAllEnvironmentVariables(envName string) ([]models.SingleEnvironmentVaria secrets, err := GetPlainTextSecretsViaJTW(loggedInUserDetails.UserCredentials.JTWToken, loggedInUserDetails.UserCredentials.PrivateKey, workspaceFile.WorkspaceId, envName) return secrets, err - } else if infisicalToken != "" { + } else { log.Debug("Trying to fetch secrets using service token") return GetPlainTextSecretsViaServiceToken(infisicalToken) - } else { - return nil, fmt.Errorf("unable to fetch secrets because we could not find a service token or a logged in user") } } From d579684d2f76655742ac94c4fde2c4a1acec825f Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Thu, 12 Jan 2023 22:00:06 -0800 Subject: [PATCH 27/65] increase version --- cli/packages/cmd/root.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/packages/cmd/root.go b/cli/packages/cmd/root.go index b1b53d2ff..e68c6826c 100644 --- a/cli/packages/cmd/root.go +++ b/cli/packages/cmd/root.go @@ -15,7 +15,7 @@ var rootCmd = &cobra.Command{ Short: "Infisical CLI is used to inject environment variables into any process", Long: `Infisical is a simple, end-to-end encrypted service that enables teams to sync and manage their environment variables across their development life cycle.`, CompletionOptions: cobra.CompletionOptions{HiddenDefaultCmd: true}, - Version: "0.2.0", + Version: "0.2.1", } // Execute adds all child commands to the root command and sets flags appropriately. From d958341154e346d677edf640d40119b254c41e09 Mon Sep 17 00:00:00 2001 From: Vladyslav Matsiiako Date: Thu, 12 Jan 2023 22:15:17 -0800 Subject: [PATCH 28/65] Corrected the telemetery event name --- backend/src/controllers/v2/secretsController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/controllers/v2/secretsController.ts b/backend/src/controllers/v2/secretsController.ts index b5a27874f..ea495ad87 100644 --- a/backend/src/controllers/v2/secretsController.ts +++ b/backend/src/controllers/v2/secretsController.ts @@ -206,7 +206,7 @@ export const getSecrets = async (req: Request, res: Response) => { if (postHogClient) { postHogClient.capture({ - event: 'secrets added', + event: 'secrets pulled', distinctId: userEmail, properties: { numberOfSecrets: secrets.length, From f57f3e6475e3c00da577a2f6b5b2189bdfcdcb17 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Thu, 12 Jan 2023 23:26:52 -0800 Subject: [PATCH 29/65] enable integ --- backend/src/variables/integration.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/variables/integration.ts b/backend/src/variables/integration.ts index baea2b093..a1aa5d94d 100644 --- a/backend/src/variables/integration.ts +++ b/backend/src/variables/integration.ts @@ -47,7 +47,7 @@ const INTEGRATION_OPTIONS = [ name: 'Vercel', slug: 'vercel', image: 'Vercel', - isAvailable: false, + isAvailable: true, type: 'vercel', clientId: '', clientSlug: CLIENT_SLUG_VERCEL, @@ -57,7 +57,7 @@ const INTEGRATION_OPTIONS = [ name: 'Netlify', slug: 'netlify', image: 'Netlify', - isAvailable: false, + isAvailable: true, type: 'oauth2', clientId: CLIENT_ID_NETLIFY, docsLink: '' @@ -66,7 +66,7 @@ const INTEGRATION_OPTIONS = [ name: 'GitHub', slug: 'github', image: 'GitHub', - isAvailable: false, + isAvailable: true, type: 'oauth2', clientId: CLIENT_ID_GITHUB, docsLink: '' From 9d4ea2dcda6e6c399e1a743de974c570ec2cc386 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Fri, 13 Jan 2023 15:04:46 +0700 Subject: [PATCH 30/65] Continue api-reference docs --- backend/package-lock.json | 9 +- backend/package.json | 3 +- backend/{api-documentation.json => spec.json} | 651 ++++++++++++++++-- backend/src/app.ts | 6 +- backend/src/controllers/v1/authController.ts | 5 +- backend/src/controllers/v2/index.ts | 2 + .../src/controllers/v2/secretsController.ts | 171 +++++ backend/src/controllers/v2/usersController.ts | 44 ++ .../src/controllers/v2/workspaceController.ts | 28 + backend/src/routes/v2/index.ts | 2 + backend/src/routes/v2/users.ts | 16 + backend/swagger.ts | 22 - backend/swagger/index.ts | 116 ++++ backend/swagger/schemas/index.ts | 6 + backend/swagger/schemas/secretSchema.ts | 11 + docs/api-reference/endpoints/secrets/read.mdx | 2 +- docs/api-reference/endpoints/users/me.mdx | 4 + .../endpoints/workspaces/workspace-key.mdx | 4 + .../api-reference/overview/authentication.mdx | 8 + .../overview/examples/create-secrets.mdx | 64 ++ .../overview/examples/retrieve-secrets.mdx | 10 + .../overview/examples/update-secrets.mdx | 10 + docs/api-reference/overview/introduction.mdx | 16 + docs/api-reference/overview/usage.mdx | 18 + docs/mint.json | 32 +- docs/spec.yaml | 328 ++++++++- 26 files changed, 1466 insertions(+), 122 deletions(-) rename backend/{api-documentation.json => spec.json} (79%) create mode 100644 backend/src/controllers/v2/usersController.ts create mode 100644 backend/src/routes/v2/users.ts delete mode 100644 backend/swagger.ts create mode 100644 backend/swagger/index.ts create mode 100644 backend/swagger/schemas/index.ts create mode 100644 backend/swagger/schemas/secretSchema.ts create mode 100644 docs/api-reference/endpoints/users/me.mdx create mode 100644 docs/api-reference/endpoints/workspaces/workspace-key.mdx create mode 100644 docs/api-reference/overview/examples/create-secrets.mdx create mode 100644 docs/api-reference/overview/examples/retrieve-secrets.mdx create mode 100644 docs/api-reference/overview/examples/update-secrets.mdx create mode 100644 docs/api-reference/overview/usage.mdx diff --git a/backend/package-lock.json b/backend/package-lock.json index b51aa7cf0..38c1126cc 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -28,6 +28,7 @@ "express-validator": "^6.14.2", "handlebars": "^4.7.7", "helmet": "^5.1.1", + "js-yaml": "^4.1.0", "jsonwebtoken": "^9.0.0", "jsrp": "^0.2.4", "libsodium-wrappers": "^0.7.10", @@ -3698,8 +3699,7 @@ "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "node_modules/array-flatten": { "version": "1.1.1", @@ -6638,7 +6638,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, "dependencies": { "argparse": "^2.0.1" }, @@ -14980,8 +14979,7 @@ "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "array-flatten": { "version": "1.1.1", @@ -17197,7 +17195,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, "requires": { "argparse": "^2.0.1" } diff --git a/backend/package.json b/backend/package.json index 08f5a815c..b9359f680 100644 --- a/backend/package.json +++ b/backend/package.json @@ -5,7 +5,7 @@ "scripts": { "start": "npm run build && node build/index.js", "dev": "nodemon", - "swagger-autogen": "node ./swagger.ts", + "swagger-autogen": "node ./swagger/index.ts", "build": "rimraf ./build && tsc && cp -R ./src/templates ./build", "lint": "eslint . --ext .ts", "lint-and-fix": "eslint . --ext .ts --fix", @@ -94,6 +94,7 @@ "express-validator": "^6.14.2", "handlebars": "^4.7.7", "helmet": "^5.1.1", + "js-yaml": "^4.1.0", "jsonwebtoken": "^9.0.0", "jsrp": "^0.2.4", "libsodium-wrappers": "^0.7.10", diff --git a/backend/api-documentation.json b/backend/spec.json similarity index 79% rename from backend/api-documentation.json rename to backend/spec.json index 79cf0eb3b..188365290 100644 --- a/backend/api-documentation.json +++ b/backend/spec.json @@ -5,6 +5,16 @@ "description": "List of all available APIs that can be consumed", "version": "1.0.0" }, + "servers": [ + { + "url": "https://infisical.com", + "description": "Production server" + }, + { + "url": "http://localhost:8080", + "description": "Local server" + } + ], "paths": { "/api/v1/secret/{secretId}/secret-versions": { "get": { @@ -43,6 +53,43 @@ } } }, + "/api/v1/secret/{secretId}/secret-versions/rollback": { + "post": { + "description": "", + "parameters": [ + { + "name": "secretId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "version": { + "example": "any" + } + } + } + } + } + } + } + }, "/api/v1/secret-snapshot/{secretSnapshotId}": { "get": { "description": "", @@ -126,6 +173,43 @@ } } }, + "/api/v1/workspace/{workspaceId}/secret-snapshots/rollback": { + "post": { + "description": "", + "parameters": [ + { + "name": "workspaceId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "version": { + "example": "any" + } + } + } + } + } + } + } + }, "/api/v1/workspace/{workspaceId}/logs": { "get": { "description": "", @@ -476,7 +560,11 @@ "post": { "description": "", "parameters": [], - "responses": {} + "responses": { + "200": { + "description": "OK" + } + } } }, "/api/v1/bot/{workspaceId}": { @@ -1997,6 +2085,35 @@ } } }, + "/api/v2/users/me": { + "get": { + "summary": "Retrieve the current user on the request", + "description": "Retrieve the current user on the request", + "parameters": [], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "$ref": "#/components/schemas/CurrentUser", + "description": "Current user on request" + } + } + } + }, + "400": { + "description": "Bad Request" + } + }, + "security": [ + { + "apiKeyAuth": [] + } + ] + } + }, "/api/v2/workspace/{workspaceId}/secrets": { "post": { "description": "", @@ -2080,7 +2197,8 @@ }, "/api/v2/workspace/{workspaceId}/encrypted-key": { "get": { - "description": "", + "summary": "Return encrypted project key", + "description": "Return encrypted project key", "parameters": [ { "name": "workspaceId", @@ -2088,17 +2206,34 @@ "required": true, "schema": { "type": "string" - } + }, + "description": "ID of project" } ], "responses": { "200": { - "description": "OK" + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProjectKey" + }, + "description": "Encrypted project key for the given project" + } + } + } }, "400": { "description": "Bad Request" } - } + }, + "security": [ + { + "apiKeyAuth": [] + } + ] } }, "/api/v2/workspace/{workspaceId}/service-token-data": { @@ -2124,7 +2259,7 @@ } } }, - "/api/v2/secret/batch-create/workspace/{workspaceId}/environment/{environmentName}": { + "/api/v2/secret/batch-create/workspace/{workspaceId}/environment/{environment}": { "post": { "description": "", "parameters": [ @@ -2137,7 +2272,7 @@ } }, { - "name": "environmentName", + "name": "environment", "in": "path", "required": true, "schema": { @@ -2166,7 +2301,7 @@ } } }, - "/api/v2/secret/workspace/{workspaceId}/environment/{environmentName}": { + "/api/v2/secret/workspace/{workspaceId}/environment/{environment}": { "post": { "description": "", "parameters": [ @@ -2179,47 +2314,7 @@ } }, { - "name": "environmentName", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - }, - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "secret": { - "example": "any" - } - } - } - } - } - } - }, - "patch": { - "description": "", - "parameters": [ - { - "name": "workspaceId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "environmentName", + "name": "environment", "in": "path", "required": true, "schema": { @@ -2397,6 +2492,230 @@ } } }, + "/api/v2/secret/workspace/{workspaceId}/environment/{environmentName}": { + "patch": { + "description": "", + "parameters": [ + { + "name": "workspaceId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "environmentName", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "secret": { + "example": "any" + } + } + } + } + } + } + } + }, + "/api/v2/secrets/": { + "post": { + "summary": "Create new secret(s)", + "description": "Create one or many secrets for a given project and environment.", + "parameters": [], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Secret" + }, + "description": "Array of newly-created secrets for the given project and environment" + } + } + } + } + }, + "security": [ + { + "apiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "workspaceId": { + "type": "string", + "description": "ID of project" + }, + "environment": { + "type": "string", + "description": "Environment within project" + }, + "secrets": { + "$ref": "#/components/schemas/CreateSecret", + "description": "Secret(s) to create - object or array of objects" + } + } + } + } + } + } + }, + "get": { + "summary": "Read secrets", + "description": "Read secrets from a project and environment", + "parameters": [], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Secret" + }, + "description": "Array of secrets for the given project and environment" + } + } + } + } + }, + "security": [ + { + "apiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "workspaceId": { + "type": "string", + "description": "ID of project" + }, + "environment": { + "type": "string", + "description": "Environment within project" + } + } + } + } + } + } + }, + "patch": { + "summary": "Update secret(s)", + "description": "Update secret(s)", + "parameters": [], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Secret" + }, + "description": "Array of newly-updated secrets for the given project and environment" + } + } + } + } + }, + "security": [ + { + "apiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "secrets": { + "$ref": "#/components/schemas/UpdateSecret", + "description": "Secret(s) to update - object or array of objects" + } + } + } + } + } + } + }, + "delete": { + "summary": "Delete secret(s)", + "description": "Delete one or many secrets by their ID(s)", + "parameters": [], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Secret" + }, + "description": "Array of deleted secrets" + } + } + } + } + }, + "security": [ + { + "apiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "secretIds": { + "type": "string", + "description": "ID(s) of secrets - string or array of strings" + } + } + } + } + } + } + } + }, "/api/v2/service-token/": { "get": { "description": "", @@ -2557,11 +2876,243 @@ } }, "components": { + "schemas": { + "CurrentUser": { + "type": "object", + "properties": { + "_id": { + "type": "string", + "example": "" + }, + "email": { + "type": "string", + "example": "" + }, + "firstName": { + "type": "string", + "example": "" + }, + "lastName": { + "type": "string", + "example": "" + }, + "publicKey": { + "type": "string", + "example": "" + }, + "encryptedPrivateKey": { + "type": "string", + "example": "" + }, + "updatedAt": { + "type": "string", + "example": "" + }, + "createdAt": { + "type": "string", + "example": "" + } + } + }, + "ProjectKey": { + "type": "object", + "properties": { + "encryptedkey": { + "type": "string", + "example": "" + }, + "nonce": { + "type": "string", + "example": "" + }, + "sender": { + "type": "object", + "properties": { + "publicKey": { + "type": "string", + "example": "" + } + } + }, + "receiver": { + "type": "string", + "example": "" + }, + "workspace": { + "type": "string", + "example": "" + } + } + }, + "CreateSecret": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "shared" + }, + "secretKeyCiphertext": { + "type": "string", + "example": "" + }, + "secretKeyIV": { + "type": "string", + "example": "" + }, + "secretKeyTag": { + "type": "string", + "example": "" + }, + "secretValueCiphertext": { + "type": "string", + "example": "" + }, + "secretValueIV": { + "type": "string", + "example": "" + }, + "secretValueTag": { + "type": "string", + "example": "" + }, + "secretCommentCiphertext": { + "type": "string", + "example": "" + }, + "secretCommentIV": { + "type": "string", + "example": "" + }, + "secretCommentTag": { + "type": "string", + "example": "" + } + } + }, + "UpdateSecret": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "" + }, + "secretKeyCiphertext": { + "type": "string", + "example": "" + }, + "secretKeyIV": { + "type": "string", + "example": "" + }, + "secretKeyTag": { + "type": "string", + "example": "" + }, + "secretValueCiphertext": { + "type": "string", + "example": "" + }, + "secretValueIV": { + "type": "string", + "example": "" + }, + "secretValueTag": { + "type": "string", + "example": "" + }, + "secretCommentCiphertext": { + "type": "string", + "example": "" + }, + "secretCommentIV": { + "type": "string", + "example": "" + }, + "secretCommentTag": { + "type": "string", + "example": "" + } + } + }, + "Secret": { + "type": "object", + "properties": { + "_id": { + "type": "string", + "example": "" + }, + "version": { + "type": "number", + "example": 1 + }, + "workspace": { + "type": "string", + "example": "" + }, + "type": { + "type": "string", + "example": "shared" + }, + "user": {}, + "secretKeyCiphertext": { + "type": "string", + "example": "" + }, + "secretKeyIV": { + "type": "string", + "example": "" + }, + "secretKeyTag": { + "type": "string", + "example": "" + }, + "secretValueCiphertext": { + "type": "string", + "example": "" + }, + "secretValueIV": { + "type": "string", + "example": "" + }, + "secretValueTag": { + "type": "string", + "example": "" + }, + "secretCommentCiphertext": { + "type": "string", + "example": "" + }, + "secretCommentIV": { + "type": "string", + "example": "" + }, + "secretCommentTag": { + "type": "string", + "example": "" + }, + "updatedAt": { + "type": "string", + "example": "" + }, + "createdAt": { + "type": "string", + "example": "" + } + } + } + }, "securitySchemes": { "bearerAuth": { "type": "http", "scheme": "bearer", - "bearerFormat": "JWT" + "bearerFormat": "JWT", + "description": "This security definition uses the HTTP 'bearer' scheme, which allows the client to authenticate using a JSON Web Token (JWT) that is passed in the Authorization header of the request." + }, + "apiKeyAuth": { + "type": "apiKey", + "in": "header", + "name": "X-API-Key", + "description": "This security definition uses an API key, which is passed in the header of the request as the value of the \"X-API-Key\" header. The client must provide a valid key in order to access the API." } } } diff --git a/backend/src/app.ts b/backend/src/app.ts index aa7ac9b28..8cefd31ac 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -8,7 +8,7 @@ import cookieParser from 'cookie-parser'; import dotenv from 'dotenv'; import swaggerUi = require('swagger-ui-express'); // eslint-disable-next-line @typescript-eslint/no-var-requires -const swaggerFile = require('../api-documentation.json') +const swaggerFile = require('../spec.json') dotenv.config(); @@ -41,7 +41,8 @@ import { integrationAuth as v1IntegrationAuthRouter } from './routes/v1'; import { - secret as v2SecretRouter, + users as v2UsersRouter, + secret as v2SecretRouter, // begin to phase out secrets as v2SecretsRouter, workspace as v2WorkspaceRouter, serviceTokenData as v2ServiceTokenDataRouter, @@ -103,6 +104,7 @@ app.use('/api/v1/integration', v1IntegrationRouter); app.use('/api/v1/integration-auth', v1IntegrationAuthRouter); // v2 routes +app.use('/api/v2/users', v2UsersRouter); app.use('/api/v2/workspace', v2WorkspaceRouter); // TODO: turn into plural route app.use('/api/v2/secret', v2SecretRouter); // stop supporting, TODO: revise app.use('/api/v2/secrets', v2SecretsRouter); diff --git a/backend/src/controllers/v1/authController.ts b/backend/src/controllers/v1/authController.ts index defd03d8a..882db688f 100644 --- a/backend/src/controllers/v1/authController.ts +++ b/backend/src/controllers/v1/authController.ts @@ -170,10 +170,11 @@ export const logout = async (req: Request, res: Response) => { * @param res * @returns */ -export const checkAuth = async (req: Request, res: Response) => - res.status(200).send({ +export const checkAuth = async (req: Request, res: Response) => { + return res.status(200).send({ message: 'Authenticated' }); +} /** * Return new token by redeeming refresh token diff --git a/backend/src/controllers/v2/index.ts b/backend/src/controllers/v2/index.ts index 1651c09ee..bc6848590 100644 --- a/backend/src/controllers/v2/index.ts +++ b/backend/src/controllers/v2/index.ts @@ -1,3 +1,4 @@ +import * as usersController from './usersController'; import * as workspaceController from './workspaceController'; import * as serviceTokenDataController from './serviceTokenDataController'; import * as apiKeyDataController from './apiKeyDataController'; @@ -5,6 +6,7 @@ import * as secretController from './secretController'; import * as secretsController from './secretsController'; export { + usersController, workspaceController, serviceTokenDataController, apiKeyDataController, diff --git a/backend/src/controllers/v2/secretsController.ts b/backend/src/controllers/v2/secretsController.ts index 69cacd75c..31ad0d94c 100644 --- a/backend/src/controllers/v2/secretsController.ts +++ b/backend/src/controllers/v2/secretsController.ts @@ -21,6 +21,53 @@ import { BadRequestError } from '../../utils/errors'; * @param res */ export const createSecrets = async (req: Request, res: Response) => { + /* + #swagger.summary = 'Create new secret(s)' + #swagger.description = 'Create one or many secrets for a given project and environment.' + + #swagger.security = [{ + "apiKeyAuth": [] + }] + + #swagger.requestBody = { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "workspaceId": { + "type": "string", + "description": "ID of project", + }, + "environment": { + "type": "string", + "description": "Environment within project" + }, + "secrets": { + $ref: "#/components/schemas/CreateSecret", + "description": "Secret(s) to create - object or array of objects" + } + } + } + } + } + } + + #swagger.responses[200] = { + content: { + "application/json": { + "schema": { + "type": "array", + "items": { + $ref: "#/components/schemas/Secret" + }, + "description": "Array of newly-created secrets for the given project and environment" + } + } + } + } + */ const channel = req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli'; const { workspaceId, environment } = req.body; @@ -150,6 +197,49 @@ export const createSecrets = async (req: Request, res: Response) => { * @returns */ export const getSecrets = async (req: Request, res: Response) => { + /* + #swagger.summary = 'Read secrets' + #swagger.description = 'Read secrets from a project and environment' + + #swagger.security = [{ + "apiKeyAuth": [] + }] + + #swagger.requestBody = { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "workspaceId": { + "type": "string", + "description": "ID of project" + }, + "environment": { + "type": "string", + "description": "Environment within project" + } + } + } + } + } + } + + #swagger.responses[200] = { + content: { + "application/json": { + "schema": { + "type": "array", + "items": { + $ref: "#/components/schemas/Secret" + }, + "description": "Array of secrets for the given project and environment" + } + } + } + } + */ const { workspaceId, environment } = req.query; let userId: Types.ObjectId | undefined = undefined // used for getting personal secrets for user @@ -217,6 +307,48 @@ export const getSecrets = async (req: Request, res: Response) => { * @param res */ export const updateSecrets = async (req: Request, res: Response) => { + + // TODO: fix update secret schema + + /* + #swagger.summary = 'Update secret(s)' + #swagger.description = 'Update secret(s)' + + #swagger.security = [{ + "apiKeyAuth": [] + }] + + #swagger.requestBody = { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "secrets": { + $ref: "#/components/schemas/UpdateSecret", + "description": "Secret(s) to update - object or array of objects" + } + } + } + } + } + } + + #swagger.responses[200] = { + content: { + "application/json": { + "schema": { + "type": "array", + "items": { + $ref: "#/components/schemas/Secret" + }, + "description": "Array of newly-updated secrets for the given project and environment" + } + } + } + } + */ const channel = req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli'; // TODO: move type @@ -384,6 +516,45 @@ export const updateSecrets = async (req: Request, res: Response) => { * @param res */ export const deleteSecrets = async (req: Request, res: Response) => { + /* + #swagger.summary = 'Delete secret(s)' + #swagger.description = 'Delete one or many secrets by their ID(s)' + + #swagger.security = [{ + "apiKeyAuth": [] + }] + + #swagger.requestBody = { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "secretIds": { + "type": "string", + "description": "ID(s) of secrets - string or array of strings" + }, + } + } + } + } + } + + #swagger.responses[200] = { + content: { + "application/json": { + schema: { + "type": "array", + "items": { + $ref: "#/components/schemas/Secret" + }, + "description": "Array of deleted secrets" + } + } + } + } + */ const channel = req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli'; const toDelete = req.secrets.map((s: any) => s._id); diff --git a/backend/src/controllers/v2/usersController.ts b/backend/src/controllers/v2/usersController.ts new file mode 100644 index 000000000..faac8f646 --- /dev/null +++ b/backend/src/controllers/v2/usersController.ts @@ -0,0 +1,44 @@ +import { Request, Response } from 'express'; +import * as Sentry from '@sentry/node'; +import { + User +} from '../../models'; + +export const getMe = async (req: Request, res: Response) => { + /* + #swagger.summary = "Retrieve the current user on the request" + #swagger.description = "Retrieve the current user on the request" + + #swagger.security = [{ + "apiKeyAuth": [] + }] + + #swagger.responses[200] = { + content: { + "application/json": { + "schema": { + "type": "object", + $ref: "#/components/schemas/CurrentUser", + "description": "Current user on request" + } + } + } + } + */ + let user; + try { + user = await User + .findById(req.user._id) + .select('+publicKey +encryptedPrivateKey'); + } catch (err) { + Sentry.setUser({ email: req.user.email }); + Sentry.captureException(err); + return res.status(400).send({ + message: 'Failed to get user' + }); + } + + return res.status(200).send({ + user + }); +} \ No newline at end of file diff --git a/backend/src/controllers/v2/workspaceController.ts b/backend/src/controllers/v2/workspaceController.ts index 0dbdfa076..6acec644d 100644 --- a/backend/src/controllers/v2/workspaceController.ts +++ b/backend/src/controllers/v2/workspaceController.ts @@ -169,6 +169,34 @@ export const pullSecrets = async (req: Request, res: Response) => { }; export const getWorkspaceKey = async (req: Request, res: Response) => { + /* + #swagger.summary = 'Return encrypted project key' + #swagger.description = 'Return encrypted project key' + + #swagger.security = [{ + "apiKeyAuth": [] + }] + + #swagger.parameters['workspaceId'] = { + "description": "ID of project", + "required": true, + "type": "string" + } + + #swagger.responses[200] = { + content: { + "application/json": { + "schema": { + "type": "array", + "items": { + $ref: "#/components/schemas/ProjectKey" + }, + "description": "Encrypted project key for the given project" + } + } + } + } + */ let key; try { const { workspaceId } = req.params; diff --git a/backend/src/routes/v2/index.ts b/backend/src/routes/v2/index.ts index 8bea42620..a30c72642 100644 --- a/backend/src/routes/v2/index.ts +++ b/backend/src/routes/v2/index.ts @@ -1,3 +1,4 @@ +import users from './users'; import secret from './secret'; // stop-supporting import secrets from './secrets'; import workspace from './workspace'; @@ -5,6 +6,7 @@ import serviceTokenData from './serviceTokenData'; import apiKeyData from './apiKeyData'; export { + users, secret, secrets, workspace, diff --git a/backend/src/routes/v2/users.ts b/backend/src/routes/v2/users.ts new file mode 100644 index 000000000..dba107b15 --- /dev/null +++ b/backend/src/routes/v2/users.ts @@ -0,0 +1,16 @@ +import express from 'express'; +const router = express.Router(); +import { + requireAuth +} from '../../middleware'; +import { usersController } from '../../controllers/v2'; + +router.get( + '/me', + requireAuth({ + acceptedAuthModes: ['jwt'] + }), + usersController.getMe +); + +export default router; \ No newline at end of file diff --git a/backend/swagger.ts b/backend/swagger.ts deleted file mode 100644 index 0505bb813..000000000 --- a/backend/swagger.ts +++ /dev/null @@ -1,22 +0,0 @@ -// eslint-disable-next-line @typescript-eslint/no-var-requires -const swaggerAutogen = require('swagger-autogen')({ openapi: '3.0.0' }); - -const doc = { - info: { - title: 'Infisical API', - description: 'List of all available APIs that can be consumed', - }, - host: ['https://infisical.com'], - securityDefinitions: { - bearerAuth: { - type: 'http', - scheme: 'bearer', - bearerFormat: 'JWT' - } - } -}; - -const outputFile = './api-documentation.json'; -const endpointsFiles = ['./src/app.ts']; - -swaggerAutogen(outputFile, endpointsFiles, doc); \ No newline at end of file diff --git a/backend/swagger/index.ts b/backend/swagger/index.ts new file mode 100644 index 000000000..2b8bc4f62 --- /dev/null +++ b/backend/swagger/index.ts @@ -0,0 +1,116 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const swaggerAutogen = require('swagger-autogen')({ openapi: '3.0.0' }); +const fs = require('fs').promises; +const yaml = require('js-yaml'); +const { secretSchema } = require('./schemas/index.ts'); + +/** + * Generates OpenAPI specs for all Infisical API endpoints: + * - spec.json in /backend for api-serving + * - spec.yaml in /docs for API reference + */ +const generateOpenAPISpec = async () => { + const doc = { + info: { + title: 'Infisical API', + description: 'List of all available APIs that can be consumed', + }, + host: ['https://infisical.com'], + servers: [ + { + url: 'https://infisical.com', + description: 'Production server' + }, + { + url: 'http://localhost:8080', + description: 'Local server' + } + ], + securityDefinitions: { + bearerAuth: { + type: 'http', + scheme: 'bearer', + bearerFormat: 'JWT', + description: "This security definition uses the HTTP 'bearer' scheme, which allows the client to authenticate using a JSON Web Token (JWT) that is passed in the Authorization header of the request." + }, + apiKeyAuth: { + type: 'apiKey', + in: 'header', + name: 'X-API-Key', + description: 'This security definition uses an API key, which is passed in the header of the request as the value of the "X-API-Key" header. The client must provide a valid key in order to access the API.' + } + }, + definitions: { + CurrentUser: { + _id: '', + email: '', + firstName: '', + lastName: '', + publicKey: '', + encryptedPrivateKey: '', + updatedAt: '', + createdAt: '' + }, + ProjectKey: { + encryptedkey: '', + nonce: '', + sender: { + publicKey: '' + }, + receiver: '', + workspace: '' + }, + CreateSecret: { + type: 'shared', + secretKeyCiphertext: '', + secretKeyIV: '', + secretKeyTag: '', + secretValueCiphertext: '', + secretValueIV: '', + secretValueTag: '', + secretCommentCiphertext: '', + secretCommentIV: '', + secretCommentTag: '' + }, + UpdateSecret: { + id: '', + secretKeyCiphertext: '', + secretKeyIV: '', + secretKeyTag: '', + secretValueCiphertext: '', + secretValueIV: '', + secretValueTag: '', + secretCommentCiphertext: '', + secretCommentIV: '', + secretCommentTag: '' + }, + Secret: { + _id: '', + version: 1, + workspace : '', + type: 'shared', + user: null, + secretKeyCiphertext: '', + secretKeyIV: '', + secretKeyTag: '', + secretValueCiphertext: '', + secretValueIV: '', + secretValueTag: '', + secretCommentCiphertext: '', + secretCommentIV: '', + secretCommentTag: '', + updatedAt: '', + createdAt: '' + } + } + }; + + const outputJSONFile = '../spec.json'; + const outputYAMLFile = '../docs/spec.yaml'; + const endpointsFiles = ['../src/app.ts']; + + const spec = await swaggerAutogen(outputJSONFile, endpointsFiles, doc); + await fs.writeFile(outputYAMLFile, yaml.dump(spec.data)); +} + +generateOpenAPISpec(); diff --git a/backend/swagger/schemas/index.ts b/backend/swagger/schemas/index.ts new file mode 100644 index 000000000..b0b427fce --- /dev/null +++ b/backend/swagger/schemas/index.ts @@ -0,0 +1,6 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const secretSchema = require('./secretSchema.ts'); + +module.exports = { + secretSchema +} \ No newline at end of file diff --git a/backend/swagger/schemas/secretSchema.ts b/backend/swagger/schemas/secretSchema.ts new file mode 100644 index 000000000..85c6ce017 --- /dev/null +++ b/backend/swagger/schemas/secretSchema.ts @@ -0,0 +1,11 @@ +const secretSchema = { + _id: { + type: 'string', + format: 'objectId' + }, + version: { + type: 'number' + } +} + +module.exports = secretSchema; \ No newline at end of file diff --git a/docs/api-reference/endpoints/secrets/read.mdx b/docs/api-reference/endpoints/secrets/read.mdx index 4305f192c..c88fb0ec7 100644 --- a/docs/api-reference/endpoints/secrets/read.mdx +++ b/docs/api-reference/endpoints/secrets/read.mdx @@ -1,4 +1,4 @@ --- -title: "Read" +title: "Retrieve" openapi: "GET /api/v2/secrets/" --- diff --git a/docs/api-reference/endpoints/users/me.mdx b/docs/api-reference/endpoints/users/me.mdx new file mode 100644 index 000000000..9273f0ca6 --- /dev/null +++ b/docs/api-reference/endpoints/users/me.mdx @@ -0,0 +1,4 @@ +--- +title: "Get Current User" +openapi: "GET /api/v2/users/me" +--- diff --git a/docs/api-reference/endpoints/workspaces/workspace-key.mdx b/docs/api-reference/endpoints/workspaces/workspace-key.mdx new file mode 100644 index 000000000..2658676a3 --- /dev/null +++ b/docs/api-reference/endpoints/workspaces/workspace-key.mdx @@ -0,0 +1,4 @@ +--- +title: "Get Project Key" +openapi: "GET /api/v2/workspace/{workspaceId}/encrypted-key" +--- diff --git a/docs/api-reference/overview/authentication.mdx b/docs/api-reference/overview/authentication.mdx index 27a2dc134..8cc218253 100644 --- a/docs/api-reference/overview/authentication.mdx +++ b/docs/api-reference/overview/authentication.mdx @@ -1,3 +1,11 @@ --- title: "Authentication" --- + +To authenticate requests with Infisical, you must include an API key in the `X-API-KEY` header of HTTP requests made to the platform. You can obtain an API key from your user settings. + + + It's important to keep your API key secure, as it grants access to your + secrets in Infisical. For added security, consider rotating your API key on a + regular basis. + diff --git a/docs/api-reference/overview/examples/create-secrets.mdx b/docs/api-reference/overview/examples/create-secrets.mdx new file mode 100644 index 000000000..94f830094 --- /dev/null +++ b/docs/api-reference/overview/examples/create-secrets.mdx @@ -0,0 +1,64 @@ +--- +title: "Create secrets" +--- + +In this example, we demonstrate how to add secrets to a project and environment. + +Prerequisites: + +- Set up and add envars to [Infisical Cloud](https://app.infisical.com) +- Grasp a basic understanding of the system and its underlying cryptography [here](/api-reference/overview/introduction). + +## Flow + +1. Get your (encrypted) private key. +2. Decrypt your (encrypted) private key with your password. +3. Get the project key for the project. +4. Decrypt the project key with your private key. +5. Encrypt your secrets with the project key. +6. Send (encrypted) secrets to the Infical API + +## Example + +```js +const axios = require("axios"); +const aes = require("aes-256-gcm"); +const nacl = require("tweetnacl"); +nacl.util = require("tweetnacl-util"); + +const WORKSPACE_KEY = "3a7a243eb62078c13f09203e75e8cb32"; + +const secretKey = "SOME_KEY"; +const secretValue = "SOME_VALUE"; + +// encrypt key of secret +const { + ciphertext: secretKeyCiphertext, + iv: secretKeyIV, + tag: secretKeyTag, +} = aes.encrypt(secretKey, WORKSPACE_KEY); + +// encrypt value of secret +const { + ciphertext: secretValueCiphertext, + iv: secretValueIV, + tag: secretValueTag, +} = aes.encrypt(secretKey, WORKSPACE_KEY); + +// construct request body +const secret = { + secretKeyCiphertext, + secretKeyIV, + secretKeyTag, + secretValueCiphertext, + secretValueIV, + secretValueTag, +}; +``` + + + This example uses [TweetNaCl.js](https://tweetnacl.js.org/#/), a port of + TweetNacl/Nacl, to perform asymmeric decryption of the project key but there + are ports of NaCl in every major language. + + diff --git a/docs/api-reference/overview/examples/retrieve-secrets.mdx b/docs/api-reference/overview/examples/retrieve-secrets.mdx new file mode 100644 index 000000000..2a314e65d --- /dev/null +++ b/docs/api-reference/overview/examples/retrieve-secrets.mdx @@ -0,0 +1,10 @@ +--- +title: "Retrieve secrets" +--- + +1. Get your (encrypted) private key. +2. Decrypt your (encrypted) private key with your password. +3. Get the project key for the project. +4. Decrypt the project key with your private key. +5. Get secrets for a project and environment. +6. Decrypt the secrets in your project. diff --git a/docs/api-reference/overview/examples/update-secrets.mdx b/docs/api-reference/overview/examples/update-secrets.mdx new file mode 100644 index 000000000..b302710a3 --- /dev/null +++ b/docs/api-reference/overview/examples/update-secrets.mdx @@ -0,0 +1,10 @@ +--- +title: "Update secrets" +--- + +1. Get your (encrypted) private key. +2. Decrypt your (encrypted) private key with your password. +3. Get the project key for the project. +4. Decrypt the project key with your private key. +5. Encrypt your secrets with the project key. +6. Send (encrypted) updated secrets to the Infical API diff --git a/docs/api-reference/overview/introduction.mdx b/docs/api-reference/overview/introduction.mdx index 9632e3788..f93ba91fe 100644 --- a/docs/api-reference/overview/introduction.mdx +++ b/docs/api-reference/overview/introduction.mdx @@ -1,3 +1,19 @@ --- title: "Introduction" --- + +Infisical's REST API provides users an alternative way to programmatically access and manage +secrets via HTTP requests. This can be useful for automating tasks, such as +rotating credentials, or for integrating secret management into a larger system. + +With the REST API, users can create, read, update, and delete secrets, as well as manage access control, query audit logs, and more. + +## Concepts + +Using Infisical's API to manage secrets requires a basic understanding of the system and its underlying cryptography detailed [here](/security/overview). + +- Each user has a public/private key pair that is stored with the platform; private keys are encrypted locally by the user's password before being sent off to the server during the account signup process. +- Each (encrypted) secret belongs to a project and environment. +- Each project has an (encrypted) project key used to encrypt the secrets within that project; Infisical stores copies of the project key, for each member of that project, encrypted under each member's public key. +- Secrets are encrypted symmetrically by your copy of the project key belonging to the project containing. +- Infisical uses AES256-GCM and [TweetNaCl.js](https://tweetnacl.js.org/#/) for symmetric and asymmetric encryption/decryption operations. diff --git a/docs/api-reference/overview/usage.mdx b/docs/api-reference/overview/usage.mdx new file mode 100644 index 000000000..9f23080c7 --- /dev/null +++ b/docs/api-reference/overview/usage.mdx @@ -0,0 +1,18 @@ +--- +title: "Usage" +--- + +Prerequisites: + +- Set up and add envars to [Infisical Cloud](https://app.infisical.com) or your self-hosted instance. +- Obtain an API Key in your user settings to be included in requests to the Infisical API. + +Using Infisical's API to manage secrets requires a basic understanding of the system and its underlying cryptography detailed [here](/security/overview). + +## Concepts + +- Each user has a public/private key pair that is stored with the platform; private keys are encrypted locally by the user's password before being sent off to the server during the account signup process. +- Each (encrypted) secret belongs to a project and environment. +- Each project has an (encrypted) project key used to encrypt the secrets within that project; Infisical stores copies of the project key, for each member of that project, encrypted under each member's public key. +- Secrets are encrypted symmetrically by your copy of the project key belonging to the project containing. +- Infisical uses AES256-GCM and [TweetNaCl.js](https://tweetnacl.js.org/#/) for symmetric and asymmetric encryption/decryption operations. diff --git a/docs/mint.json b/docs/mint.json index 363b27a3e..39a3726d9 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -21,6 +21,16 @@ "to": "#F8B7BD" } }, + "api": { + "baseUrl": [ + "https://app.infisical.com", + "http://localhost:8080" + ], + "auth": { + "method": "api-key", + "name": "X-API-KEY" + } + }, "topbarLinks": [ { "name": "Log In", "url": "https://app.infisical.com/login" } ], @@ -134,12 +144,32 @@ "group": "Overview", "pages": [ "api-reference/overview/introduction", - "api-reference/overview/authentication" + "api-reference/overview/authentication", + { + "group": "Examples", + "pages": [ + "api-reference/overview/examples/create-secrets", + "api-reference/overview/examples/retrieve-secrets", + "api-reference/overview/examples/update-secrets" + ] + } ] }, { "group": "Endpoints", "pages": [ + { + "group": "Users", + "pages": [ + "api-reference/endpoints/users/me" + ] + }, + { + "group": "Projects", + "pages": [ + "api-reference/endpoints/workspaces/workspace-key" + ] + }, { "group": "Secrets", "pages": [ diff --git a/docs/spec.yaml b/docs/spec.yaml index a0271395b..dd62f167b 100644 --- a/docs/spec.yaml +++ b/docs/spec.yaml @@ -4,7 +4,10 @@ info: description: List of all available APIs that can be consumed version: 1.0.0 servers: + - url: https://infisical.com + description: Production server - url: http://localhost:8080 + description: Local server paths: /api/v1/secret/{secretId}/secret-versions: get: @@ -1270,6 +1273,24 @@ paths: description: OK '400': description: Bad Request + /api/v2/users/me: + get: + summary: Retrieve the current user on the request + description: Retrieve the current user on the request + parameters: [] + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + $ref: '#/components/schemas/CurrentUser' + description: Current user on request + '400': + description: Bad Request + security: + - apiKeyAuth: [] /api/v2/workspace/{workspaceId}/secrets: post: description: '' @@ -1321,18 +1342,29 @@ paths: description: Bad Request /api/v2/workspace/{workspaceId}/encrypted-key: get: - description: '' + summary: Return encrypted project key + description: Return encrypted project key parameters: - name: workspaceId in: path required: true schema: type: string + description: ID of project responses: '200': description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ProjectKey' + description: Encrypted project key for the given project '400': description: Bad Request + security: + - apiKeyAuth: [] /api/v2/workspace/{workspaceId}/service-token-data: get: description: '' @@ -1513,57 +1545,122 @@ paths: example: any /api/v2/secrets/: post: - description: '' + summary: Create new secret(s) + description: Create one or many secrets for a given project and environment. parameters: [] responses: '200': description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Secret' + description: >- + Array of newly-created secrets for the given project and + environment + security: + - apiKeyAuth: [] requestBody: + required: true content: application/json: schema: type: object properties: - secrets: - example: any workspaceId: - example: any + type: string + description: ID of project environment: - example: any + type: string + description: Environment within project + secrets: + $ref: '#/components/schemas/CreateSecret' + description: Secret(s) to create - object or array of objects get: - description: '' - parameters: - - name: workspaceId - in: query - schema: - type: string - - name: environment - in: query - schema: - type: string - responses: - '200': - description: OK - patch: - description: '' + summary: Read secrets + description: Read secrets from a project and environment parameters: [] responses: '200': description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Secret' + description: Array of secrets for the given project and environment + security: + - apiKeyAuth: [] requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + workspaceId: + type: string + description: ID of project + environment: + type: string + description: Environment within project + patch: + summary: Update secret(s) + description: Update secret(s) + parameters: [] + responses: + '200': + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Secret' + description: >- + Array of newly-updated secrets for the given project and + environment + security: + - apiKeyAuth: [] + requestBody: + required: true content: application/json: schema: type: object properties: secrets: - example: any + $ref: '#/components/schemas/UpdateSecret' + description: Secret(s) to update - object or array of objects delete: - description: '' + summary: Delete secret(s) + description: Delete one or many secrets by their ID(s) parameters: [] responses: '200': description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Secret' + description: Array of deleted secrets + security: + - apiKeyAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + secretIds: + type: string + description: ID(s) of secrets - string or array of strings /api/v2/service-token/: get: description: '' @@ -1665,26 +1762,183 @@ paths: description: OK components: schemas: - secret: + CurrentUser: + type: object + properties: + _id: + type: string + example: '' + email: + type: string + example: '' + firstName: + type: string + example: '' + lastName: + type: string + example: '' + publicKey: + type: string + example: '' + encryptedPrivateKey: + type: string + example: '' + updatedAt: + type: string + example: '' + createdAt: + type: string + example: '' + ProjectKey: + type: object + properties: + encryptedkey: + type: string + example: '' + nonce: + type: string + example: '' + sender: + type: object + properties: + publicKey: + type: string + example: '' + receiver: + type: string + example: '' + workspace: + type: string + example: '' + CreateSecret: type: object properties: type: type: string - example: object - properties: - type: object - properties: - test: - type: object - properties: - type: - type: string - example: integer - description: - type: string - example: '123' + example: shared + secretKeyCiphertext: + type: string + example: '' + secretKeyIV: + type: string + example: '' + secretKeyTag: + type: string + example: '' + secretValueCiphertext: + type: string + example: '' + secretValueIV: + type: string + example: '' + secretValueTag: + type: string + example: '' + secretCommentCiphertext: + type: string + example: '' + secretCommentIV: + type: string + example: '' + secretCommentTag: + type: string + example: '' + UpdateSecret: + type: object + properties: + id: + type: string + example: '' + secretKeyCiphertext: + type: string + example: '' + secretKeyIV: + type: string + example: '' + secretKeyTag: + type: string + example: '' + secretValueCiphertext: + type: string + example: '' + secretValueIV: + type: string + example: '' + secretValueTag: + type: string + example: '' + secretCommentCiphertext: + type: string + example: '' + secretCommentIV: + type: string + example: '' + secretCommentTag: + type: string + example: '' + Secret: + type: object + properties: + _id: + type: string + example: '' + version: + type: number + example: 1 + workspace: + type: string + example: '' + type: + type: string + example: shared + user: {} + secretKeyCiphertext: + type: string + example: '' + secretKeyIV: + type: string + example: '' + secretKeyTag: + type: string + example: '' + secretValueCiphertext: + type: string + example: '' + secretValueIV: + type: string + example: '' + secretValueTag: + type: string + example: '' + secretCommentCiphertext: + type: string + example: '' + secretCommentIV: + type: string + example: '' + secretCommentTag: + type: string + example: '' + updatedAt: + type: string + example: '' + createdAt: + type: string + example: '' securitySchemes: bearerAuth: type: http scheme: bearer bearerFormat: JWT + description: >- + This security definition uses the HTTP 'bearer' scheme, which allows the + client to authenticate using a JSON Web Token (JWT) that is passed in + the Authorization header of the request. + apiKeyAuth: + type: apiKey + in: header + name: X-API-Key + description: >- + This security definition uses an API key, which is passed in the header + of the request as the value of the "X-API-Key" header. The client must + provide a valid key in order to access the API. From b67abf94d4e4e265686735e35d406856216cffaf Mon Sep 17 00:00:00 2001 From: Vladyslav Matsiiako Date: Fri, 13 Jan 2023 01:02:43 -0800 Subject: [PATCH 31/65] Fixing minor bugs for custom environments --- frontend/components/basic/Listbox.tsx | 2 +- .../basic/dialog/AddServiceTokenDialog.js | 4 +- frontend/pages/dashboard/[id].tsx | 75 ++----------------- frontend/pages/settings/project/[id].tsx | 1 + 4 files changed, 12 insertions(+), 70 deletions(-) diff --git a/frontend/components/basic/Listbox.tsx b/frontend/components/basic/Listbox.tsx index d315df2e6..c67735d6e 100644 --- a/frontend/components/basic/Listbox.tsx +++ b/frontend/components/basic/Listbox.tsx @@ -48,7 +48,7 @@ export default function ListBox({ {text} {' '} - {selected} + {selected?.charAt(0).toUpperCase() + selected?.slice(1)}
{data && ( diff --git a/frontend/components/basic/dialog/AddServiceTokenDialog.js b/frontend/components/basic/dialog/AddServiceTokenDialog.js index a8c5c15a7..0be330708 100644 --- a/frontend/components/basic/dialog/AddServiceTokenDialog.js +++ b/frontend/components/basic/dialog/AddServiceTokenDialog.js @@ -66,7 +66,7 @@ const AddServiceTokenDialog = ({ let newServiceToken = await addServiceToken({ name: serviceTokenName, workspaceId, - environment: selectedServiceTokenEnv.slug, + environment: selectedServiceTokenEnv?.slug ? selectedServiceTokenEnv.slug : environments[0]?.name, expiresIn: expiryMapping[serviceTokenExpiresIn], encryptedKey: ciphertext, iv, @@ -156,7 +156,7 @@ const AddServiceTokenDialog = ({
name)} onChange={(envName) => setSelectedServiceTokenEnv( diff --git a/frontend/pages/dashboard/[id].tsx b/frontend/pages/dashboard/[id].tsx index 8f6d21c13..395ed2924 100644 --- a/frontend/pages/dashboard/[id].tsx +++ b/frontend/pages/dashboard/[id].tsx @@ -237,7 +237,9 @@ export default function Dashboard() { setInitialData(dataToSort); reorderRows(dataToSort); - setIsLoading(false); + setTimeout( + () => setIsLoading(false) + , 700); } catch (error) { console.log('Error', error); setData(undefined); @@ -361,67 +363,6 @@ export default function Dashboard() { // Once "Save changes" is clicked, disable that button setButtonReady(false); - const secretsToBeDeleted = initialData - .filter( - (initDataPoint) => - !newData! - .map((newDataPoint) => newDataPoint.id) - .includes(initDataPoint.id) - ) - .map((secret) => secret.id); - - const secretsToBeAdded = newData!.filter( - (newDataPoint) => - !initialData - .map((initDataPoint) => initDataPoint.id) - .includes(newDataPoint.id) - ); - - const secretsToBeUpdated = newData!.filter((newDataPoint) => - initialData - .filter( - (initDataPoint) => - newData! - .map((newDataPoint) => newDataPoint.id) - .includes(initDataPoint.id) && - (newData!.filter( - (newDataPoint) => newDataPoint.id == initDataPoint.id - )[0].value != initDataPoint.value || - newData!.filter( - (newDataPoint) => newDataPoint.id == initDataPoint.id - )[0].key != initDataPoint.key || - newData!.filter( - (newDataPoint) => newDataPoint.id == initDataPoint.id - )[0].comment != initDataPoint.comment) - ) - .map((secret) => secret.id) - .includes(newDataPoint.id) - ); - - if (secretsToBeDeleted.length > 0) { - await deleteSecrets({ secretIds: secretsToBeDeleted }); - } - // ENV - if (secretsToBeAdded.length > 0) { - const secrets = await encryptSecrets({ - secretsToEncrypt: secretsToBeAdded, - workspaceId, - env: selectedEnv.slug, - }); - secrets && - (await addSecrets({ - secrets, - env: selectedEnv.slug, - workspaceId, - })); - } - if (secretsToBeUpdated.length > 0) { - const secrets = await encryptSecrets({ - secretsToEncrypt: secretsToBeUpdated, - workspaceId, - env: selectedEnv.slug, - }); - secrets && (await updateSecrets({ secrets })); const secretsToBeDeleted = initialData! .filter(initDataPoint => !newData!.map(newDataPoint => newDataPoint.id).includes(initDataPoint.id)) @@ -471,11 +412,11 @@ export default function Dashboard() { await deleteSecrets({ secretIds: secretsToBeDeleted.concat(overridesToBeDeleted) }); } if (secretsToBeAdded.concat(overridesToBeAdded).length > 0) { - const secrets = await encryptSecrets({ secretsToEncrypt: secretsToBeAdded.concat(overridesToBeAdded), workspaceId, env: envMapping[env] }); - secrets && await addSecrets({ secrets, env: envMapping[env], workspaceId }); + const secrets = await encryptSecrets({ secretsToEncrypt: secretsToBeAdded.concat(overridesToBeAdded), workspaceId, env: selectedEnv.slug }); + secrets && await addSecrets({ secrets, env: selectedEnv.slug, workspaceId }); } if (secretsToBeUpdated.concat(overridesToBeUpdated).length > 0) { - const secrets = await encryptSecrets({ secretsToEncrypt: secretsToBeUpdated.concat(overridesToBeUpdated), workspaceId, env: envMapping[env] }); + const secrets = await encryptSecrets({ secretsToEncrypt: secretsToBeUpdated.concat(overridesToBeUpdated), workspaceId, env: selectedEnv.slug }); secrets && await updateSecrets({ secrets }); } @@ -643,7 +584,7 @@ export default function Dashboard() { onButtonPressed={async () => { // Update secrets in the state only for the current environment const rolledBackSecrets = snapshotData.secretVersions - .filter(row => reverseEnvMapping[row.environment] == env) + .filter(row => row.environment == selectedEnv.slug) .map((sv, position) => { return { id: sv.id, idOverride: sv.id, pos: position, valueOverride: sv.valueOverride, key: sv.key, value: sv.value, comment: '' @@ -799,7 +740,7 @@ export default function Dashboard() { /> ))} {snapshotData && snapshotData.secretVersions?.sort((a, b) => a.key.localeCompare(b.key)) - .filter(row => reverseEnvMapping[row.environment] == snapshotEnv) + .filter(row => row.environment == selectedSnapshotEnv?.slug) .filter(row => row.key.toUpperCase().includes(searchKeys.toUpperCase())) .filter( row => !(snapshotData.secretVersions?.filter(row => (snapshotData.secretVersions diff --git a/frontend/pages/settings/project/[id].tsx b/frontend/pages/settings/project/[id].tsx index 619ef2e6b..5b9e3f5de 100644 --- a/frontend/pages/settings/project/[id].tsx +++ b/frontend/pages/settings/project/[id].tsx @@ -25,6 +25,7 @@ type EnvData = { name: string; slug: string; }; + export default function SettingsBasic() { const [buttonReady, setButtonReady] = useState(false); const router = useRouter(); From 6c61aef526117ec32bf9234f7d20b32ea69fe88a Mon Sep 17 00:00:00 2001 From: Vladyslav Matsiiako Date: Fri, 13 Jan 2023 01:29:14 -0800 Subject: [PATCH 32/65] hotfix: fix the bug with pushing multiple envars in a sequence --- frontend/pages/dashboard/[id].tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/pages/dashboard/[id].tsx b/frontend/pages/dashboard/[id].tsx index 07b4133a0..949db52c0 100644 --- a/frontend/pages/dashboard/[id].tsx +++ b/frontend/pages/dashboard/[id].tsx @@ -412,7 +412,7 @@ export default function Dashboard() { secrets && await updateSecrets({ secrets }); } - setInitialData(newData); + setInitialData(structuredClone(newData)); // If this user has never saved environment variables before, show them a prompt to read docs if (!hasUserEverPushed) { From 6992c51e17bcbde1a49a0078db329b1dd6a1bcb5 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Fri, 13 Jan 2023 16:36:34 +0700 Subject: [PATCH 33/65] Working fixing integrations race condition --- backend/src/helpers/secret.ts | 2 +- backend/src/integrations/sync.ts | 231 +++++++++++++++---------------- 2 files changed, 116 insertions(+), 117 deletions(-) diff --git a/backend/src/helpers/secret.ts b/backend/src/helpers/secret.ts index 59d72f469..74ba062bd 100644 --- a/backend/src/helpers/secret.ts +++ b/backend/src/helpers/secret.ts @@ -39,7 +39,7 @@ const validateSecrets = async ({ try { secrets = await Secret.find({ _id: { - $in: secretIds + $in: secretIds.map((secretId: string) => new Types.ObjectId(secretId)) } }); diff --git a/backend/src/integrations/sync.ts b/backend/src/integrations/sync.ts index c4a4f16ee..81bbe7549 100644 --- a/backend/src/integrations/sync.ts +++ b/backend/src/integrations/sync.ts @@ -1,4 +1,4 @@ -import axios from 'axios'; +import axios, { AxiosError } from 'axios'; import * as Sentry from '@sentry/node'; import { Octokit } from '@octokit/rest'; // import * as sodium from 'libsodium-wrappers'; @@ -145,7 +145,6 @@ const syncSecretsVercel = async ({ secrets: any; accessToken: string; }) => { - interface VercelSecret { id?: string; type: string; @@ -155,131 +154,131 @@ const syncSecretsVercel = async ({ } try { - // Get all (decrypted) secrets back from Vercel in - // decrypted format - const params: { [key: string]: string } = { - decrypt: 'true', - ...( integrationAuth?.teamId ? { - teamId: integrationAuth.teamId - } : {}) - } - - const res = (await Promise.all((await axios.get( - `${INTEGRATION_VERCEL_API_URL}/v9/projects/${integration.app}/env`, - { + // Get all (decrypted) secrets back from Vercel in + // decrypted format + const params: { [key: string]: string } = { + decrypt: 'true', + ...( integrationAuth?.teamId ? { + teamId: integrationAuth.teamId + } : {}) + } + + const res = (await Promise.all((await axios.get( + `${INTEGRATION_VERCEL_API_URL}/v9/projects/${integration.app}/env`, + { + params, + headers: { + Authorization: `Bearer ${accessToken}` + } + } + )) + .data + .envs + .filter((secret: VercelSecret) => secret.target.includes(integration.target)) + .map(async (secret: VercelSecret) => (await axios.get( + `${INTEGRATION_VERCEL_API_URL}/v9/projects/${integration.app}/env/${secret.id}`, + { params, headers: { Authorization: `Bearer ${accessToken}` } - } - )) - .data - .envs - .filter((secret: VercelSecret) => secret.target.includes(integration.target)) - .map(async (secret: VercelSecret) => (await axios.get( - `${INTEGRATION_VERCEL_API_URL}/v9/projects/${integration.app}/env/${secret.id}`, - { - params, - headers: { - Authorization: `Bearer ${accessToken}` - } - } - )).data) - )).reduce((obj: any, secret: any) => ({ - ...obj, - [secret.key]: secret - }), {}); - - const updateSecrets: VercelSecret[] = []; - const deleteSecrets: VercelSecret[] = []; - const newSecrets: VercelSecret[] = []; + } + )).data) + )).reduce((obj: any, secret: any) => ({ + ...obj, + [secret.key]: secret + }), {}); + + const updateSecrets: VercelSecret[] = []; + const deleteSecrets: VercelSecret[] = []; + const newSecrets: VercelSecret[] = []; - // Identify secrets to create - Object.keys(secrets).map((key) => { - if (!(key in res)) { - // case: secret has been created - newSecrets.push({ - key: key, - value: secrets[key], - type: 'encrypted', - target: [integration.target] - }); - } - }); - - // Identify secrets to update and delete - Object.keys(res).map((key) => { - if (key in secrets) { - if (res[key].value !== secrets[key]) { - // case: secret value has changed - updateSecrets.push({ - id: res[key].id, - key: key, - value: secrets[key], - type: 'encrypted', - target: [integration.target] - }); - } - } else { - // case: secret has been deleted - deleteSecrets.push({ - id: res[key].id, - key: key, - value: res[key].value, - type: 'encrypted', - target: [integration.target], - }); - } - }); + // Identify secrets to create + Object.keys(secrets).map((key) => { + if (!(key in res)) { + // case: secret has been created + newSecrets.push({ + key: key, + value: secrets[key], + type: 'encrypted', + target: [integration.target] + }); + } + }); + + // Identify secrets to update and delete + Object.keys(res).map((key) => { + if (key in secrets) { + if (res[key].value !== secrets[key]) { + // case: secret value has changed + updateSecrets.push({ + id: res[key].id, + key: key, + value: secrets[key], + type: 'encrypted', + target: [integration.target] + }); + } + } else { + // case: secret has been deleted + deleteSecrets.push({ + id: res[key].id, + key: key, + value: res[key].value, + type: 'encrypted', + target: [integration.target], + }); + } + }); - // Sync/push new secrets - if (newSecrets.length > 0) { - await axios.post( - `${INTEGRATION_VERCEL_API_URL}/v10/projects/${integration.app}/env`, - newSecrets, - { - params, - headers: { - Authorization: `Bearer ${accessToken}` - } + // Sync/push new secrets + if (newSecrets.length > 0) { + await axios.post( + `${INTEGRATION_VERCEL_API_URL}/v10/projects/${integration.app}/env`, + newSecrets, + { + params, + headers: { + Authorization: `Bearer ${accessToken}` } - ); - } + } + ); + } - // Sync/push updated secrets - if (updateSecrets.length > 0) { - updateSecrets.forEach(async (secret: VercelSecret) => { - const { - id, - ...updatedSecret - } = secret; - await axios.patch( - `${INTEGRATION_VERCEL_API_URL}/v9/projects/${integration.app}/env/${secret.id}`, - updatedSecret, - { - params, - headers: { - Authorization: `Bearer ${accessToken}` - } + // Sync/push updated secrets + if (updateSecrets.length > 0) { + updateSecrets.forEach(async (secret: VercelSecret) => { + const { + id, + ...updatedSecret + } = secret; + await axios.patch( + `${INTEGRATION_VERCEL_API_URL}/v9/projects/${integration.app}/env/${secret.id}`, + updatedSecret, + { + params, + headers: { + Authorization: `Bearer ${accessToken}` } - ); - }); - } + } + ); + }); + } - // Delete secrets - if (deleteSecrets.length > 0) { - deleteSecrets.forEach(async (secret: VercelSecret) => { - await axios.delete( - `${INTEGRATION_VERCEL_API_URL}/v9/projects/${integration.app}/env/${secret.id}`, - { - params, - headers: { - Authorization: `Bearer ${accessToken}` - } + // Delete secrets + if (deleteSecrets.length > 0) { + deleteSecrets.forEach(async (secret: VercelSecret) => { + await axios.delete( + `${INTEGRATION_VERCEL_API_URL}/v9/projects/${integration.app}/env/${secret.id}`, + { + params, + headers: { + Authorization: `Bearer ${accessToken}` } - ); - }); - } + } + ); + }); + } } catch (err) { Sentry.setUser(null); Sentry.captureException(err); From cbd8302afe6897db134b5ed90fd5acd4fea8adbb Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Fri, 13 Jan 2023 18:15:17 +0700 Subject: [PATCH 34/65] Add temp patch for CRUD ops race conditions --- .../src/controllers/v2/secretsController.ts | 30 +- backend/src/integrations/sync.ts | 352 +++++++++--------- 2 files changed, 193 insertions(+), 189 deletions(-) diff --git a/backend/src/controllers/v2/secretsController.ts b/backend/src/controllers/v2/secretsController.ts index ea495ad87..098cfed6d 100644 --- a/backend/src/controllers/v2/secretsController.ts +++ b/backend/src/controllers/v2/secretsController.ts @@ -67,8 +67,17 @@ export const createSecrets = async (req: Request, res: Response) => { })) ); + setTimeout(async () => { + // trigger event - push secrets + await EventService.handleEvent({ + event: eventPushSecrets({ + workspaceId + }) + }); + }, 5000); + // (EE) add secret versions for new secrets - EESecretService.addSecretVersions({ + await EESecretService.addSecretVersions({ secretVersions: newSecrets.map(({ _id, version, @@ -104,13 +113,6 @@ export const createSecrets = async (req: Request, res: Response) => { })) }); - // trigger event - push secrets - await EventService.handleEvent({ - event: eventPushSecrets({ - workspaceId - }) - }); - const addAction = await EELogService.createActionSecret({ name: ACTION_ADD_SECRETS, userId: req.user._id.toString(), @@ -342,11 +344,13 @@ export const updateSecrets = async (req: Request, res: Response) => { Object.keys(workspaceSecretObj).forEach(async (key) => { // trigger event - push secrets - await EventService.handleEvent({ - event: eventPushSecrets({ - workspaceId: key - }) - }); + setTimeout(async () => { + await EventService.handleEvent({ + event: eventPushSecrets({ + workspaceId: key + }) + }); + }, 10000); const updateAction = await EELogService.createActionSecret({ name: ACTION_UPDATE_SECRETS, diff --git a/backend/src/integrations/sync.ts b/backend/src/integrations/sync.ts index 81bbe7549..0ae57dc59 100644 --- a/backend/src/integrations/sync.ts +++ b/backend/src/integrations/sync.ts @@ -306,188 +306,188 @@ const syncSecretsNetlify = async ({ }) => { try { - interface NetlifyValue { - id?: string; - context: string; // 'dev' | 'branch-deploy' | 'deploy-preview' | 'production', - value: string; - } - - interface NetlifySecret { - key: string; - values: NetlifyValue[]; - } - - interface NetlifySecretsRes { - [index: string]: NetlifySecret; - } - - const getParams = new URLSearchParams({ - context_name: 'all', // integration.context or all - site_id: integration.siteId - }); - - const res = (await axios.get( - `${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env`, - { - params: getParams, - headers: { - Authorization: `Bearer ${accessToken}` - } - } - )) - .data - .reduce((obj: any, secret: any) => ({ - ...obj, - [secret.key]: secret - }), {}); - - const newSecrets: NetlifySecret[] = []; // createEnvVars - const deleteSecrets: string[] = []; // deleteEnvVar - const deleteSecretValues: NetlifySecret[] = []; // deleteEnvVarValue - const updateSecrets: NetlifySecret[] = []; // setEnvVarValue + interface NetlifyValue { + id?: string; + context: string; // 'dev' | 'branch-deploy' | 'deploy-preview' | 'production', + value: string; + } + + interface NetlifySecret { + key: string; + values: NetlifyValue[]; + } + + interface NetlifySecretsRes { + [index: string]: NetlifySecret; + } + + const getParams = new URLSearchParams({ + context_name: 'all', // integration.context or all + site_id: integration.siteId + }); + + const res = (await axios.get( + `${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env`, + { + params: getParams, + headers: { + Authorization: `Bearer ${accessToken}` + } + } + )) + .data + .reduce((obj: any, secret: any) => ({ + ...obj, + [secret.key]: secret + }), {}); + + const newSecrets: NetlifySecret[] = []; // createEnvVars + const deleteSecrets: string[] = []; // deleteEnvVar + const deleteSecretValues: NetlifySecret[] = []; // deleteEnvVarValue + const updateSecrets: NetlifySecret[] = []; // setEnvVarValue + + // identify secrets to create and update + Object.keys(secrets).map((key) => { + if (!(key in res)) { + // case: Infisical secret does not exist in Netlify -> create secret + newSecrets.push({ + key, + values: [{ + value: secrets[key], + context: integration.context + }] + }); + } else { + // case: Infisical secret exists in Netlify + const contexts = res[key].values + .reduce((obj: any, value: NetlifyValue) => ({ + ...obj, + [value.context]: value + }), {}); + + if (integration.context in contexts) { + // case: Netlify secret value exists in integration context + if (secrets[key] !== contexts[integration.context].value) { + // case: Infisical and Netlify secret values are different + // -> update Netlify secret context and value + updateSecrets.push({ + key, + values: [{ + context: integration.context, + value: secrets[key] + }] + }); + } + } else { + // case: Netlify secret value does not exist in integration context + // -> add the new Netlify secret context and value + updateSecrets.push({ + key, + values: [{ + context: integration.context, + value: secrets[key] + }] + }); + } + } + }) + + // identify secrets to delete + // TODO: revise (patch case where 1 context was deleted but others still there + Object.keys(res).map((key) => { + // loop through each key's context + if (!(key in secrets)) { + // case: Netlify secret does not exist in Infisical + + const numberOfValues = res[key].values.length; + + res[key].values.forEach((value: NetlifyValue) => { + if (value.context === integration.context) { + if (numberOfValues <= 1) { + // case: Netlify secret value has less than 1 context -> delete secret + deleteSecrets.push(key); + } else { + // case: Netlify secret value has more than 1 context -> delete secret value context + deleteSecretValues.push({ + key, + values: [{ + id: value.id, + context: integration.context, + value: value.value + }] + }); + } + } + }); + } + }); - // identify secrets to create and update - Object.keys(secrets).map((key) => { - if (!(key in res)) { - // case: Infisical secret does not exist in Netlify -> create secret - newSecrets.push({ - key, - values: [{ - value: secrets[key], - context: integration.context - }] - }); - } else { - // case: Infisical secret exists in Netlify - const contexts = res[key].values - .reduce((obj: any, value: NetlifyValue) => ({ - ...obj, - [value.context]: value - }), {}); - - if (integration.context in contexts) { - // case: Netlify secret value exists in integration context - if (secrets[key] !== contexts[integration.context].value) { - // case: Infisical and Netlify secret values are different - // -> update Netlify secret context and value - updateSecrets.push({ - key, - values: [{ - context: integration.context, - value: secrets[key] - }] - }); - } - } else { - // case: Netlify secret value does not exist in integration context - // -> add the new Netlify secret context and value - updateSecrets.push({ - key, - values: [{ - context: integration.context, - value: secrets[key] - }] - }); - } - } - }) - - // identify secrets to delete - // TODO: revise (patch case where 1 context was deleted but others still there - Object.keys(res).map((key) => { - // loop through each key's context - if (!(key in secrets)) { - // case: Netlify secret does not exist in Infisical - - const numberOfValues = res[key].values.length; - - res[key].values.forEach((value: NetlifyValue) => { - if (value.context === integration.context) { - if (numberOfValues <= 1) { - // case: Netlify secret value has less than 1 context -> delete secret - deleteSecrets.push(key); - } else { - // case: Netlify secret value has more than 1 context -> delete secret value context - deleteSecretValues.push({ - key, - values: [{ - id: value.id, - context: integration.context, - value: value.value - }] - }); - } - } - }); - } - }); + const syncParams = new URLSearchParams({ + site_id: integration.siteId + }); - const syncParams = new URLSearchParams({ - site_id: integration.siteId - }); + if (newSecrets.length > 0) { + await axios.post( + `${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env`, + newSecrets, + { + params: syncParams, + headers: { + Authorization: `Bearer ${accessToken}` + } + } + ); + } - if (newSecrets.length > 0) { - await axios.post( - `${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env`, - newSecrets, - { - params: syncParams, - headers: { - Authorization: `Bearer ${accessToken}` - } - } - ); - } + if (updateSecrets.length > 0) { + updateSecrets.forEach(async (secret: NetlifySecret) => { + await axios.patch( + `${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env/${secret.key}`, + { + context: secret.values[0].context, + value: secret.values[0].value + }, + { + params: syncParams, + headers: { + Authorization: `Bearer ${accessToken}` + } + } + ); + }); + } - if (updateSecrets.length > 0) { - updateSecrets.forEach(async (secret: NetlifySecret) => { - await axios.patch( - `${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env/${secret.key}`, - { - context: secret.values[0].context, - value: secret.values[0].value - }, - { - params: syncParams, - headers: { - Authorization: `Bearer ${accessToken}` - } - } - ); - }); - } + if (deleteSecrets.length > 0) { + deleteSecrets.forEach(async (key: string) => { + await axios.delete( + `${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env/${key}`, + { + params: syncParams, + headers: { + Authorization: `Bearer ${accessToken}` + } + } + ); + }); + } - if (deleteSecrets.length > 0) { - deleteSecrets.forEach(async (key: string) => { - await axios.delete( - `${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env/${key}`, - { - params: syncParams, - headers: { - Authorization: `Bearer ${accessToken}` - } - } - ); - }); - } - - if (deleteSecretValues.length > 0) { - deleteSecretValues.forEach(async (secret: NetlifySecret) => { - await axios.delete( - `${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env/${secret.key}/value/${secret.values[0].id}`, - { - params: syncParams, - headers: { - Authorization: `Bearer ${accessToken}` - } - } - ); - }); - } + if (deleteSecretValues.length > 0) { + deleteSecretValues.forEach(async (secret: NetlifySecret) => { + await axios.delete( + `${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env/${secret.key}/value/${secret.values[0].id}`, + { + params: syncParams, + headers: { + Authorization: `Bearer ${accessToken}` + } + } + ); + }); + } } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - throw new Error('Failed to sync secrets to Heroku'); + Sentry.setUser(null); + Sentry.captureException(err); + throw new Error('Failed to sync secrets to Heroku'); } } From 29592a1e9e89299c3d1dd084be38cf45811802b6 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Fri, 13 Jan 2023 20:25:13 +0700 Subject: [PATCH 35/65] Add SMTP support for AWS SES and docs for it --- backend/src/services/smtp.ts | 8 ++++++- docs/images/email-aws-ses-console.png | Bin 0 -> 378652 bytes docs/images/email-aws-ses-user.png | Bin 0 -> 176008 bytes docs/self-hosting/configuration/email.mdx | 28 +++++++++++++++++++--- 4 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 docs/images/email-aws-ses-console.png create mode 100644 docs/images/email-aws-ses-user.png diff --git a/backend/src/services/smtp.ts b/backend/src/services/smtp.ts index 12841eee7..f960f6bae 100644 --- a/backend/src/services/smtp.ts +++ b/backend/src/services/smtp.ts @@ -28,7 +28,13 @@ if (SMTP_SECURE) { } break; default: - mailOpts.secure = true; + if (SMTP_HOST.includes('amazonaws.com')) { + mailOpts.tls = { + ciphers: 'TLSv1.2' + } + } else { + mailOpts.secure = true; + } break; } } diff --git a/docs/images/email-aws-ses-console.png b/docs/images/email-aws-ses-console.png new file mode 100644 index 0000000000000000000000000000000000000000..2882ba4d5f6aef3febc27f9a75c61795eba5a8c2 GIT binary patch literal 378652 zcmbrl2Rz)(_AstX2qGeaC?SYw(OZ-xO7tFmbyi!^7ORtlC?O$wiyk#PyH<&A^|n}x z5WV-h{5Q|@+Z@w8EVpHqZ}51mnX^8^eR1<4Aug=4K7=bG{KjK0=*V5CyA@WgKe8t;hz#Y%JS)s! zE|I;t0lOIR-D&h4kU5d`+73jdovqB^29>nlBM~&W!4V+5qxH5~l!sS3_&q(|?Vw3~S4aHJ zWp;gqdjmyM?}z)N;fAlZt*tBNw(R7CJxZ2ml$jZaiDI0Ak;#u@dTa~2eTov5S}%4x ze%u1jUZ06w)mhuVDsWF?iHv_j5-uyGP2<5Q#r0|+BzNe${jxg;hC^JB?zU9_1Aak^ zlm@3v=Cj$Tp?Q1t<%^16u8&Fz?Uz6JvlbY}Q$HO1!fh##pUjie!=)CJfYR~d_PM+q zE1a|+_}TSVy-G|<0N5GESr?g0xqt417r2w$7_dJp*b%svIQV(zORfT8x3Asc*dWzY zTg!;WUp1~i(oKTW!)}3J+l@0+k|=pkR%}iYTMKEg&Hy*}Ukq%LCCao3fz3~aLSHfC zYPS&j#S#Krh=(ni321}J_8&Ex^vJ#JLt~wAu5_% z%y?{BL_(ijLMWdSPqZ+K;fl@^`aNg9fv?5P$W8PhmP8=<1~(z1l}__UM62z0GTjim z@5E!F`14PJ#2hV#ZzxR&k*&_Vx4T=Iy>BYC5I-e)ew&=-<=t0QBVnq{T-}V>FBxO` z?%tA@-P2@=$GgeQS^dC>nEGX&CR;VVLlF7%uB@sdn?edG#nH>QA>&ojs1T*^bS0#z zKithF`hr5|AA3`#;n}sFy}@m~Sr+8@6Z3}N?_Lwh+SclK|vy@kJ3{~`Q?*&?d&g%B&0Dkt2& zUAUdZQg9D%k8&?PBqZc(NaPPc!loY%??~Q-k|f?erRZ4ioZvRk8d0MWBHT@x) z1I`-PlW)!EhG+OYEF}^AfJAT_eTj!+lXB(X*F?7>(0J>+n9i84Wh!MFX!>=V z9PJJ*KP-d*AodU&NWtOzBhGWib3cp#X7rp7xGqG*6Z-nOqy|0z5dV#*v2>dvtHSpM zg#@ESOdKaIPbb5Ha-zy2PXyNm==di@$@u}G8SBfIx8^TIm4v52BhLH7V|_vs9&mfu zn!6yGV1i+jVcZm}du2m`Ppwbs84qa*A({nkSrfs4Sw zV8<z3EiGM6y52{?D_y8PjLLe}Vt%)M#Ugz+!*I_A?_uSx5( z#nAn|6VCm$BfX>O`T$g`L&rzH3z+cILSp>v|;CfU9f{1WeF?fYjW!Z)ek)`kT?eLXqxz3Zp# kXD^@FEgXJmL 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 z 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 literal 0 HcmV?d00001 diff --git a/docs/images/email-aws-ses-user.png b/docs/images/email-aws-ses-user.png new file mode 100644 index 0000000000000000000000000000000000000000..f740e58a5eee015348fd93031b8b8652c9119ca7 GIT binary patch literal 176008 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ΝpO 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 ByB`1m literal 0 HcmV?d00001 diff --git a/docs/self-hosting/configuration/email.mdx b/docs/self-hosting/configuration/email.mdx index b1e911fb6..666ca7622 100644 --- a/docs/self-hosting/configuration/email.mdx +++ b/docs/self-hosting/configuration/email.mdx @@ -48,7 +48,7 @@ SMTP_FROM_NAME=Infisical ``` - Remember that you will need to restart Infisical for this to work properly. + Remember that you will need to restart Infisical for this to work properly. ## Mailgun @@ -70,6 +70,28 @@ SMTP_FROM_ADDRESS=hey@example.com # your email address being used to send out em SMTP_FROM_NAME=Infisical ``` +## AWS SES + +1. Create an account and [configure AWS SES](https://aws.amazon.com/premiumsupport/knowledge-center/ses-set-up-connect-smtp/) to send emails in the Amazon SES console. +2. Create an IAM user for SMTP authentication and obtain SMTP credentials in SMTP settings > Create SMTP credentials + +![opening AWS SES console](../../images/email-aws-ses-console.png) + +![creating AWS IAM SES user](../../images/email-aws-ses-user.png) + +3. With your AWS SES SMTP credentials, you can now set up your SMTP environment variables: + +``` +SMTP_HOST=smtp.mailgun.org # obtained from credentials page +SMTP_HOST=email-smtp.ap-northeast-1.amazonaws.com # SMTP endpoint obtained from SMTP settings +SMTP_USERNAME=xxx # your SMTP username +SMTP_PASSWORD=xxx # your SMTP password +SMTP_PORT=587 +SMTP_SECURE=true +SMTP_FROM_ADDRESS=hey@example.com # your email address being used to send out emails +SMTP_FROM_NAME=Infisical +``` + - Remember that you will need to restart Infisical for this to work properly. - \ No newline at end of file + Remember that you will need to restart Infisical for this to work properly. + From 036d32aeba2f899f9c999a85ec2aed0015388770 Mon Sep 17 00:00:00 2001 From: akhilmhdh Date: Fri, 13 Jan 2023 21:09:59 +0530 Subject: [PATCH 36/65] feat(#31): added multi env support in integration --- .../components/integrations/Integration.tsx | 164 ++++++++++-------- .../integrations/IntegrationSection.tsx | 7 +- frontend/pages/api/workspace/getAWorkspace.ts | 31 ++++ frontend/pages/integrations/[id].js | 20 ++- 4 files changed, 139 insertions(+), 83 deletions(-) create mode 100644 frontend/pages/api/workspace/getAWorkspace.ts diff --git a/frontend/components/integrations/Integration.tsx b/frontend/components/integrations/Integration.tsx index 73d6ee0dc..4dba86841 100644 --- a/frontend/components/integrations/Integration.tsx +++ b/frontend/components/integrations/Integration.tsx @@ -34,12 +34,24 @@ interface IntegrationApp { siteId: string; } -const Integration = ({ - integration -}: { +type Props = { integration: Integration; -}) => { - const [integrationEnvironment, setIntegrationEnvironment] = useState(integration.environment); + environments: Array<{ name: string; slug: string }>; +}; + +const Integration = ({ + integration, + environments = [] +}:Props ) => { + // set initial environment. This find will only execute when component is mounting + const [integrationEnvironment, setIntegrationEnvironment] = useState< + Props['environments'][0] + >( + environments.find(({ slug }) => slug === integration.environment) || { + name: '', + slug: '', + } + ); const [fileState, setFileState] = useState([]); const router = useRouter(); const [apps, setApps] = useState([]); // integration app objects @@ -132,42 +144,47 @@ const Integration = ({ if (!integrationApp || apps.length === 0) return
return ( -
-
+
+
-

ENVIRONMENT

- { - setIntegrationEnvironment(environment); - }} +

+ ENVIRONMENT +

+ name) + : null + } + selected={integrationEnvironment.name} + onChange={(envName) => + setIntegrationEnvironment( + environments.find(({ name }) => envName === name) || { + name: 'unknown', + slug: 'unknown', + } + ) + } isFull={true} />
-
+
+ className='mx-4 text-gray-400 mt-8' + />
-
-

+

+

INTEGRATION

-
+
{integration.integration.charAt(0).toUpperCase() + integration.integration.slice(1)}
-
-
- APP -
+
+
APP
app.name) : null} selected={integrationApp} @@ -178,52 +195,55 @@ const Integration = ({
{renderIntegrationSpecificParams(integration)}
-
- {integration.isActive ? ( -
- -
In Sync
-
- ) : ( -
); diff --git a/frontend/components/integrations/IntegrationSection.tsx b/frontend/components/integrations/IntegrationSection.tsx index 52d5565ff..633a747af 100644 --- a/frontend/components/integrations/IntegrationSection.tsx +++ b/frontend/components/integrations/IntegrationSection.tsx @@ -5,7 +5,8 @@ import guidGenerator from "~/utilities/randomId"; import Integration from "./Integration"; interface Props { - integrations: any + integrations: any; + environments: Array<{ name: string; slug: string }>; } interface IntegrationType { @@ -19,7 +20,8 @@ interface IntegrationType { } const ProjectIntegrationSection = ({ - integrations + integrations, + environments = [], }: Props) => { return integrations.length > 0 ? (
@@ -33,6 +35,7 @@ const ProjectIntegrationSection = ({ ))}
diff --git a/frontend/pages/api/workspace/getAWorkspace.ts b/frontend/pages/api/workspace/getAWorkspace.ts new file mode 100644 index 000000000..71c5036eb --- /dev/null +++ b/frontend/pages/api/workspace/getAWorkspace.ts @@ -0,0 +1,31 @@ +import SecurityClient from '~/utilities/SecurityClient'; + +interface Workspace { + __v: number; + _id: string; + name: string; + organization: string; + environments: Array<{ name: string; slug: string }>; +} + +/** + * This route lets us get the workspaces of a certain user + * @returns + */ +const getAWorkspace = (workspaceID:string) => { + return SecurityClient.fetchCall(`/api/v1/workspace/${workspaceID}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }).then(async (res) => { + if (res?.status == 200) { + const data = (await res.json()) as unknown as { workspace: Workspace }; + return data.workspace; + } + + throw new Error('Failed to get workspace'); + }); +}; + +export default getAWorkspace; diff --git a/frontend/pages/integrations/[id].js b/frontend/pages/integrations/[id].js index 2a5460dfb..8a11bc3c4 100644 --- a/frontend/pages/integrations/[id].js +++ b/frontend/pages/integrations/[id].js @@ -24,6 +24,7 @@ import setBotActiveStatus from "../api/bot/setBotActiveStatus"; import getIntegrationOptions from "../api/integrations/GetIntegrationOptions"; import getWorkspaceAuthorizations from "../api/integrations/getWorkspaceAuthorizations"; import getWorkspaceIntegrations from "../api/integrations/getWorkspaceIntegrations"; +import getAWorkspace from "../api/workspace/getAWorkspace"; import getLatestFileKey from "../api/workspace/getLatestFileKey"; const { decryptAssymmetric, @@ -34,6 +35,7 @@ const crypto = require("crypto"); export default function Integrations() { const [cloudIntegrationOptions, setCloudIntegrationOptions] = useState([]); const [integrationAuths, setIntegrationAuths] = useState([]); + const [environments,setEnvironments] = useState([]) const [integrations, setIntegrations] = useState([]); const [bot, setBot] = useState(null); const [isActivateBotDialogOpen, setIsActivateBotDialogOpen] = useState(false); @@ -41,11 +43,15 @@ export default function Integrations() { const [selectedIntegrationOption, setSelectedIntegrationOption] = useState(null); const router = useRouter(); + const workspaceId = router.query.id; const { t } = useTranslation(); useEffect(async () => { try { + const workspace = await getAWorkspace(workspaceId); + setEnvironments(workspace.environments); + // get cloud integration options setCloudIntegrationOptions( await getIntegrationOptions() @@ -54,23 +60,19 @@ export default function Integrations() { // get project integration authorizations setIntegrationAuths( await getWorkspaceAuthorizations({ - workspaceId: router.query.id, + workspaceId }) ); // get project integrations setIntegrations( await getWorkspaceIntegrations({ - workspaceId: router.query.id, + workspaceId, }) ); // get project bot - setBot( - await getBot({ - workspaceId: router.query.id - } - )); + setBot(await getBot({ workspaceId })); } catch (err) { console.log(err); @@ -90,7 +92,7 @@ export default function Integrations() { if (bot) { // case: there is a bot - const key = await getLatestFileKey({ workspaceId: router.query.id }); + const key = await getLatestFileKey({ workspaceId }); const PRIVATE_KEY = localStorage.getItem('PRIVATE_KEY'); const WORKSPACE_KEY = decryptAssymmetric({ @@ -214,7 +216,7 @@ export default function Integrations() { handleBotActivate={handleBotActivate} handleIntegrationOption={handleIntegrationOption} /> */} - + {(cloudIntegrationOptions.length > 0 && bot) ? ( Date: Fri, 13 Jan 2023 09:18:41 -0800 Subject: [PATCH 37/65] Remove netlify and gh --- backend/src/variables/integration.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/variables/integration.ts b/backend/src/variables/integration.ts index a1aa5d94d..b298a784a 100644 --- a/backend/src/variables/integration.ts +++ b/backend/src/variables/integration.ts @@ -57,7 +57,7 @@ const INTEGRATION_OPTIONS = [ name: 'Netlify', slug: 'netlify', image: 'Netlify', - isAvailable: true, + isAvailable: false, type: 'oauth2', clientId: CLIENT_ID_NETLIFY, docsLink: '' @@ -66,7 +66,7 @@ const INTEGRATION_OPTIONS = [ name: 'GitHub', slug: 'github', image: 'GitHub', - isAvailable: true, + isAvailable: false, type: 'oauth2', clientId: CLIENT_ID_GITHUB, docsLink: '' From a8f38a5367dbbca680a07ddb511e7d6c991fc99f Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Fri, 13 Jan 2023 11:40:29 -0800 Subject: [PATCH 38/65] Add github action for gamma deploy --- .github/workflows/deploy-gamma.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/workflows/deploy-gamma.yml diff --git a/.github/workflows/deploy-gamma.yml b/.github/workflows/deploy-gamma.yml new file mode 100644 index 000000000..290f1f892 --- /dev/null +++ b/.github/workflows/deploy-gamma.yml @@ -0,0 +1,26 @@ +name: Deploy to Gamma stage + +on: [workflow_dispatch] + +jobs: + deployment: + name: Deploy to gamma + runs-on: ubuntu-latest + steps: + - name: ☁️ Checkout source + uses: actions/checkout@v3 + - name: Install Helm + uses: azure/setup-helm@v3 + with: + version: v3.10.0 + - name: Install kubectl + uses: azure/setup-kubectl@v3 + - name: Install doctl + uses: digitalocean/action-doctl@v2 + with: + token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }} + - name: Save DigitalOcean kubeconfig with short-lived credentials + run: doctl kubernetes cluster kubeconfig save --expiry-seconds 600 k8s-1-25-4-do-0-nyc1-1670645170179 + - name: switch to gamma namespace + run: kubectl config set-context --current --namespace=gamma + \ No newline at end of file From 0242707e33e2c1e7846d5c86350940f5e0b73ebe Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Fri, 13 Jan 2023 11:49:38 -0800 Subject: [PATCH 39/65] gh action test kubectl --- .github/workflows/deploy-gamma.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/deploy-gamma.yml b/.github/workflows/deploy-gamma.yml index 290f1f892..f9167c296 100644 --- a/.github/workflows/deploy-gamma.yml +++ b/.github/workflows/deploy-gamma.yml @@ -23,4 +23,6 @@ jobs: run: doctl kubernetes cluster kubeconfig save --expiry-seconds 600 k8s-1-25-4-do-0-nyc1-1670645170179 - name: switch to gamma namespace run: kubectl config set-context --current --namespace=gamma + - name: test kubectl + run: kubectl get ingress \ No newline at end of file From 799a839940156d1ce944e55ce32d615a16ffb3a0 Mon Sep 17 00:00:00 2001 From: Michel Wilhelm Date: Fri, 13 Jan 2023 16:52:15 -0300 Subject: [PATCH 40/65] feat: adding new export format --- cli/packages/cmd/export.go | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/cli/packages/cmd/export.go b/cli/packages/cmd/export.go index fd04e9ce2..33021f1c1 100644 --- a/cli/packages/cmd/export.go +++ b/cli/packages/cmd/export.go @@ -16,10 +16,11 @@ import ( ) const ( - FormatDotenv string = "dotenv" - FormatJson string = "json" - FormatCSV string = "csv" - FormatYaml string = "yaml" + FormatDotenv string = "dotenv" + FormatJson string = "json" + FormatCSV string = "csv" + FormatYaml string = "yaml" + FormatDotEnvExport string = "dotenv-export" ) // exportCmd represents the export command @@ -85,6 +86,8 @@ func formatEnvs(envs []models.SingleEnvironmentVariable, format string) (string, switch strings.ToLower(format) { case FormatDotenv: return formatAsDotEnv(envs), nil + case FormatDotEnvExport: + return formatAsDotEnvExport(envs), nil case FormatJson: return formatAsJson(envs), nil case FormatCSV: @@ -117,6 +120,15 @@ func formatAsDotEnv(envs []models.SingleEnvironmentVariable) string { return dotenv } +// Format environment variables as a dotenv file with export at the beginning +func formatAsDotEnvExport(envs []models.SingleEnvironmentVariable) string { + var dotenv string + for _, env := range envs { + dotenv += fmt.Sprintf("export %s='%s'\n", env.Key, env.Value) + } + return dotenv +} + func formatAsYaml(envs []models.SingleEnvironmentVariable) string { var dotenv string for _, env := range envs { From 58aee0239fa181719175d178720faee9f8755f9b Mon Sep 17 00:00:00 2001 From: Michel Wilhelm Date: Fri, 13 Jan 2023 16:59:22 -0300 Subject: [PATCH 41/65] docs: adding documentation about dotenv-export --- docs/cli/commands/export.mdx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/docs/cli/commands/export.mdx b/docs/cli/commands/export.mdx index b80fb7470..bcbc89e89 100644 --- a/docs/cli/commands/export.mdx +++ b/docs/cli/commands/export.mdx @@ -12,12 +12,12 @@ Export environment variables from the platform into a file format. ## Options -| Option | Description | Default value | -| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | -| `--env` | Used to set the environment that secrets are pulled from. Accepted values: `dev`, `staging`, `test`, `prod` | `dev` | -| `--projectId` | Only required if injecting via the [service token method](../token). If you are not using service token, the project id will be automatically retrieved from the `.infisical.json` located at the root of your local project. | `None` | -| `--expand` | Parse shell parameter expansions in your secrets (e.g., `${DOMAIN}`) | `true` | -| `--format` | Format of the output file. Accepted values: `dotenv`, `csv` and `json` | `dotenv` | +| Option | Description | Default value | +| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | +| `--env` | Used to set the environment that secrets are pulled from. Accepted values: `dev`, `staging`, `test`, `prod` | `dev` | +| `--projectId` | Only required if injecting via the [service token method](../token). If you are not using service token, the project id will be automatically retrieved from the `.infisical.json` located at the root of your local project. | `None` | +| `--expand` | Parse shell parameter expansions in your secrets (e.g., `${DOMAIN}`) | `true` | +| `--format` | Format of the output file. Accepted values: `dotenv`, `dotenv-export`, `csv` and `json` | `dotenv` | ## Examples @@ -25,6 +25,9 @@ Export environment variables from the platform into a file format. # Export variables to a .env file infisical export > .env +# Export variables to a .env file (with export keyword) +infisical export --format=dotenv-export > .env + # Export variables to a CSV file infisical export --format=csv > secrets.csv @@ -33,4 +36,5 @@ infisical export --format=json > secrets.json # Export variables to a YAML file infisical export --format=yaml > secrets.yaml + ``` From dc6d036d86c865fe7b82cb1ec4f1e78663bd3a97 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Fri, 13 Jan 2023 12:08:47 -0800 Subject: [PATCH 42/65] write helm values to file form secret --- .github/workflows/deploy-gamma.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/deploy-gamma.yml b/.github/workflows/deploy-gamma.yml index f9167c296..2b6258109 100644 --- a/.github/workflows/deploy-gamma.yml +++ b/.github/workflows/deploy-gamma.yml @@ -13,6 +13,10 @@ jobs: uses: azure/setup-helm@v3 with: version: v3.10.0 + - name: Install infisical helm chart + run: | + helm repo add infisical-helm-charts 'https://dl.cloudsmith.io/public/infisical/helm-charts/helm/charts/' + helm repo update - name: Install kubectl uses: azure/setup-kubectl@v3 - name: Install doctl @@ -25,4 +29,10 @@ jobs: run: kubectl config set-context --current --namespace=gamma - name: test kubectl run: kubectl get ingress + - name: Write helm values to file + run: | + echo "${{ secrets.HELM_VALUES_GAMMA }}" > values.yaml + cat values.yml + + \ No newline at end of file From d1f296b7e791a5558c10e78a5d80e20a3714bed6 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Fri, 13 Jan 2023 12:11:05 -0800 Subject: [PATCH 43/65] fix indent gamma deploy gha --- .github/workflows/deploy-gamma.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deploy-gamma.yml b/.github/workflows/deploy-gamma.yml index 2b6258109..f914ca8aa 100644 --- a/.github/workflows/deploy-gamma.yml +++ b/.github/workflows/deploy-gamma.yml @@ -15,8 +15,8 @@ jobs: version: v3.10.0 - name: Install infisical helm chart run: | - helm repo add infisical-helm-charts 'https://dl.cloudsmith.io/public/infisical/helm-charts/helm/charts/' - helm repo update + helm repo add infisical-helm-charts 'https://dl.cloudsmith.io/public/infisical/helm-charts/helm/charts/' + helm repo update - name: Install kubectl uses: azure/setup-kubectl@v3 - name: Install doctl @@ -31,8 +31,8 @@ jobs: run: kubectl get ingress - name: Write helm values to file run: | - echo "${{ secrets.HELM_VALUES_GAMMA }}" > values.yaml - cat values.yml + echo "${{ secrets.HELM_VALUES_GAMMA }}" > values.yaml + cat values.yml \ No newline at end of file From eb9a8e0285d897c3250eae96a98e0b3e731a3e4f Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Fri, 13 Jan 2023 12:16:36 -0800 Subject: [PATCH 44/65] echo values file --- .github/workflows/deploy-gamma.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/deploy-gamma.yml b/.github/workflows/deploy-gamma.yml index f914ca8aa..6fb01abc5 100644 --- a/.github/workflows/deploy-gamma.yml +++ b/.github/workflows/deploy-gamma.yml @@ -31,8 +31,7 @@ jobs: run: kubectl get ingress - name: Write helm values to file run: | - echo "${{ secrets.HELM_VALUES_GAMMA }}" > values.yaml - cat values.yml + echo "${{ secrets.HELM_VALUES_GAMMA }}" \ No newline at end of file From fa60784a6b88ba1b27d4ec1862c6a910358e8ab6 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Fri, 13 Jan 2023 12:27:14 -0800 Subject: [PATCH 45/65] Add files via upload --- .github/values.yaml | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 .github/values.yaml diff --git a/.github/values.yaml b/.github/values.yaml new file mode 100644 index 000000000..f1551d61a --- /dev/null +++ b/.github/values.yaml @@ -0,0 +1,36 @@ +frontend: + replicaCount: 1 + image: + repository: + pullPolicy: Always + tag: "latest" + kubeSecretRef: managed-secret-frontend + +backend: + replicaCount: 1 + image: + repository: + pullPolicy: Always + tag: "latest" + kubeSecretRef: managed-backend-secret + +ingress: + enabled: true + annotations: + kubernetes.io/ingress.class: "nginx" + cert-manager.io/cluster-issuer: "letsencrypt-prod" + hostName: gamma.infisical.com + frontend: + path: / + pathType: Prefix + backend: + path: /api + pathType: Prefix + tls: + - secretName: echo-tls + hosts: + - gamma.infisical.com + +backendEnvironmentVariables: + +frontendEnvironmentVariables: \ No newline at end of file From 593765cb248656e3b3b6929c4932cfd29c8e2585 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Fri, 13 Jan 2023 12:31:55 -0800 Subject: [PATCH 46/65] cat values file after downloading --- .github/workflows/deploy-gamma.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy-gamma.yml b/.github/workflows/deploy-gamma.yml index 6fb01abc5..d46142707 100644 --- a/.github/workflows/deploy-gamma.yml +++ b/.github/workflows/deploy-gamma.yml @@ -31,7 +31,8 @@ jobs: run: kubectl get ingress - name: Write helm values to file run: | - echo "${{ secrets.HELM_VALUES_GAMMA }}" + wget https://raw.githubusercontent.com/Infisical/infisical/main/.github/values.yaml + cat values.yaml \ No newline at end of file From 323701d432fcaf5a21f04fcbffe2c705077138c6 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Fri, 13 Jan 2023 12:38:56 -0800 Subject: [PATCH 47/65] add gha upgrade helmchart --- .github/workflows/deploy-gamma.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy-gamma.yml b/.github/workflows/deploy-gamma.yml index d46142707..b72f87a61 100644 --- a/.github/workflows/deploy-gamma.yml +++ b/.github/workflows/deploy-gamma.yml @@ -29,10 +29,11 @@ jobs: run: kubectl config set-context --current --namespace=gamma - name: test kubectl run: kubectl get ingress - - name: Write helm values to file + - name: Download helm values to file and upgrade gamma deploy run: | wget https://raw.githubusercontent.com/Infisical/infisical/main/.github/values.yaml - cat values.yaml + helm upgrade infisical infisical-helm-charts/infisical --values values.yaml --recreate-pods + \ No newline at end of file From 17bae52830f587752c59d28e57c1ceaeba5f7ff0 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Fri, 13 Jan 2023 12:52:07 -0800 Subject: [PATCH 48/65] gamma deployment after image build --- .github/workflows/docker-image.yml | 34 +++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index ab7b939e7..34847749b 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -99,4 +99,36 @@ jobs: infisical/frontend:latest platforms: linux/amd64,linux/arm64 build-args: | - POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }} \ No newline at end of file + POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }} + gamma-deployment: + name: Deploy to gamma + runs-on: ubuntu-latest + needs: frontend-image backend-image + if: github.event_name == 'repository_dispatch' + steps: + - name: ☁️ Checkout source + uses: actions/checkout@v3 + - name: Install Helm + uses: azure/setup-helm@v3 + with: + version: v3.10.0 + - name: Install infisical helm chart + run: | + helm repo add infisical-helm-charts 'https://dl.cloudsmith.io/public/infisical/helm-charts/helm/charts/' + helm repo update + - name: Install kubectl + uses: azure/setup-kubectl@v3 + - name: Install doctl + uses: digitalocean/action-doctl@v2 + with: + token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }} + - name: Save DigitalOcean kubeconfig with short-lived credentials + run: doctl kubernetes cluster kubeconfig save --expiry-seconds 600 k8s-1-25-4-do-0-nyc1-1670645170179 + - name: switch to gamma namespace + run: kubectl config set-context --current --namespace=gamma + - name: test kubectl + run: kubectl get ingress + - name: Download helm values to file and upgrade gamma deploy + run: | + wget https://raw.githubusercontent.com/Infisical/infisical/main/.github/values.yaml + helm upgrade infisical infisical-helm-charts/infisical --values values.yaml --recreate-pods \ No newline at end of file From 189d24589e90ef5271b269b4765dd39f50b5d748 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Fri, 13 Jan 2023 12:53:53 -0800 Subject: [PATCH 49/65] correct needs fields in gha --- .github/workflows/docker-image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 34847749b..30e938a54 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -103,7 +103,7 @@ jobs: gamma-deployment: name: Deploy to gamma runs-on: ubuntu-latest - needs: frontend-image backend-image + needs: [frontend-image, backend-image] if: github.event_name == 'repository_dispatch' steps: - name: ☁️ Checkout source From a00e6df59ff3db951a2f69835d158bd26ce679dc Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Fri, 13 Jan 2023 13:09:57 -0800 Subject: [PATCH 50/65] add manual approval setp --- .github/workflows/docker-image.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 30e938a54..9d8a6b0a8 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -100,10 +100,19 @@ jobs: platforms: linux/amd64,linux/arm64 build-args: | POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }} + manualApprove: + needs: [frontend-image, backend-image] + environment: + name: gamma-deployment + runs-on: ubuntu-latest + steps: + - name: manual approve + run: | + echo "Manually approved" gamma-deployment: name: Deploy to gamma runs-on: ubuntu-latest - needs: [frontend-image, backend-image] + needs: [manualApprove] if: github.event_name == 'repository_dispatch' steps: - name: ☁️ Checkout source From 092e4a55bdb0cdf8f94fd757cd4d0c4c0eff84ee Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Fri, 13 Jan 2023 13:13:15 -0800 Subject: [PATCH 51/65] enable auto deploy to gamma --- .github/workflows/docker-image.yml | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 9d8a6b0a8..142245dc8 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -100,20 +100,10 @@ jobs: platforms: linux/amd64,linux/arm64 build-args: | POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }} - manualApprove: - needs: [frontend-image, backend-image] - environment: - name: gamma-deployment - runs-on: ubuntu-latest - steps: - - name: manual approve - run: | - echo "Manually approved" gamma-deployment: name: Deploy to gamma runs-on: ubuntu-latest - needs: [manualApprove] - if: github.event_name == 'repository_dispatch' + needs: [frontend-image, backend-image] steps: - name: ☁️ Checkout source uses: actions/checkout@v3 From 64d1f252e29f1f415e90f5f73f0b62a811e32e33 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Fri, 13 Jan 2023 13:39:30 -0800 Subject: [PATCH 52/65] Rename workflow file --- .github/workflows/docker-image.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 142245dc8..ffc6e5c40 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -1,5 +1,4 @@ -name: Push frontend and backend to Dockerhub - +name: Build, Publish and Deploy to Gamma on: [workflow_dispatch] jobs: From 87a3f9a03c03bdeb6a8ed94500c75f97f97789ea Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Fri, 13 Jan 2023 13:41:56 -0800 Subject: [PATCH 53/65] delete gamma deploy workflow file --- .github/workflows/deploy-gamma.yml | 39 ------------------------------ 1 file changed, 39 deletions(-) delete mode 100644 .github/workflows/deploy-gamma.yml diff --git a/.github/workflows/deploy-gamma.yml b/.github/workflows/deploy-gamma.yml deleted file mode 100644 index b72f87a61..000000000 --- a/.github/workflows/deploy-gamma.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: Deploy to Gamma stage - -on: [workflow_dispatch] - -jobs: - deployment: - name: Deploy to gamma - runs-on: ubuntu-latest - steps: - - name: ☁️ Checkout source - uses: actions/checkout@v3 - - name: Install Helm - uses: azure/setup-helm@v3 - with: - version: v3.10.0 - - name: Install infisical helm chart - run: | - helm repo add infisical-helm-charts 'https://dl.cloudsmith.io/public/infisical/helm-charts/helm/charts/' - helm repo update - - name: Install kubectl - uses: azure/setup-kubectl@v3 - - name: Install doctl - uses: digitalocean/action-doctl@v2 - with: - token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }} - - name: Save DigitalOcean kubeconfig with short-lived credentials - run: doctl kubernetes cluster kubeconfig save --expiry-seconds 600 k8s-1-25-4-do-0-nyc1-1670645170179 - - name: switch to gamma namespace - run: kubectl config set-context --current --namespace=gamma - - name: test kubectl - run: kubectl get ingress - - name: Download helm values to file and upgrade gamma deploy - run: | - wget https://raw.githubusercontent.com/Infisical/infisical/main/.github/values.yaml - helm upgrade infisical infisical-helm-charts/infisical --values values.yaml --recreate-pods - - - - \ No newline at end of file From 986434d66ae7ddbef7f60d2fd9cb15f15b877792 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Fri, 13 Jan 2023 14:01:23 -0800 Subject: [PATCH 54/65] Add infisical in Makefile for docker compose --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index 266eaf9b5..9e1f5c3f6 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,9 @@ push: up-dev: docker-compose -f docker-compose.dev.yml up --build +i-dev: + infisical export && infisical export > .env && docker-compose -f docker-compose.dev.yml up --build + up-prod: docker-compose -f docker-compose.yml up --build From d386f2702d276db7d47226716daaa16e809e0bff Mon Sep 17 00:00:00 2001 From: Vladyslav Matsiiako Date: Fri, 13 Jan 2023 16:05:50 -0800 Subject: [PATCH 55/65] Minor style changes to integrations --- frontend/components/integrations/CloudIntegration.tsx | 2 +- frontend/components/integrations/Integration.tsx | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/frontend/components/integrations/CloudIntegration.tsx b/frontend/components/integrations/CloudIntegration.tsx index 75a8019a5..dd1b13f47 100644 --- a/frontend/components/integrations/CloudIntegration.tsx +++ b/frontend/components/integrations/CloudIntegration.tsx @@ -74,7 +74,7 @@ const CloudIntegration = ({ integrationAuths .map((authorization) => authorization.integration) .includes(cloudIntegrationOption.name.toLowerCase()) && ( -
+
{ event.stopPropagation(); diff --git a/frontend/components/integrations/Integration.tsx b/frontend/components/integrations/Integration.tsx index 4dba86841..bbc8861dc 100644 --- a/frontend/components/integrations/Integration.tsx +++ b/frontend/components/integrations/Integration.tsx @@ -101,7 +101,7 @@ const Integration = ({ case "vercel": return (
-
+
ENVIRONMENT
); From 71fbf519ce86af02b5321c0d646e88ff96a12639 Mon Sep 17 00:00:00 2001 From: Vladyslav Matsiiako Date: Fri, 13 Jan 2023 16:17:40 -0800 Subject: [PATCH 56/65] Minor style changes - capitalization --- frontend/components/basic/Listbox.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/components/basic/Listbox.tsx b/frontend/components/basic/Listbox.tsx index c67735d6e..a65135dfa 100644 --- a/frontend/components/basic/Listbox.tsx +++ b/frontend/components/basic/Listbox.tsx @@ -46,9 +46,9 @@ export default function ListBox({ >
{text} - + {' '} - {selected?.charAt(0).toUpperCase() + selected?.slice(1)} + {selected}
{data && ( From 3e6206951eb17582b593fe9ef4d32981bdb27965 Mon Sep 17 00:00:00 2001 From: Vladyslav Matsiiako Date: Fri, 13 Jan 2023 17:19:22 -0800 Subject: [PATCH 57/65] updated Readme with new contributors --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2876c6ee8..0e680ff29 100644 --- a/README.md +++ b/README.md @@ -333,7 +333,8 @@ Infisical officially launched as v.1.0 on November 21st, 2022. There are a lot o -
+ + ## 🌎 Translations From 5d8c4ad03f3a3a699c4c88e836ab328f09e44adb Mon Sep 17 00:00:00 2001 From: Vladyslav Matsiiako Date: Fri, 13 Jan 2023 17:32:36 -0800 Subject: [PATCH 58/65] Changed the formulation to secrets and configs --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 0e680ff29..67b6d519c 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ infisical

-

Open-source, E2EE, simple tool to manage and sync environment variables across your team and infrastructure.

+

Open-source, E2EE, simple tool to manage secrets and configs across your team and infrastructure.

@@ -34,13 +34,13 @@ Dashboard -**[Infisical](https://infisical.com)** is an open source, E2EE tool to help teams manage and sync environment variables across their development workflow and infrastructure. It's designed to be simple and take minutes to get going. +**[Infisical](https://infisical.com)** is an open source, E2EE tool to help teams manage and sync secrets and configs across their development workflow and infrastructure. It's designed to be simple and take minutes to get going. -- **[User-Friendly Dashboard](https://infisical.com/docs/getting-started/dashboard/project)** to manage your team's environment variables within projects -- **[Language-Agnostic CLI](https://infisical.com/docs/cli/overview)** that pulls and injects environment variables into your local workflow +- **[User-Friendly Dashboard](https://infisical.com/docs/getting-started/dashboard/project)** to manage your team's secrets and configs within projects +- **[Language-Agnostic CLI](https://infisical.com/docs/cli/overview)** that pulls and injects esecrets and configs into your local workflow - **[Complete control over your data](https://infisical.com/docs/self-hosting/overview)** - host it yourself on any infrastructure - **Navigate Multiple Environments** per project (e.g. development, staging, production, etc.) -- **Personal overrides** for environment variables +- **Personal overrides** for secrets and configs - **[Integrations](https://infisical.com/docs/integrations/overview)** with CI/CD and production infrastructure - **[Secret Versioning](https://infisical.com/docs/getting-started/dashboard/versioning)** - check the history of change for any secret - **[Activity Logs](https://infisical.com/docs/getting-started/dashboard/audit-logs)** - check what user in the project is performing what actions with secrets From b63360813a8d30846441256472dafd4b4c35145d Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Sat, 14 Jan 2023 09:48:13 +0700 Subject: [PATCH 59/65] Continue API reference development --- backend/spec.json | 36 +++++++++++-- backend/src/controllers/v2/usersController.ts | 2 +- .../ee/controllers/v1/workspaceController.ts | 53 +++++++++++++++++-- backend/swagger/index.ts | 2 + 4 files changed, 84 insertions(+), 9 deletions(-) diff --git a/backend/spec.json b/backend/spec.json index 188365290..baad21e7e 100644 --- a/backend/spec.json +++ b/backend/spec.json @@ -175,7 +175,8 @@ }, "/api/v1/workspace/{workspaceId}/secret-snapshots/rollback": { "post": { - "description": "", + "summary": "Roll back project secrets to those captured in a secret snapshot version", + "description": "Roll back project secrets to those captured in a secret snapshot version", "parameters": [ { "name": "workspaceId", @@ -183,25 +184,44 @@ "required": true, "schema": { "type": "string" - } + }, + "description": "ID of project" } ], "responses": { "200": { - "description": "OK" + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Secret" + }, + "description": "Array of secrets captured in the secret snapshot" + } + } + } }, "400": { "description": "Bad Request" } }, + "security": [ + { + "apiKeyAuth": [] + } + ], "requestBody": { + "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "version": { - "example": "any" + "type": "integer", + "description": "Version of secret snapshot to roll back to" } } } @@ -2904,6 +2924,14 @@ "type": "string", "example": "" }, + "iv": { + "type": "string", + "example": "" + }, + "tag": { + "type": "string", + "example": "" + }, "updatedAt": { "type": "string", "example": "" diff --git a/backend/src/controllers/v2/usersController.ts b/backend/src/controllers/v2/usersController.ts index faac8f646..194e508b3 100644 --- a/backend/src/controllers/v2/usersController.ts +++ b/backend/src/controllers/v2/usersController.ts @@ -29,7 +29,7 @@ export const getMe = async (req: Request, res: Response) => { try { user = await User .findById(req.user._id) - .select('+publicKey +encryptedPrivateKey'); + .select('+publicKey +encryptedPrivateKey +iv +tag'); } catch (err) { Sentry.setUser({ email: req.user.email }); Sentry.captureException(err); diff --git a/backend/src/ee/controllers/v1/workspaceController.ts b/backend/src/ee/controllers/v1/workspaceController.ts index 8fd7c8746..f29c42861 100644 --- a/backend/src/ee/controllers/v1/workspaceController.ts +++ b/backend/src/ee/controllers/v1/workspaceController.ts @@ -78,16 +78,61 @@ export const getWorkspaceSecretSnapshotsCount = async (req: Request, res: Respon * @returns */ export const rollbackWorkspaceSecretSnapshot = async (req: Request, res: Response) => { + /* + #swagger.summary = 'Roll back project secrets to those captured in a secret snapshot version' + #swagger.description = 'Roll back project secrets to those captured in a secret snapshot version' + + #swagger.security = [{ + "apiKeyAuth": [] + }] + + #swagger.parameters['workspaceId'] = { + "description": "ID of project", + "required": true, + "type": "string" + } + + #swagger.requestBody = { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "version": { + "type": "integer", + "description": "Version of secret snapshot to roll back to", + } + } + } + } + } + } + + #swagger.responses[200] = { + content: { + "application/json": { + schema: { + "type": "array", + "items": { + $ref: "#/components/schemas/Secret" + }, + "description": "Array of secrets captured in the secret snapshot" + } + } + } + } + */ let secrets; try { const { workspaceId } = req.params; const { version } = req.body; // validate secret snapshot - const secretSnapshot = await SecretSnapshot.findOne({ - workspace: workspaceId, - version - }).populate<{ secretVersions: ISecretVersion[]}>('secretVersions'); + const secretSnapshot = await SecretSnapshot.findOne({ + workspace: workspaceId, + version + }).populate<{ secretVersions: ISecretVersion[]}>('secretVersions'); if (!secretSnapshot) throw new Error('Failed to find secret snapshot'); diff --git a/backend/swagger/index.ts b/backend/swagger/index.ts index 2b8bc4f62..91f27da44 100644 --- a/backend/swagger/index.ts +++ b/backend/swagger/index.ts @@ -48,6 +48,8 @@ const generateOpenAPISpec = async () => { lastName: '', publicKey: '', encryptedPrivateKey: '', + iv: '', + tag: '', updatedAt: '', createdAt: '' }, From 98d84b6717e64413250ecc6d2abcc01bb1690cc0 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Fri, 13 Jan 2023 19:48:40 -0800 Subject: [PATCH 60/65] add FormatDotEnvExport to list of available formats --- cli/packages/cmd/export.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/packages/cmd/export.go b/cli/packages/cmd/export.go index 33021f1c1..689f507d3 100644 --- a/cli/packages/cmd/export.go +++ b/cli/packages/cmd/export.go @@ -95,7 +95,7 @@ func formatEnvs(envs []models.SingleEnvironmentVariable, format string) (string, case FormatYaml: return formatAsYaml(envs), nil default: - return "", fmt.Errorf("invalid format type: %s. Available format types are [%s]", format, []string{FormatDotenv, FormatJson, FormatCSV, FormatYaml}) + return "", fmt.Errorf("invalid format type: %s. Available format types are [%s]", format, []string{FormatDotenv, FormatJson, FormatCSV, FormatYaml, FormatDotEnvExport}) } } From 3cd9241aee5615f690b7aea09a2a05835a31fcd2 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Fri, 13 Jan 2023 19:52:29 -0800 Subject: [PATCH 61/65] increase cli version --- cli/packages/cmd/root.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/packages/cmd/root.go b/cli/packages/cmd/root.go index e68c6826c..708982a22 100644 --- a/cli/packages/cmd/root.go +++ b/cli/packages/cmd/root.go @@ -15,7 +15,7 @@ var rootCmd = &cobra.Command{ Short: "Infisical CLI is used to inject environment variables into any process", Long: `Infisical is a simple, end-to-end encrypted service that enables teams to sync and manage their environment variables across their development life cycle.`, CompletionOptions: cobra.CompletionOptions{HiddenDefaultCmd: true}, - Version: "0.2.1", + Version: "0.2.2", } // Execute adds all child commands to the root command and sets flags appropriately. From e552be0a8184e7e33ecfc9cf6608b732ae51f5b9 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Fri, 13 Jan 2023 20:42:23 -0800 Subject: [PATCH 62/65] add deployment error check for gamma --- .github/workflows/docker-image.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index ffc6e5c40..b4f9546ae 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -129,4 +129,10 @@ jobs: - name: Download helm values to file and upgrade gamma deploy run: | wget https://raw.githubusercontent.com/Infisical/infisical/main/.github/values.yaml - helm upgrade infisical infisical-helm-charts/infisical --values values.yaml --recreate-pods \ No newline at end of file + helm upgrade infisical infisical-helm-charts/infisical --values values.yaml --recreate-pods + if [[ $(helm status infisical) == *"FAILED"* ]]; then + echo "Helm upgrade failed" + exit 1 + else + echo "Helm upgrade was successful" + fi \ No newline at end of file From 08dd5174b30c6bfd22f703a962c9abcfd3e87fa8 Mon Sep 17 00:00:00 2001 From: Vladyslav Matsiiako Date: Fri, 13 Jan 2023 22:59:43 -0800 Subject: [PATCH 63/65] Making the dashboard less clunky --- README.md | 2 +- frontend/components/RouteGuard.js | 15 +-------------- frontend/pages/dashboard/[id].tsx | 2 +- 3 files changed, 3 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 67b6d519c..756f65f92 100644 --- a/README.md +++ b/README.md @@ -333,7 +333,7 @@ Infisical officially launched as v.1.0 on November 21st, 2022. There are a lot o - + ## 🌎 Translations diff --git a/frontend/components/RouteGuard.js b/frontend/components/RouteGuard.js index c5f6feb35..f3693abec 100644 --- a/frontend/components/RouteGuard.js +++ b/frontend/components/RouteGuard.js @@ -68,18 +68,5 @@ export default function RouteGuard({ children }) { } } - if (authorized) { - return children; - } else { - return ( -
- google logo -
- ); - } + return children; } diff --git a/frontend/pages/dashboard/[id].tsx b/frontend/pages/dashboard/[id].tsx index 9438cf115..1a9cd41da 100644 --- a/frontend/pages/dashboard/[id].tsx +++ b/frontend/pages/dashboard/[id].tsx @@ -220,7 +220,7 @@ export default function Dashboard() { setData(undefined); } })(); - }, []); + }, [workspaceId]); useEffect(() => { (async () => { From 315810bd741cb8ab60a766cb4dea9bfbc76488a5 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Sat, 14 Jan 2023 19:02:12 +0700 Subject: [PATCH 64/65] Complete v1 API reference docs, pre-launch --- backend/spec.json | 693 ++++++++++++++++-- .../src/controllers/v2/secretsController.ts | 98 +-- backend/src/controllers/v2/usersController.ts | 9 +- .../src/controllers/v2/workspaceController.ts | 220 ++++++ .../src/ee/controllers/v1/secretController.ts | 93 +++ .../ee/controllers/v1/workspaceController.ts | 130 +++- backend/src/middleware/index.ts | 2 + .../src/middleware/requireMembershipAuth.ts | 57 ++ backend/src/routes/v1/membership.ts | 2 +- backend/src/routes/v2/workspace.ts | 51 ++ backend/src/types/express/index.d.ts | 1 + backend/swagger/index.ts | 68 ++ .../endpoints/secrets/rollback-version.mdx | 4 + .../endpoints/secrets/versions.mdx | 4 + .../workspaces/delete-membership.mdx | 4 + .../endpoints/workspaces/logs.mdx | 4 + .../endpoints/workspaces/memberships.mdx | 4 + .../workspaces/rollback-snapshot.mdx | 4 + .../endpoints/workspaces/secret-snapshots.mdx | 4 + .../workspaces/update-membership.mdx | 4 + .../endpoints/workspaces/workspace-key.mdx | 2 +- .../overview/examples/create-secrets.mdx | 156 +++- .../overview/examples/delete-secrets.mdx | 34 + .../overview/examples/retrieve-secrets.mdx | 138 +++- .../overview/examples/update-secrets.mdx | 146 +++- docs/api-reference/overview/introduction.mdx | 14 +- docs/mint.json | 15 +- docs/spec.yaml | 477 ++++++++++-- 28 files changed, 2216 insertions(+), 222 deletions(-) create mode 100644 backend/src/middleware/requireMembershipAuth.ts create mode 100644 docs/api-reference/endpoints/secrets/rollback-version.mdx create mode 100644 docs/api-reference/endpoints/secrets/versions.mdx create mode 100644 docs/api-reference/endpoints/workspaces/delete-membership.mdx create mode 100644 docs/api-reference/endpoints/workspaces/logs.mdx create mode 100644 docs/api-reference/endpoints/workspaces/memberships.mdx create mode 100644 docs/api-reference/endpoints/workspaces/rollback-snapshot.mdx create mode 100644 docs/api-reference/endpoints/workspaces/secret-snapshots.mdx create mode 100644 docs/api-reference/endpoints/workspaces/update-membership.mdx create mode 100644 docs/api-reference/overview/examples/delete-secrets.mdx diff --git a/backend/spec.json b/backend/spec.json index baad21e7e..e28c66eaa 100644 --- a/backend/spec.json +++ b/backend/spec.json @@ -18,7 +18,8 @@ "paths": { "/api/v1/secret/{secretId}/secret-versions": { "get": { - "description": "", + "summary": "Return secret versions", + "description": "Return secret versions", "parameters": [ { "name": "secretId", @@ -26,10 +27,13 @@ "required": true, "schema": { "type": "string" - } + }, + "description": "ID of secret" }, { "name": "offset", + "description": "Number of versions to skip", + "required": false, "in": "query", "schema": { "type": "string" @@ -37,6 +41,8 @@ }, { "name": "limit", + "description": "Maximum number of versions to return", + "required": false, "in": "query", "schema": { "type": "string" @@ -45,17 +51,39 @@ ], "responses": { "200": { - "description": "OK" + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "secretVersions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SecretVersion" + }, + "description": "Secret versions" + } + } + } + } + } }, "400": { "description": "Bad Request" } - } + }, + "security": [ + { + "apiKeyAuth": [] + } + ] } }, "/api/v1/secret/{secretId}/secret-versions/rollback": { "post": { - "description": "", + "summary": "Roll back secret to a version.", + "description": "Roll back secret to a version.", "parameters": [ { "name": "secretId", @@ -63,25 +91,47 @@ "required": true, "schema": { "type": "string" - } + }, + "description": "ID of secret" } ], "responses": { "200": { - "description": "OK" + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "secret": { + "type": "object", + "$ref": "#/components/schemas/Secret", + "description": "Secret rolled back to" + } + } + } + } + } }, "400": { "description": "Bad Request" } }, + "security": [ + { + "apiKeyAuth": [] + } + ], "requestBody": { + "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "version": { - "example": "any" + "type": "integer", + "description": "Version of secret to roll back to" } } } @@ -115,7 +165,8 @@ }, "/api/v1/workspace/{workspaceId}/secret-snapshots": { "get": { - "description": "", + "summary": "Return project secret snapshot ids", + "description": "Return project secret snapshots ids", "parameters": [ { "name": "workspaceId", @@ -123,10 +174,13 @@ "required": true, "schema": { "type": "string" - } + }, + "description": "ID of project" }, { "name": "offset", + "description": "Number of secret snapshots to skip", + "required": false, "in": "query", "schema": { "type": "string" @@ -134,6 +188,8 @@ }, { "name": "limit", + "description": "Maximum number of secret snapshots to return", + "required": false, "in": "query", "schema": { "type": "string" @@ -142,12 +198,33 @@ ], "responses": { "200": { - "description": "OK" + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "secretSnapshots": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SecretSnapshot" + }, + "description": "Project secret snapshots" + } + } + } + } + } }, "400": { "description": "Bad Request" } - } + }, + "security": [ + { + "apiKeyAuth": [] + } + ] } }, "/api/v1/workspace/{workspaceId}/secret-snapshots/count": { @@ -175,8 +252,8 @@ }, "/api/v1/workspace/{workspaceId}/secret-snapshots/rollback": { "post": { - "summary": "Roll back project secrets to those captured in a secret snapshot version", - "description": "Roll back project secrets to those captured in a secret snapshot version", + "summary": "Roll back project secrets to those captured in a secret snapshot version.", + "description": "Roll back project secrets to those captured in a secret snapshot version.", "parameters": [ { "name": "workspaceId", @@ -194,11 +271,16 @@ "content": { "application/json": { "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Secret" - }, - "description": "Array of secrets captured in the secret snapshot" + "type": "object", + "properties": { + "secrets": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Secret" + }, + "description": "Secrets rolled back to" + } + } } } } @@ -232,18 +314,31 @@ }, "/api/v1/workspace/{workspaceId}/logs": { "get": { - "description": "", + "summary": "Return project (audit) logs", + "description": "Return project (audit) logs", "parameters": [ { "name": "workspaceId", "in": "path", "required": true, + "schema": { + "type": "string" + }, + "description": "ID of project" + }, + { + "name": "userId", + "description": "ID of project member", + "required": false, + "in": "query", "schema": { "type": "string" } }, { "name": "offset", + "description": "Number of logs to skip", + "required": false, "in": "query", "schema": { "type": "string" @@ -251,6 +346,8 @@ }, { "name": "limit", + "description": "Maximum number of logs to return", + "required": false, "in": "query", "schema": { "type": "string" @@ -258,20 +355,21 @@ }, { "name": "sortBy", - "in": "query", + "description": "Order to sort the logs by", "schema": { - "type": "string" - } - }, - { - "name": "userId", - "in": "query", - "schema": { - "type": "string" - } + "type": "string", + "enum": [ + "oldest", + "recent" + ] + }, + "required": false, + "in": "query" }, { "name": "actionNames", + "description": "Names of log actions (comma-separated)", + "required": false, "in": "query", "schema": { "type": "string" @@ -280,12 +378,33 @@ ], "responses": { "200": { - "description": "OK" + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "logs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Log" + }, + "description": "Project logs" + } + } + } + } + } }, "400": { "description": "Bad Request" } - } + }, + "security": [ + { + "apiKeyAuth": [] + } + ] } }, "/api/v1/action/{actionId}": { @@ -2117,8 +2236,13 @@ "application/json": { "schema": { "type": "object", - "$ref": "#/components/schemas/CurrentUser", - "description": "Current user on request" + "properties": { + "user": { + "type": "object", + "$ref": "#/components/schemas/CurrentUser", + "description": "Current user on request" + } + } } } } @@ -2279,6 +2403,170 @@ } } }, + "/api/v2/workspace/{workspaceId}/memberships": { + "get": { + "summary": "Return project memberships", + "description": "Return project memberships", + "parameters": [ + { + "name": "workspaceId", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "ID of project" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "memberships": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Membership" + }, + "description": "Memberships of project" + } + } + } + } + } + }, + "400": { + "description": "Bad Request" + } + }, + "security": [ + { + "apiKeyAuth": [] + } + ] + } + }, + "/api/v2/workspace/{workspaceId}/memberships/{membershipId}": { + "delete": { + "summary": "Delete project membership", + "description": "Delete project membership", + "parameters": [ + { + "name": "workspaceId", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "ID of project" + }, + { + "name": "membershipId", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "ID of membership" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "membership": { + "$ref": "#/components/schemas/Membership", + "description": "Deleted membership" + } + } + } + } + } + }, + "400": { + "description": "Bad Request" + } + }, + "security": [ + { + "apiKeyAuth": [] + } + ] + }, + "patch": { + "summary": "Update project membership", + "description": "Update project membership", + "parameters": [ + { + "name": "workspaceId", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "ID of project" + }, + { + "name": "membershipId", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "ID of membership" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "membership": { + "$ref": "#/components/schemas/Membership", + "description": "Updated membership" + } + } + } + } + } + }, + "400": { + "description": "Bad Request" + } + }, + "security": [ + { + "apiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "role": { + "type": "string", + "description": "Role of membership - either admin or member" + } + } + } + } + } + } + } + }, "/api/v2/secret/batch-create/workspace/{workspaceId}/environment/{environment}": { "post": { "description": "", @@ -2565,11 +2853,16 @@ "content": { "application/json": { "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Secret" - }, - "description": "Array of newly-created secrets for the given project and environment" + "type": "object", + "properties": { + "secrets": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Secret" + }, + "description": "Newly-created secrets for the given project and environment" + } + } } } } @@ -2608,18 +2901,49 @@ "get": { "summary": "Read secrets", "description": "Read secrets from a project and environment", - "parameters": [], + "parameters": [ + { + "name": "workspaceId", + "description": "ID of project", + "required": true, + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "environment", + "description": "Environment within project", + "required": true, + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "content", + "in": "query", + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Secret" - }, - "description": "Array of secrets for the given project and environment" + "type": "object", + "properties": { + "secrets": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Secret" + }, + "description": "Secrets for the given project and environment" + } + } } } } @@ -2629,27 +2953,7 @@ { "apiKeyAuth": [] } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "workspaceId": { - "type": "string", - "description": "ID of project" - }, - "environment": { - "type": "string", - "description": "Environment within project" - } - } - } - } - } - } + ] }, "patch": { "summary": "Update secret(s)", @@ -2661,11 +2965,16 @@ "content": { "application/json": { "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Secret" - }, - "description": "Array of newly-updated secrets for the given project and environment" + "type": "object", + "properties": { + "secrets": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Secret" + }, + "description": "Updated secrets" + } + } } } } @@ -2703,11 +3012,16 @@ "content": { "application/json": { "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Secret" - }, - "description": "Array of deleted secrets" + "type": "object", + "properties": { + "secrets": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Secret" + }, + "description": "Deleted secrets" + } + } } } } @@ -2942,6 +3256,52 @@ } } }, + "Membership": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "_id": { + "type": "string", + "example": "" + }, + "email": { + "type": "string", + "example": "" + }, + "firstName": { + "type": "string", + "example": "" + }, + "lastName": { + "type": "string", + "example": "" + }, + "publicKey": { + "type": "string", + "example": "" + }, + "updatedAt": { + "type": "string", + "example": "" + }, + "createdAt": { + "type": "string", + "example": "" + } + } + }, + "workspace": { + "type": "string", + "example": "" + }, + "role": { + "type": "string", + "example": "admin" + } + } + }, "ProjectKey": { "type": "object", "properties": { @@ -3127,6 +3487,187 @@ "example": "" } } + }, + "Log": { + "type": "object", + "properties": { + "_id": { + "type": "string", + "example": "" + }, + "user": { + "type": "object", + "properties": { + "_id": { + "type": "string", + "example": "" + }, + "email": { + "type": "string", + "example": "" + }, + "firstName": { + "type": "string", + "example": "" + }, + "lastName": { + "type": "string", + "example": "" + } + } + }, + "workspace": { + "type": "string", + "example": "" + }, + "actionNames": { + "type": "array", + "example": [ + "addSecrets" + ], + "items": { + "type": "string" + } + }, + "actions": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "example": "addSecrets" + }, + "user": { + "type": "string", + "example": "" + }, + "workspace": { + "type": "string", + "example": "" + }, + "payload": { + "type": "array", + "items": { + "type": "object", + "properties": { + "oldSecretVersion": { + "type": "string", + "example": "" + }, + "newSecretVersion": { + "type": "string", + "example": "" + } + } + } + } + } + } + }, + "channel": { + "type": "string", + "example": "cli" + }, + "ipAddress": { + "type": "string", + "example": "192.168.0.1" + }, + "updatedAt": { + "type": "string", + "example": "" + }, + "createdAt": { + "type": "string", + "example": "" + } + } + }, + "SecretSnapshot": { + "type": "object", + "properties": { + "workspace": { + "type": "string", + "example": "" + }, + "version": { + "type": "number", + "example": 1 + }, + "secretVersions": { + "type": "array", + "items": { + "type": "object", + "properties": { + "_id": { + "type": "string", + "example": "" + } + } + } + } + } + }, + "SecretVersion": { + "type": "object", + "properties": { + "_id": { + "type": "string", + "example": "" + }, + "secret": { + "type": "string", + "example": "" + }, + "version": { + "type": "number", + "example": 1 + }, + "workspace": { + "type": "string", + "example": "" + }, + "type": { + "type": "string", + "example": "" + }, + "user": { + "type": "string", + "example": "" + }, + "environment": { + "type": "string", + "example": "" + }, + "isDeleted": { + "type": "string", + "example": "" + }, + "secretKeyCiphertext": { + "type": "string", + "example": "" + }, + "secretKeyIV": { + "type": "string", + "example": "" + }, + "secretKeyTag": { + "type": "string", + "example": "" + }, + "secretValueCiphertext": { + "type": "string", + "example": "" + }, + "secretValueIV": { + "type": "string", + "example": "" + }, + "secretValueTag": { + "type": "string", + "example": "" + } + } } }, "securitySchemes": { diff --git a/backend/src/controllers/v2/secretsController.ts b/backend/src/controllers/v2/secretsController.ts index 31ad0d94c..43434df63 100644 --- a/backend/src/controllers/v2/secretsController.ts +++ b/backend/src/controllers/v2/secretsController.ts @@ -58,11 +58,16 @@ export const createSecrets = async (req: Request, res: Response) => { content: { "application/json": { "schema": { - "type": "array", - "items": { - $ref: "#/components/schemas/Secret" - }, - "description": "Array of newly-created secrets for the given project and environment" + "type": "object", + "properties": { + "secrets": { + "type": "array", + "items": { + $ref: "#/components/schemas/Secret" + }, + "description": "Newly-created secrets for the given project and environment" + } + } } } } @@ -205,36 +210,32 @@ export const getSecrets = async (req: Request, res: Response) => { "apiKeyAuth": [] }] - #swagger.requestBody = { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "workspaceId": { - "type": "string", - "description": "ID of project" - }, - "environment": { - "type": "string", - "description": "Environment within project" - } - } - } - } - } - } + #swagger.parameters['workspaceId'] = { + "description": "ID of project", + "required": true, + "type": "string" + } + + #swagger.parameters['environment'] = { + "description": "Environment within project", + "required": true, + "type": "string" + } #swagger.responses[200] = { content: { "application/json": { "schema": { - "type": "array", - "items": { - $ref: "#/components/schemas/Secret" - }, - "description": "Array of secrets for the given project and environment" + "type": "object", + "properties": { + "secrets": { + "type": "array", + "items": { + $ref: "#/components/schemas/Secret" + }, + "description": "Secrets for the given project and environment" + } + } } } } @@ -307,9 +308,6 @@ export const getSecrets = async (req: Request, res: Response) => { * @param res */ export const updateSecrets = async (req: Request, res: Response) => { - - // TODO: fix update secret schema - /* #swagger.summary = 'Update secret(s)' #swagger.description = 'Update secret(s)' @@ -339,15 +337,20 @@ export const updateSecrets = async (req: Request, res: Response) => { content: { "application/json": { "schema": { - "type": "array", - "items": { - $ref: "#/components/schemas/Secret" - }, - "description": "Array of newly-updated secrets for the given project and environment" + "type": "object", + "properties": { + "secrets": { + "type": "array", + "items": { + $ref: "#/components/schemas/Secret" + }, + "description": "Updated secrets" + } + } } } } - } + } */ const channel = req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli'; @@ -544,12 +547,17 @@ export const deleteSecrets = async (req: Request, res: Response) => { #swagger.responses[200] = { content: { "application/json": { - schema: { - "type": "array", - "items": { - $ref: "#/components/schemas/Secret" - }, - "description": "Array of deleted secrets" + "schema": { + "type": "object", + "properties": { + "secrets": { + "type": "array", + "items": { + $ref: "#/components/schemas/Secret" + }, + "description": "Deleted secrets" + } + } } } } diff --git a/backend/src/controllers/v2/usersController.ts b/backend/src/controllers/v2/usersController.ts index 194e508b3..7ad247ff0 100644 --- a/backend/src/controllers/v2/usersController.ts +++ b/backend/src/controllers/v2/usersController.ts @@ -18,8 +18,13 @@ export const getMe = async (req: Request, res: Response) => { "application/json": { "schema": { "type": "object", - $ref: "#/components/schemas/CurrentUser", - "description": "Current user on request" + "properties": { + "user": { + "type": "object", + $ref: "#/components/schemas/CurrentUser", + "description": "Current user on request" + } + } } } } diff --git a/backend/src/controllers/v2/workspaceController.ts b/backend/src/controllers/v2/workspaceController.ts index 6acec644d..2b3aeda1d 100644 --- a/backend/src/controllers/v2/workspaceController.ts +++ b/backend/src/controllers/v2/workspaceController.ts @@ -1,7 +1,9 @@ import { Request, Response } from 'express'; import * as Sentry from '@sentry/node'; +import { Types } from 'mongoose'; import { Workspace, + Secret, Membership, MembershipOrg, Integration, @@ -242,4 +244,222 @@ export const getWorkspaceServiceTokenData = async ( return res.status(200).send({ serviceTokenData }); +} + +/** + * Return memberships for workspace with id [workspaceId] + * @param req + * @param res + * @returns + */ +export const getWorkspaceMemberships = async (req: Request, res: Response) => { + /* + #swagger.summary = 'Return project memberships' + #swagger.description = 'Return project memberships' + + #swagger.security = [{ + "apiKeyAuth": [] + }] + + #swagger.parameters['workspaceId'] = { + "description": "ID of project", + "required": true, + "type": "string" + } + + #swagger.responses[200] = { + content: { + "application/json": { + "schema": { + "type": "object", + "properties": { + "memberships": { + "type": "array", + "items": { + $ref: "#/components/schemas/Membership" + }, + "description": "Memberships of project" + } + } + } + } + } + } + */ + let memberships; + try { + const { workspaceId } = req.params; + + memberships = await Membership.find({ + workspace: workspaceId + }).populate('user', '+publicKey'); + } catch (err) { + Sentry.setUser({ email: req.user.email }); + Sentry.captureException(err); + return res.status(400).send({ + message: 'Failed to get workspace members' + }); + } + + return res.status(200).send({ + memberships + }); +} + +/** + * Delete workspace membership with id [membershipId] + * @param req + * @param res + * @returns + */ +export const deleteWorkspaceMembership = async (req: Request, res: Response) => { + /* + #swagger.summary = 'Delete project membership' + #swagger.description = 'Delete project membership' + + #swagger.security = [{ + "apiKeyAuth": [] + }] + + #swagger.parameters['workspaceId'] = { + "description": "ID of project", + "required": true, + "type": "string" + } + + #swagger.parameters['membershipId'] = { + "description": "ID of membership", + "required": true, + "type": "string" + } + + #swagger.responses[200] = { + content: { + "application/json": { + "schema": { + "type": "object", + "properties": { + "membership": { + $ref: "#/components/schemas/Membership", + "description": "Deleted membership" + } + } + } + } + } + } + */ + let membership; + try { + const { + membershipId + } = req.params; + + membership = await Membership.findByIdAndDelete(membershipId); + + if (!membership) throw new Error('Failed to delete workspace membership'); + + await Key.deleteMany({ + receiver: membership.user, + workspace: membership.workspace + }); + } catch (err) { + Sentry.setUser({ email: req.user.email }); + Sentry.captureException(err); + return res.status(400).send({ + message: 'Failed to delete workspace membership' + }); + } + + return res.status(200).send({ + membership + }); +} + +/** + * Update role of membership with id [membershipId] to role [role] + * @param req + * @param res + * @returns + */ +export const updateWorkspaceMembership = async (req: Request, res: Response) => { + /* + #swagger.summary = 'Update project membership' + #swagger.description = 'Update project membership' + + #swagger.security = [{ + "apiKeyAuth": [] + }] + + #swagger.parameters['workspaceId'] = { + "description": "ID of project", + "required": true, + "type": "string" + } + + #swagger.parameters['membershipId'] = { + "description": "ID of membership", + "required": true, + "type": "string" + } + + #swagger.requestBody = { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "role": { + "type": "string", + "description": "Role of membership - either admin or member", + } + } + } + } + } + } + + #swagger.responses[200] = { + content: { + "application/json": { + "schema": { + "type": "object", + "properties": { + "membership": { + $ref: "#/components/schemas/Membership", + "description": "Updated membership" + } + } + } + } + } + } + */ + let membership; + try { + const { + membershipId + } = req.params; + const { role } = req.body; + + membership = await Membership.findByIdAndUpdate( + membershipId, + { + role + }, { + new: true + } + ); + } catch (err) { + Sentry.setUser({ email: req.user.email }); + Sentry.captureException(err); + return res.status(400).send({ + message: 'Failed to update workspace membership' + }); + } + + return res.status(200).send({ + membership + }); } \ No newline at end of file diff --git a/backend/src/ee/controllers/v1/secretController.ts b/backend/src/ee/controllers/v1/secretController.ts index 751f21611..562c8aa88 100644 --- a/backend/src/ee/controllers/v1/secretController.ts +++ b/backend/src/ee/controllers/v1/secretController.ts @@ -10,6 +10,51 @@ import { EESecretService } from '../../services'; * @param res */ export const getSecretVersions = async (req: Request, res: Response) => { + /* + #swagger.summary = 'Return secret versions' + #swagger.description = 'Return secret versions' + + #swagger.security = [{ + "apiKeyAuth": [] + }] + + #swagger.parameters['secretId'] = { + "description": "ID of secret", + "required": true, + "type": "string" + } + + #swagger.parameters['offset'] = { + "description": "Number of versions to skip", + "required": false, + "type": "string" + } + + #swagger.parameters['limit'] = { + "description": "Maximum number of versions to return", + "required": false, + "type": "string" + } + + #swagger.responses[200] = { + content: { + "application/json": { + schema: { + "type": "object", + "properties": { + "secretVersions": { + "type": "array", + "items": { + $ref: "#/components/schemas/SecretVersion" + }, + "description": "Secret versions" + } + } + } + } + } + } + */ let secretVersions; try { const { secretId } = req.params; @@ -44,6 +89,54 @@ import { EESecretService } from '../../services'; * @returns */ export const rollbackSecretVersion = async (req: Request, res: Response) => { + /* + #swagger.summary = 'Roll back secret to a version.' + #swagger.description = 'Roll back secret to a version.' + + #swagger.security = [{ + "apiKeyAuth": [] + }] + + #swagger.parameters['secretId'] = { + "description": "ID of secret", + "required": true, + "type": "string" + } + + #swagger.requestBody = { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "version": { + "type": "integer", + "description": "Version of secret to roll back to" + } + } + } + } + } + } + + #swagger.responses[200] = { + content: { + "application/json": { + schema: { + "type": "object", + "properties": { + "secret": { + "type": "object", + $ref: "#/components/schemas/Secret", + "description": "Secret rolled back to" + } + } + } + } + } + } + */ let secret; try { const { secretId } = req.params; diff --git a/backend/src/ee/controllers/v1/workspaceController.ts b/backend/src/ee/controllers/v1/workspaceController.ts index f29c42861..ea9bb7dab 100644 --- a/backend/src/ee/controllers/v1/workspaceController.ts +++ b/backend/src/ee/controllers/v1/workspaceController.ts @@ -19,6 +19,51 @@ import { getLatestSecretVersionIds } from '../../helpers/secretVersion'; * @param res */ export const getWorkspaceSecretSnapshots = async (req: Request, res: Response) => { + /* + #swagger.summary = 'Return project secret snapshot ids' + #swagger.description = 'Return project secret snapshots ids' + + #swagger.security = [{ + "apiKeyAuth": [] + }] + + #swagger.parameters['workspaceId'] = { + "description": "ID of project", + "required": true, + "type": "string" + } + + #swagger.parameters['offset'] = { + "description": "Number of secret snapshots to skip", + "required": false, + "type": "string" + } + + #swagger.parameters['limit'] = { + "description": "Maximum number of secret snapshots to return", + "required": false, + "type": "string" + } + + #swagger.responses[200] = { + content: { + "application/json": { + schema: { + "type": "object", + "properties": { + "secretSnapshots": { + "type": "array", + "items": { + $ref: "#/components/schemas/SecretSnapshot" + }, + "description": "Project secret snapshots" + } + } + } + } + } + } + */ let secretSnapshots; try { const { workspaceId } = req.params; @@ -79,8 +124,8 @@ export const getWorkspaceSecretSnapshotsCount = async (req: Request, res: Respon */ export const rollbackWorkspaceSecretSnapshot = async (req: Request, res: Response) => { /* - #swagger.summary = 'Roll back project secrets to those captured in a secret snapshot version' - #swagger.description = 'Roll back project secrets to those captured in a secret snapshot version' + #swagger.summary = 'Roll back project secrets to those captured in a secret snapshot version.' + #swagger.description = 'Roll back project secrets to those captured in a secret snapshot version.' #swagger.security = [{ "apiKeyAuth": [] @@ -113,11 +158,16 @@ export const rollbackWorkspaceSecretSnapshot = async (req: Request, res: Respons content: { "application/json": { schema: { - "type": "array", - "items": { - $ref: "#/components/schemas/Secret" - }, - "description": "Array of secrets captured in the secret snapshot" + "type": "object", + "properties": { + "secrets": { + "type": "array", + "items": { + $ref: "#/components/schemas/Secret" + }, + "description": "Secrets rolled back to" + } + } } } } @@ -276,6 +326,72 @@ export const rollbackWorkspaceSecretSnapshot = async (req: Request, res: Respons * @returns */ export const getWorkspaceLogs = async (req: Request, res: Response) => { + /* + #swagger.summary = 'Return project (audit) logs' + #swagger.description = 'Return project (audit) logs' + + #swagger.security = [{ + "apiKeyAuth": [] + }] + + #swagger.parameters['workspaceId'] = { + "description": "ID of project", + "required": true, + "type": "string" + } + + #swagger.parameters['userId'] = { + "description": "ID of project member", + "required": false, + "type": "string" + } + + #swagger.parameters['offset'] = { + "description": "Number of logs to skip", + "required": false, + "type": "string" + } + + #swagger.parameters['limit'] = { + "description": "Maximum number of logs to return", + "required": false, + "type": "string" + } + + #swagger.parameters['sortBy'] = { + "description": "Order to sort the logs by", + "schema": { + "type": "string", + "@enum": ["oldest", "recent"] + }, + "required": false + } + + #swagger.parameters['actionNames'] = { + "description": "Names of log actions (comma-separated)", + "required": false, + "type": "string" + } + + #swagger.responses[200] = { + content: { + "application/json": { + schema: { + "type": "object", + "properties": { + "logs": { + "type": "array", + "items": { + $ref: "#/components/schemas/Log" + }, + "description": "Project logs" + } + } + } + } + } + } + */ let logs try { const { workspaceId } = req.params; diff --git a/backend/src/middleware/index.ts b/backend/src/middleware/index.ts index 6a3537076..e9bffac2d 100644 --- a/backend/src/middleware/index.ts +++ b/backend/src/middleware/index.ts @@ -2,6 +2,7 @@ import requireAuth from './requireAuth'; import requireBotAuth from './requireBotAuth'; import requireSignupAuth from './requireSignupAuth'; import requireWorkspaceAuth from './requireWorkspaceAuth'; +import requireMembershipAuth from './requireMembershipAuth'; import requireOrganizationAuth from './requireOrganizationAuth'; import requireIntegrationAuth from './requireIntegrationAuth'; import requireIntegrationAuthorizationAuth from './requireIntegrationAuthorizationAuth'; @@ -16,6 +17,7 @@ export { requireBotAuth, requireSignupAuth, requireWorkspaceAuth, + requireMembershipAuth, requireOrganizationAuth, requireIntegrationAuth, requireIntegrationAuthorizationAuth, diff --git a/backend/src/middleware/requireMembershipAuth.ts b/backend/src/middleware/requireMembershipAuth.ts new file mode 100644 index 000000000..b44d25ceb --- /dev/null +++ b/backend/src/middleware/requireMembershipAuth.ts @@ -0,0 +1,57 @@ +import { Request, Response, NextFunction } from 'express'; +import { UnauthorizedRequestError } from '../utils/errors'; +import { + Membership, +} from '../models'; +import { validateMembership } from '../helpers/membership'; + +type req = 'params' | 'body' | 'query'; +/** + * Validate membership with id [membershipId] and that user with id + * [req.user._id] can modify that membership. + * @param {Object} obj + * @param {String[]} obj.acceptedRoles - accepted workspace roles for JWT auth + * @param {String[]} obj.location - location of [workspaceId] on request (e.g. params, body) for parsing + */ +const requireMembershipAuth = ({ + acceptedRoles, + location = 'params' +}: { + acceptedRoles: string[]; + location?: req; +}) => { + return async ( + req: Request, + res: Response, + next: NextFunction + ) => { + try { + const { membershipId } = req[location]; + + const membership = await Membership.findById(membershipId); + + if (!membership) throw new Error('Failed to find target membership'); + + const userMembership = await Membership.findOne({ + workspace: membership.workspace + }); + + if (!userMembership) throw new Error('Failed to validate own membership') + + const targetMembership = await validateMembership({ + userId: req.user._id.toString(), + workspaceId: membership.workspace.toString(), + acceptedRoles + }); + + req.targetMembership = targetMembership; + + } catch (err) { + return next(UnauthorizedRequestError({ + message: 'Unable to validate workspace membership' + })); + } + } +} + +export default requireMembershipAuth; \ No newline at end of file diff --git a/backend/src/routes/v1/membership.ts b/backend/src/routes/v1/membership.ts index 76bf3e57a..f2c3eb554 100644 --- a/backend/src/routes/v1/membership.ts +++ b/backend/src/routes/v1/membership.ts @@ -4,7 +4,7 @@ import { body, param } from 'express-validator'; import { requireAuth, validateRequest } from '../../middleware'; import { membershipController } from '../../controllers/v1'; -router.get( // used for CLI (deprecate) +router.get( // used for old CLI (deprecate) '/:workspaceId/connect', requireAuth({ acceptedAuthModes: ['jwt'] diff --git a/backend/src/routes/v2/workspace.ts b/backend/src/routes/v2/workspace.ts index c90834d6d..ebbfe0d4c 100644 --- a/backend/src/routes/v2/workspace.ts +++ b/backend/src/routes/v2/workspace.ts @@ -3,6 +3,7 @@ const router = express.Router(); import { body, param, query } from 'express-validator'; import { requireAuth, + requireMembershipAuth, requireWorkspaceAuth, validateRequest } from '../../middleware'; @@ -67,4 +68,54 @@ router.get( workspaceController.getWorkspaceServiceTokenData ); +// TODO: /POST to create membership + +router.get( // new - TODO: rewire dashboard to this route + '/:workspaceId/memberships', + param('workspaceId').exists().trim(), + validateRequest, + requireAuth({ + acceptedAuthModes: ['jwt'] + }), + requireWorkspaceAuth({ + acceptedRoles: [ADMIN, MEMBER], + }), + workspaceController.getWorkspaceMemberships +); + +router.delete( // TODO - rewire dashboard to this route + '/:workspaceId/memberships/:membershipId', + param('workspaceId').exists().trim(), + param('membershipId').exists().trim(), + validateRequest, + requireAuth({ + acceptedAuthModes: ['jwt'] + }), + requireWorkspaceAuth({ + acceptedRoles: [ADMIN], + }), + requireMembershipAuth({ + acceptedRoles: [ADMIN] + }), + workspaceController.deleteWorkspaceMembership +); + +router.patch( // TODO - rewire dashboard to this route + '/:workspaceId/memberships/:membershipId', + param('workspaceId').exists().trim(), + param('membershipId').exists().trim(), + body('role').exists().isString().trim().isIn([ADMIN, MEMBER]), + validateRequest, + requireAuth({ + acceptedAuthModes: ['jwt'] + }), + requireWorkspaceAuth({ + acceptedRoles: [ADMIN], + }), + requireMembershipAuth({ + acceptedRoles: [ADMIN] + }), + workspaceController.updateWorkspaceMembership +); + export default router; diff --git a/backend/src/types/express/index.d.ts b/backend/src/types/express/index.d.ts index ae9edb4c5..1ce63e609 100644 --- a/backend/src/types/express/index.d.ts +++ b/backend/src/types/express/index.d.ts @@ -8,6 +8,7 @@ declare global { user: any; workspace: any; membership: any; + targetMembership: any; organization: any; membershipOrg: any; integration: any; diff --git a/backend/swagger/index.ts b/backend/swagger/index.ts index 91f27da44..cbf05ed7e 100644 --- a/backend/swagger/index.ts +++ b/backend/swagger/index.ts @@ -53,6 +53,19 @@ const generateOpenAPISpec = async () => { updatedAt: '', createdAt: '' }, + Membership: { + user: { + _id: '', + email: '', + firstName: '', + lastName: '', + publicKey: '', + updatedAt: '', + createdAt: '' + }, + workspace: '', + role: 'admin' + }, ProjectKey: { encryptedkey: '', nonce: '', @@ -103,6 +116,61 @@ const generateOpenAPISpec = async () => { secretCommentTag: '', updatedAt: '', createdAt: '' + }, + Log: { + _id: '', + user: { + _id: '', + email: '', + firstName: '', + lastName: '' + }, + workspace: '', + actionNames: [ + 'addSecrets' + ], + actions: [ + { + name: 'addSecrets', + user: '', + workspace: '', + payload: [ + { + oldSecretVersion: '', + newSecretVersion: '' + } + ] + } + ], + channel: 'cli', + ipAddress: '192.168.0.1', + updatedAt: '', + createdAt: '' + }, + SecretSnapshot: { + workspace: '', + version: 1, + secretVersions: [ + { + _id: '' + } + ] + }, + SecretVersion: { + _id: '', + secret: '', + version: 1, + workspace: '', + type: '', + user: '', + environment: '', + isDeleted: '', + secretKeyCiphertext: '', + secretKeyIV: '', + secretKeyTag: '', + secretValueCiphertext: '', + secretValueIV: '', + secretValueTag: '', } } }; diff --git a/docs/api-reference/endpoints/secrets/rollback-version.mdx b/docs/api-reference/endpoints/secrets/rollback-version.mdx new file mode 100644 index 000000000..dff577fe1 --- /dev/null +++ b/docs/api-reference/endpoints/secrets/rollback-version.mdx @@ -0,0 +1,4 @@ +--- +title: "Roll Back to Version" +openapi: "POST /api/v1/secret/{secretId}/secret-versions/rollback" +--- diff --git a/docs/api-reference/endpoints/secrets/versions.mdx b/docs/api-reference/endpoints/secrets/versions.mdx new file mode 100644 index 000000000..e8693684c --- /dev/null +++ b/docs/api-reference/endpoints/secrets/versions.mdx @@ -0,0 +1,4 @@ +--- +title: "Get Versions" +openapi: "GET /api/v1/secret/{secretId}/secret-versions" +--- diff --git a/docs/api-reference/endpoints/workspaces/delete-membership.mdx b/docs/api-reference/endpoints/workspaces/delete-membership.mdx new file mode 100644 index 000000000..826eafa83 --- /dev/null +++ b/docs/api-reference/endpoints/workspaces/delete-membership.mdx @@ -0,0 +1,4 @@ +--- +title: "Delete Membership" +openapi: "DELETE /api/v2/workspace/{workspaceId}/memberships/{membershipId}" +--- diff --git a/docs/api-reference/endpoints/workspaces/logs.mdx b/docs/api-reference/endpoints/workspaces/logs.mdx new file mode 100644 index 000000000..d5921c1c0 --- /dev/null +++ b/docs/api-reference/endpoints/workspaces/logs.mdx @@ -0,0 +1,4 @@ +--- +title: "Get Logs" +openapi: "GET /api/v1/workspace/{workspaceId}/logs" +--- diff --git a/docs/api-reference/endpoints/workspaces/memberships.mdx b/docs/api-reference/endpoints/workspaces/memberships.mdx new file mode 100644 index 000000000..38b86d616 --- /dev/null +++ b/docs/api-reference/endpoints/workspaces/memberships.mdx @@ -0,0 +1,4 @@ +--- +title: "Get Memberships" +openapi: "GET /api/v2/workspace/{workspaceId}/memberships" +--- diff --git a/docs/api-reference/endpoints/workspaces/rollback-snapshot.mdx b/docs/api-reference/endpoints/workspaces/rollback-snapshot.mdx new file mode 100644 index 000000000..8b648a400 --- /dev/null +++ b/docs/api-reference/endpoints/workspaces/rollback-snapshot.mdx @@ -0,0 +1,4 @@ +--- +title: "Roll Back to Snapshot" +openapi: "POST /api/v1/workspace/{workspaceId}/secret-snapshots/rollback" +--- diff --git a/docs/api-reference/endpoints/workspaces/secret-snapshots.mdx b/docs/api-reference/endpoints/workspaces/secret-snapshots.mdx new file mode 100644 index 000000000..2d645d4d0 --- /dev/null +++ b/docs/api-reference/endpoints/workspaces/secret-snapshots.mdx @@ -0,0 +1,4 @@ +--- +title: "Get Snapshots" +openapi: "GET /api/v1/workspace/{workspaceId}/secret-snapshots" +--- diff --git a/docs/api-reference/endpoints/workspaces/update-membership.mdx b/docs/api-reference/endpoints/workspaces/update-membership.mdx new file mode 100644 index 000000000..367793847 --- /dev/null +++ b/docs/api-reference/endpoints/workspaces/update-membership.mdx @@ -0,0 +1,4 @@ +--- +title: "Update Membership" +openapi: "PATCH /api/v2/workspace/{workspaceId}/memberships/{membershipId}" +--- diff --git a/docs/api-reference/endpoints/workspaces/workspace-key.mdx b/docs/api-reference/endpoints/workspaces/workspace-key.mdx index 2658676a3..05313eb8f 100644 --- a/docs/api-reference/endpoints/workspaces/workspace-key.mdx +++ b/docs/api-reference/endpoints/workspaces/workspace-key.mdx @@ -1,4 +1,4 @@ --- -title: "Get Project Key" +title: "Get Key" openapi: "GET /api/v2/workspace/{workspaceId}/encrypted-key" --- diff --git a/docs/api-reference/overview/examples/create-secrets.mdx b/docs/api-reference/overview/examples/create-secrets.mdx index 94f830094..b2afe1467 100644 --- a/docs/api-reference/overview/examples/create-secrets.mdx +++ b/docs/api-reference/overview/examples/create-secrets.mdx @@ -13,52 +13,140 @@ Prerequisites: 1. Get your (encrypted) private key. 2. Decrypt your (encrypted) private key with your password. -3. Get the project key for the project. -4. Decrypt the project key with your private key. -5. Encrypt your secrets with the project key. -6. Send (encrypted) secrets to the Infical API +3. Get the (encrypted) project key for the project. +4. Decrypt the (encrypted) project key with your private key. +5. Encrypt your secret(s) with the project key. +6. Send (encrypted) secret(s) to the Infical API ## Example ```js -const axios = require("axios"); -const aes = require("aes-256-gcm"); -const nacl = require("tweetnacl"); -nacl.util = require("tweetnacl-util"); +const crypto = require('crypto'); +const axios = require('axios'); -const WORKSPACE_KEY = "3a7a243eb62078c13f09203e75e8cb32"; +const ALGORITHM = 'aes-256-gcm'; +const BLOCK_SIZE_BYTES = 16; -const secretKey = "SOME_KEY"; -const secretValue = "SOME_VALUE"; +const encrypt = ( + text, + secret +) => { + const iv = crypto.randomBytes(BLOCK_SIZE_BYTES); + const cipher = crypto.createCipheriv(ALGORITHM, secret, iv); -// encrypt key of secret -const { - ciphertext: secretKeyCiphertext, - iv: secretKeyIV, - tag: secretKeyTag, -} = aes.encrypt(secretKey, WORKSPACE_KEY); + let ciphertext = cipher.update(text, 'utf8', 'base64'); + ciphertext += cipher.final('base64'); + return { + ciphertext, + iv: iv.toString('base64'), + tag: cipher.getAuthTag().toString('base64') + }; +} -// encrypt value of secret -const { - ciphertext: secretValueCiphertext, - iv: secretValueIV, - tag: secretValueTag, -} = aes.encrypt(secretKey, WORKSPACE_KEY); +const decrypt = (ciphertext, iv, tag, secret) => { + const decipher = crypto.createDecipheriv( + ALGORITHM, + secret, + Buffer.from(iv, 'base64') + ); + decipher.setAuthTag(Buffer.from(tag, 'base64')); -// construct request body -const secret = { - secretKeyCiphertext, - secretKeyIV, - secretKeyTag, - secretValueCiphertext, - secretValueIV, - secretValueTag, -}; + let cleartext = decipher.update(ciphertext, 'base64', 'utf8'); + cleartext += decipher.final('utf8'); + + return cleartext; +} + +const createSecrets = async () => { + const API_KEY = 'your_api_key'; + const PSWD = 'your_pswd'; + const WORKSPACE_ID = 'your_workspace_id'; + + const SECRET_KEY = 'SOME_KEY'; + const SECRET_VALUE = 'SOME_VALUE'; + + // 1. get (encrypted) private key + const user = await axios.get( + 'https://api.infisical.com/api/v2/users/me', { + headers: { + 'X-API-KEY': API_KEY + } + } + ); + + // 2. decrypt your (encrypted) private key with your password + const privateKey = decrypt({ + ciphertext: user.encryptedPrivateKey, + iv: user.iv, + tag: user.tag, + secret: PSWD.slice(0, 32).padStart(32, '0'); + }); + + // 3. get the (encrypted) project key for the project + const encryptedProjectKey = await axios.get( + `https://api.infisical.com/api/v2/workspace/${WORKSPACE_ID}`, { + headers: { + 'X-API-KEY': API_KEY + } + } + ); + + // 4. decrypt the project key with your private key + const projectKey = nacl.box.open( + util.decodeBase64(encryptedProjectKey), + util.decodeBase64(projectKey.nonce), + util.decodeBase64(projectKey.sender.publicKey), + util.decodeBase64(privateKey) + ); + + // 5. encrypt your secret(s) with the project key + const { + ciphertext: secretKeyCiphertext, + iv: secretKeyIV, + tag: secretKeyTag + } = encrypt(SECRET_KEY, projectKey); + + const { + ciphertext: secretValueCiphertext, + iv: secretValueIV, + tag: secretValueTag + } = encrypt(SECRET_VALUE, projectKey); + + const secret = { + secretKeyCiphertext, + secretKeyIV, + secretKeyTag, + secretValueCiphertext, + secretValueIV, + secretValueTag + } + + // 6. Send (encrypted) secret(s) to the Infisical API + await axios.post( + `https://api.infisical.com/api/v2/secrets`, + { + workspaceId: WORKSPACE_ID, + environment: 'dev', + secrets: secret + }, + { + headers: { + 'X-API-KEY': API_KEY + } + } + ); +} + +createSecrets(); ``` This example uses [TweetNaCl.js](https://tweetnacl.js.org/#/), a port of TweetNacl/Nacl, to perform asymmeric decryption of the project key but there - are ports of NaCl in every major language. - + are ports of NaCl available in every major language. + + It can be useful to perform steps 1-4 ahead of time and store away your + private key (and even project key) for later use. The Infisical CLI works by + securely storing your private key via your OS keyring. + diff --git a/docs/api-reference/overview/examples/delete-secrets.mdx b/docs/api-reference/overview/examples/delete-secrets.mdx new file mode 100644 index 000000000..c602cef7e --- /dev/null +++ b/docs/api-reference/overview/examples/delete-secrets.mdx @@ -0,0 +1,34 @@ +--- +title: "Delete secrets" +--- + +In this example, we demonstrate how to delete secrets + +Prerequisites: + +- Set up and add envars to [Infisical Cloud](https://app.infisical.com) +- Grasp a basic understanding of the system and its underlying cryptography [here](/api-reference/overview/introduction). + +## Example + +```js +const deleteSecrets = async () => { + const API_KEY = "your_api_key"; + const SECRET_ID = "ID"; // ID of secret to delete + + // 6. Send ID(s) of secret(s) to delete to the Infisical API + await axios.delete( + `https://api.infisical.com/api/v2/secrets`, + { + secretIds: SECRET_ID, + }, + { + headers: { + "X-API-KEY": API_KEY, + }, + } + ); +}; + +deleteSecrets(); +``` diff --git a/docs/api-reference/overview/examples/retrieve-secrets.mdx b/docs/api-reference/overview/examples/retrieve-secrets.mdx index 2a314e65d..2f34339c5 100644 --- a/docs/api-reference/overview/examples/retrieve-secrets.mdx +++ b/docs/api-reference/overview/examples/retrieve-secrets.mdx @@ -2,9 +2,141 @@ title: "Retrieve secrets" --- +In this example, we demonstrate how to retrieve secrets from a project and environment. + +Prerequisites: + +- Set up and add envars to [Infisical Cloud](https://app.infisical.com) +- Grasp a basic understanding of the system and its underlying cryptography [here](/api-reference/overview/introduction). + +## Flow + 1. Get your (encrypted) private key. 2. Decrypt your (encrypted) private key with your password. -3. Get the project key for the project. -4. Decrypt the project key with your private key. +3. Get the (encrypted) project key for the project. +4. Decrypt the (encrypted) project key with your private key. 5. Get secrets for a project and environment. -6. Decrypt the secrets in your project. +6. Decrypt the (encrypted) secrets + +## Example + +```js +const crypto = require('crypto'); +const axios = require('axios'); + +const ALGORITHM = 'aes-256-gcm'; +const BLOCK_SIZE_BYTES = 16; + +const encrypt = ( + text, + secret +) => { + const iv = crypto.randomBytes(BLOCK_SIZE_BYTES); + const cipher = crypto.createCipheriv(ALGORITHM, secret, iv); + + let ciphertext = cipher.update(text, 'utf8', 'base64'); + ciphertext += cipher.final('base64'); + return { + ciphertext, + iv: iv.toString('base64'), + tag: cipher.getAuthTag().toString('base64') + }; +} + +const decrypt = (ciphertext, iv, tag, secret) => { + const decipher = crypto.createDecipheriv( + ALGORITHM, + secret, + Buffer.from(iv, 'base64') + ); + decipher.setAuthTag(Buffer.from(tag, 'base64')); + + let cleartext = decipher.update(ciphertext, 'base64', 'utf8'); + cleartext += decipher.final('utf8'); + + return cleartext; +} + +const retrieveSecrets = async () => { + const API_KEY = 'your_api_key'; + const PSWD = 'your_pswd'; + const WORKSPACE_ID = 'your_workspace_id'; + + // 1. get (encrypted) private key + const user = await axios.get( + 'https://api.infisical.com/api/v2/users/me', { + headers: { + 'X-API-KEY': API_KEY + } + } + ); + + // 2. decrypt your (encrypted) private key with your password + const privateKey = decrypt({ + ciphertext: user.encryptedPrivateKey, + iv: user.iv, + tag: user.tag, + secret: PSWD.slice(0, 32).padStart(32, '0'); + }); + + // 3. get the (encrypted) project key for the project + const encryptedProjectKey = await axios.get( + `https://api.infisical.com/api/v2/workspace/${WORKSPACE_ID}`, { + headers: { + 'X-API-KEY': API_KEY + } + } + ); + + // 4. decrypt the project key with your private key + const projectKey = nacl.box.open( + util.decodeBase64(encryptedProjectKey), + util.decodeBase64(projectKey.nonce), + util.decodeBase64(projectKey.sender.publicKey), + util.decodeBase64(privateKey) + ); + + // 5. get (encrypted) secrets for a project and environment. + const encryptedSecrets = await axios.get( + 'https://api.infisical.com/api/v2/secrets', { + headers: { + 'X-API-KEY': API_KEY + } + } + ); + + // 6. decrypt the (encrypted) secrets + const secrets = encryptedSecrets.map((encryptedSecret) => { + const secretKey = decrypt({ + ciphertext: encryptedSecret.secretKeyCiphertext, + iv: encryptedSecret.secretKeyIV, + tag: encryptedSecret.secretKeyTag + secret: projectKey + }); + const secretValue = decrypt({ + ciphertext: encryptedSecret.secretValueCiphertext, + iv: encryptedSecret.secretValueIV, + tag: encryptedSecret.secretValueTag + secret: projectKey + }); + + return ({ + secretKey, + secretValue + }); + }); +} + +retrieveSecrets(); +``` + + + This example uses [TweetNaCl.js](https://tweetnacl.js.org/#/), a port of + TweetNacl/Nacl, to perform asymmeric decryption of the project key but there + are ports of NaCl available in every major language. + + + It can be useful to perform steps 1-4 ahead of time and store away your + private key (and even project key) for later use. The Infisical CLI works by + securely storing your private key via your OS keyring. + diff --git a/docs/api-reference/overview/examples/update-secrets.mdx b/docs/api-reference/overview/examples/update-secrets.mdx index b302710a3..b8566d9b3 100644 --- a/docs/api-reference/overview/examples/update-secrets.mdx +++ b/docs/api-reference/overview/examples/update-secrets.mdx @@ -2,9 +2,151 @@ title: "Update secrets" --- +In this example, we demonstrate how to update secrets + +Prerequisites: + +- Set up and add envars to [Infisical Cloud](https://app.infisical.com) +- Grasp a basic understanding of the system and its underlying cryptography [here](/api-reference/overview/introduction). + +## Flow + 1. Get your (encrypted) private key. 2. Decrypt your (encrypted) private key with your password. 3. Get the project key for the project. 4. Decrypt the project key with your private key. -5. Encrypt your secrets with the project key. -6. Send (encrypted) updated secrets to the Infical API +5. Encrypt your secret(s) with the project key. +6. Send (encrypted) updated secret(s) to the Infical API + +## Example + +```js +const crypto = require('crypto'); +const axios = require('axios'); + +const ALGORITHM = 'aes-256-gcm'; +const BLOCK_SIZE_BYTES = 16; + +const encrypt = ( + text, + secret +) => { + const iv = crypto.randomBytes(BLOCK_SIZE_BYTES); + const cipher = crypto.createCipheriv(ALGORITHM, secret, iv); + + let ciphertext = cipher.update(text, 'utf8', 'base64'); + ciphertext += cipher.final('base64'); + return { + ciphertext, + iv: iv.toString('base64'), + tag: cipher.getAuthTag().toString('base64') + }; +} + +const decrypt = (ciphertext, iv, tag, secret) => { + const decipher = crypto.createDecipheriv( + ALGORITHM, + secret, + Buffer.from(iv, 'base64') + ); + decipher.setAuthTag(Buffer.from(tag, 'base64')); + + let cleartext = decipher.update(ciphertext, 'base64', 'utf8'); + cleartext += decipher.final('utf8'); + + return cleartext; +} + +const updateSecrets = async () => { + const API_KEY = 'your_api_key'; + const PSWD = 'your_pswd'; + const WORKSPACE_ID = 'your_workspace_id'; + + const SECRET_ID = 'ID' // ID of secret to update + const SECRET_KEY = 'SOME_KEY'; + const SECRET_VALUE = 'SOME_VALUE'; + + // 1. get (encrypted) private key + const user = await axios.get( + 'https://api.infisical.com/api/v2/users/me', { + headers: { + 'X-API-KEY': API_KEY + } + } + ); + + // 2. decrypt your (encrypted) private key with your password + const privateKey = decrypt({ + ciphertext: user.encryptedPrivateKey, + iv: user.iv, + tag: user.tag, + secret: PSWD.slice(0, 32).padStart(32, '0'); + }); + + // 3. get the (encrypted) project key for the project + const encryptedProjectKey = await axios.get( + `https://api.infisical.com/api/v2/workspace/${WORKSPACE_ID}`, { + headers: { + 'X-API-KEY': API_KEY + } + } + ); + + // 4. decrypt the project key with your private key + const projectKey = nacl.box.open( + util.decodeBase64(encryptedProjectKey), + util.decodeBase64(projectKey.nonce), + util.decodeBase64(projectKey.sender.publicKey), + util.decodeBase64(privateKey) + ); + + // 5. encrypt your secret(s) with the project key + const { + ciphertext: secretKeyCiphertext, + iv: secretKeyIV, + tag: secretKeyTag + } = encrypt(SECRET_KEY, projectKey); + + const { + ciphertext: secretValueCiphertext, + iv: secretValueIV, + tag: secretValueTag + } = encrypt(SECRET_VALUE, projectKey); + + const secret = { + id: SECRET_ID, + secretKeyCiphertext, + secretKeyIV, + secretKeyTag, + secretValueCiphertext, + secretValueIV, + secretValueTag + } + + // 6. Send (encrypted) secret(s) to the Infisical API + await axios.patch( + `https://api.infisical.com/api/v2/secrets`, + { + secrets: secret + }, + { + headers: { + 'X-API-KEY': API_KEY + } + } + ); +} + +updateSecrets(); +``` + + + This example uses [TweetNaCl.js](https://tweetnacl.js.org/#/), a port of + TweetNacl/Nacl, to perform asymmeric decryption of the project key but there + are ports of NaCl available in every major language. + + + It can be useful to perform steps 1-4 ahead of time and store away your + private key (and even project key) for later use. The Infisical CLI works by + securely storing your private key via your OS keyring. + diff --git a/docs/api-reference/overview/introduction.mdx b/docs/api-reference/overview/introduction.mdx index f93ba91fe..7babef955 100644 --- a/docs/api-reference/overview/introduction.mdx +++ b/docs/api-reference/overview/introduction.mdx @@ -2,8 +2,13 @@ title: "Introduction" --- + + Infisical's REST API is currently unavailable and scheduled to go live on Jan + 16! + + Infisical's REST API provides users an alternative way to programmatically access and manage -secrets via HTTP requests. This can be useful for automating tasks, such as +secrets via HTTPS requests. This can be useful for automating tasks, such as rotating credentials, or for integrating secret management into a larger system. With the REST API, users can create, read, update, and delete secrets, as well as manage access control, query audit logs, and more. @@ -17,3 +22,10 @@ Using Infisical's API to manage secrets requires a basic understanding of the sy - Each project has an (encrypted) project key used to encrypt the secrets within that project; Infisical stores copies of the project key, for each member of that project, encrypted under each member's public key. - Secrets are encrypted symmetrically by your copy of the project key belonging to the project containing. - Infisical uses AES256-GCM and [TweetNaCl.js](https://tweetnacl.js.org/#/) for symmetric and asymmetric encryption/decryption operations. + + + Infisical's system ensures greater security such that secrets are + encrypted/decrypted on the client-side but requires users to properly + implement cryptographic operations to maintain end-to-end encryption (E2EE). + We're + diff --git a/docs/mint.json b/docs/mint.json index 39a3726d9..77d17c6c3 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -150,7 +150,8 @@ "pages": [ "api-reference/overview/examples/create-secrets", "api-reference/overview/examples/retrieve-secrets", - "api-reference/overview/examples/update-secrets" + "api-reference/overview/examples/update-secrets", + "api-reference/overview/examples/delete-secrets" ] } ] @@ -167,7 +168,13 @@ { "group": "Projects", "pages": [ - "api-reference/endpoints/workspaces/workspace-key" + "api-reference/endpoints/workspaces/memberships", + "api-reference/endpoints/workspaces/update-membership", + "api-reference/endpoints/workspaces/delete-membership", + "api-reference/endpoints/workspaces/workspace-key", + "api-reference/endpoints/workspaces/logs", + "api-reference/endpoints/workspaces/secret-snapshots", + "api-reference/endpoints/workspaces/rollback-snapshot" ] }, { @@ -176,7 +183,9 @@ "api-reference/endpoints/secrets/create", "api-reference/endpoints/secrets/read", "api-reference/endpoints/secrets/update", - "api-reference/endpoints/secrets/delete" + "api-reference/endpoints/secrets/delete", + "api-reference/endpoints/secrets/versions", + "api-reference/endpoints/secrets/rollback-version" ] } ] diff --git a/docs/spec.yaml b/docs/spec.yaml index dd62f167b..991682871 100644 --- a/docs/spec.yaml +++ b/docs/spec.yaml @@ -11,48 +11,81 @@ servers: paths: /api/v1/secret/{secretId}/secret-versions: get: - description: '' + summary: Return secret versions + description: Return secret versions parameters: - name: secretId in: path required: true schema: type: string + description: ID of secret - name: offset + description: Number of versions to skip + required: false in: query schema: type: string - name: limit + description: Maximum number of versions to return + required: false in: query schema: type: string responses: '200': description: OK + content: + application/json: + schema: + type: object + properties: + secretVersions: + type: array + items: + $ref: '#/components/schemas/SecretVersion' + description: Secret versions '400': description: Bad Request + security: + - apiKeyAuth: [] /api/v1/secret/{secretId}/secret-versions/rollback: post: - description: '' + summary: Roll back secret to a version. + description: Roll back secret to a version. parameters: - name: secretId in: path required: true schema: type: string + description: ID of secret responses: '200': description: OK + content: + application/json: + schema: + type: object + properties: + secret: + type: object + $ref: '#/components/schemas/Secret' + description: Secret rolled back to '400': description: Bad Request + security: + - apiKeyAuth: [] requestBody: + required: true content: application/json: schema: type: object properties: version: - example: any + type: integer + description: Version of secret to roll back to /api/v1/secret-snapshot/{secretSnapshotId}: get: description: '' @@ -69,26 +102,44 @@ paths: description: Bad Request /api/v1/workspace/{workspaceId}/secret-snapshots: get: - description: '' + summary: Return project secret snapshot ids + description: Return project secret snapshots ids parameters: - name: workspaceId in: path required: true schema: type: string + description: ID of project - name: offset + description: Number of secret snapshots to skip + required: false in: query schema: type: string - name: limit + description: Maximum number of secret snapshots to return + required: false in: query schema: type: string responses: '200': description: OK + content: + application/json: + schema: + type: object + properties: + secretSnapshots: + type: array + items: + $ref: '#/components/schemas/SecretSnapshot' + description: Project secret snapshots '400': description: Bad Request + security: + - apiKeyAuth: [] /api/v1/workspace/{workspaceId}/secret-snapshots/count: get: description: '' @@ -105,60 +156,107 @@ paths: description: Bad Request /api/v1/workspace/{workspaceId}/secret-snapshots/rollback: post: - description: '' + summary: >- + Roll back project secrets to those captured in a secret snapshot + version. + description: >- + Roll back project secrets to those captured in a secret snapshot + version. parameters: - name: workspaceId in: path required: true schema: type: string + description: ID of project responses: '200': description: OK + content: + application/json: + schema: + type: object + properties: + secrets: + type: array + items: + $ref: '#/components/schemas/Secret' + description: Secrets rolled back to '400': description: Bad Request + security: + - apiKeyAuth: [] requestBody: + required: true content: application/json: schema: type: object properties: version: - example: any + type: integer + description: Version of secret snapshot to roll back to /api/v1/workspace/{workspaceId}/logs: get: - description: '' + summary: Return project (audit) logs + description: Return project (audit) logs parameters: - name: workspaceId in: path required: true schema: type: string + description: ID of project + - name: userId + description: ID of project member + required: false + in: query + schema: + type: string - name: offset + description: Number of logs to skip + required: false in: query schema: type: string - name: limit + description: Maximum number of logs to return + required: false in: query schema: type: string - name: sortBy - in: query + description: Order to sort the logs by schema: type: string - - name: userId + enum: + - oldest + - recent + required: false in: query - schema: - type: string - name: actionNames + description: Names of log actions (comma-separated) + required: false in: query schema: type: string responses: '200': description: OK + content: + application/json: + schema: + type: object + properties: + logs: + type: array + items: + $ref: '#/components/schemas/Log' + description: Project logs '400': description: Bad Request + security: + - apiKeyAuth: [] /api/v1/action/{actionId}: get: description: '' @@ -1285,8 +1383,11 @@ paths: application/json: schema: type: object - $ref: '#/components/schemas/CurrentUser' - description: Current user on request + properties: + user: + type: object + $ref: '#/components/schemas/CurrentUser' + description: Current user on request '400': description: Bad Request security: @@ -1379,6 +1480,107 @@ paths: description: OK '400': description: Bad Request + /api/v2/workspace/{workspaceId}/memberships: + get: + summary: Return project memberships + description: Return project memberships + parameters: + - name: workspaceId + in: path + required: true + schema: + type: string + description: ID of project + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + memberships: + type: array + items: + $ref: '#/components/schemas/Membership' + description: Memberships of project + '400': + description: Bad Request + security: + - apiKeyAuth: [] + /api/v2/workspace/{workspaceId}/memberships/{membershipId}: + delete: + summary: Delete project membership + description: Delete project membership + parameters: + - name: workspaceId + in: path + required: true + schema: + type: string + description: ID of project + - name: membershipId + in: path + required: true + schema: + type: string + description: ID of membership + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + membership: + $ref: '#/components/schemas/Membership' + description: Deleted membership + '400': + description: Bad Request + security: + - apiKeyAuth: [] + patch: + summary: Update project membership + description: Update project membership + parameters: + - name: workspaceId + in: path + required: true + schema: + type: string + description: ID of project + - name: membershipId + in: path + required: true + schema: + type: string + description: ID of membership + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + membership: + $ref: '#/components/schemas/Membership' + description: Updated membership + '400': + description: Bad Request + security: + - apiKeyAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + role: + type: string + description: Role of membership - either admin or member /api/v2/secret/batch-create/workspace/{workspaceId}/environment/{environment}: post: description: '' @@ -1554,12 +1756,15 @@ paths: content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/Secret' - description: >- - Array of newly-created secrets for the given project and - environment + type: object + properties: + secrets: + type: array + items: + $ref: '#/components/schemas/Secret' + description: >- + Newly-created secrets for the given project and + environment security: - apiKeyAuth: [] requestBody: @@ -1581,32 +1786,38 @@ paths: get: summary: Read secrets description: Read secrets from a project and environment - parameters: [] + parameters: + - name: workspaceId + description: ID of project + required: true + in: query + schema: + type: string + - name: environment + description: Environment within project + required: true + in: query + schema: + type: string + - name: content + in: query + schema: + type: string responses: '200': description: OK content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/Secret' - description: Array of secrets for the given project and environment + type: object + properties: + secrets: + type: array + items: + $ref: '#/components/schemas/Secret' + description: Secrets for the given project and environment security: - apiKeyAuth: [] - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - workspaceId: - type: string - description: ID of project - environment: - type: string - description: Environment within project patch: summary: Update secret(s) description: Update secret(s) @@ -1617,12 +1828,13 @@ paths: content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/Secret' - description: >- - Array of newly-updated secrets for the given project and - environment + type: object + properties: + secrets: + type: array + items: + $ref: '#/components/schemas/Secret' + description: Updated secrets security: - apiKeyAuth: [] requestBody: @@ -1645,10 +1857,13 @@ paths: content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/Secret' - description: Array of deleted secrets + type: object + properties: + secrets: + type: array + items: + $ref: '#/components/schemas/Secret' + description: Deleted secrets security: - apiKeyAuth: [] requestBody: @@ -1783,12 +1998,51 @@ components: encryptedPrivateKey: type: string example: '' + iv: + type: string + example: '' + tag: + type: string + example: '' updatedAt: type: string example: '' createdAt: type: string example: '' + Membership: + type: object + properties: + user: + type: object + properties: + _id: + type: string + example: '' + email: + type: string + example: '' + firstName: + type: string + example: '' + lastName: + type: string + example: '' + publicKey: + type: string + example: '' + updatedAt: + type: string + example: '' + createdAt: + type: string + example: '' + workspace: + type: string + example: '' + role: + type: string + example: admin ProjectKey: type: object properties: @@ -1925,6 +2179,135 @@ components: createdAt: type: string example: '' + Log: + type: object + properties: + _id: + type: string + example: '' + user: + type: object + properties: + _id: + type: string + example: '' + email: + type: string + example: '' + firstName: + type: string + example: '' + lastName: + type: string + example: '' + workspace: + type: string + example: '' + actionNames: + type: array + example: + - addSecrets + items: + type: string + actions: + type: array + items: + type: object + properties: + name: + type: string + example: addSecrets + user: + type: string + example: '' + workspace: + type: string + example: '' + payload: + type: array + items: + type: object + properties: + oldSecretVersion: + type: string + example: '' + newSecretVersion: + type: string + example: '' + channel: + type: string + example: cli + ipAddress: + type: string + example: 192.168.0.1 + updatedAt: + type: string + example: '' + createdAt: + type: string + example: '' + SecretSnapshot: + type: object + properties: + workspace: + type: string + example: '' + version: + type: number + example: 1 + secretVersions: + type: array + items: + type: object + properties: + _id: + type: string + example: '' + SecretVersion: + type: object + properties: + _id: + type: string + example: '' + secret: + type: string + example: '' + version: + type: number + example: 1 + workspace: + type: string + example: '' + type: + type: string + example: '' + user: + type: string + example: '' + environment: + type: string + example: '' + isDeleted: + type: string + example: '' + secretKeyCiphertext: + type: string + example: '' + secretKeyIV: + type: string + example: '' + secretKeyTag: + type: string + example: '' + secretValueCiphertext: + type: string + example: '' + secretValueIV: + type: string + example: '' + secretValueTag: + type: string + example: '' securitySchemes: bearerAuth: type: http From 5085376f11f8f52ddb16af33c0c85f22770be600 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Sat, 14 Jan 2023 21:07:17 +0700 Subject: [PATCH 65/65] Check v2 workspace membership routes (currently not routed) --- backend/src/controllers/v2/workspaceController.ts | 2 +- backend/src/middleware/requireMembershipAuth.ts | 3 ++- backend/src/routes/v2/workspace.ts | 2 +- frontend/components/utilities/attemptLogin.ts | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/backend/src/controllers/v2/workspaceController.ts b/backend/src/controllers/v2/workspaceController.ts index 3f929ee4a..39a1be5fd 100644 --- a/backend/src/controllers/v2/workspaceController.ts +++ b/backend/src/controllers/v2/workspaceController.ts @@ -302,7 +302,7 @@ export const getWorkspaceMemberships = async (req: Request, res: Response) => { Sentry.setUser({ email: req.user.email }); Sentry.captureException(err); return res.status(400).send({ - message: 'Failed to get workspace members' + message: 'Failed to get workspace memberships' }); } diff --git a/backend/src/middleware/requireMembershipAuth.ts b/backend/src/middleware/requireMembershipAuth.ts index b44d25ceb..06c0980a3 100644 --- a/backend/src/middleware/requireMembershipAuth.ts +++ b/backend/src/middleware/requireMembershipAuth.ts @@ -45,7 +45,8 @@ const requireMembershipAuth = ({ }); req.targetMembership = targetMembership; - + + return next(); } catch (err) { return next(UnauthorizedRequestError({ message: 'Unable to validate workspace membership' diff --git a/backend/src/routes/v2/workspace.ts b/backend/src/routes/v2/workspace.ts index ebbfe0d4c..ca920e15a 100644 --- a/backend/src/routes/v2/workspace.ts +++ b/backend/src/routes/v2/workspace.ts @@ -68,7 +68,7 @@ router.get( workspaceController.getWorkspaceServiceTokenData ); -// TODO: /POST to create membership +// TODO: /POST to create membership and re-route inviting user to workspace there router.get( // new - TODO: rewire dashboard to this route '/:workspaceId/memberships', diff --git a/frontend/components/utilities/attemptLogin.ts b/frontend/components/utilities/attemptLogin.ts index f1aa03ffb..1a960af10 100644 --- a/frontend/components/utilities/attemptLogin.ts +++ b/frontend/components/utilities/attemptLogin.ts @@ -61,7 +61,7 @@ const attemptLogin = async ( // if everything works, go the main dashboard page. const { token, publicKey, encryptedPrivateKey, iv, tag } = await login2(email, clientProof); - + SecurityClient.setToken(token); const privateKey = Aes256Gcm.decrypt({