diff --git a/.gitignore b/.gitignore index 6c4414313..b8341e81b 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,8 @@ node_modules .DS_Store /dist +/completions/ +/manpages/ # frontend diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 0b348b71a..51154a4f3 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -6,6 +6,11 @@ # - cd cli && go mod tidy # # you may remove this if you don't need go generate # - cd cli && go generate ./... +before: + hooks: + - ./cli/scripts/completions.sh + - ./cli/scripts/manpages.sh + builds: - id: darwin-build binary: infisical @@ -44,6 +49,16 @@ builds: goarch: "386" dir: ./cli +archives: + - format_overrides: + - goos: windows + format: zip + files: + - README* + - LICENSE* + - manpages/* + - completions/* + release: replace_existing_draft: true mode: 'replace' @@ -92,6 +107,15 @@ nfpms: - apk - archlinux bindir: /usr/bin + contents: + - src: ./completions/infisical.bash + dst: /etc/bash_completion.d/infisical + - src: ./completions/infisical.fish + dst: /usr/share/fish/vendor_completions.d/infisical.fish + - src: ./completions/infisical.zsh + dst: /usr/share/zsh/site-functions/_infisical + - src: ./manpages/infisical.1.gz + dst: /usr/share/man/man1/infisical.1.gz scoop: bucket: owner: Infisical @@ -117,7 +141,15 @@ aurs: install -Dm755 "./infisical" "${pkgdir}/usr/bin/infisical" # license install -Dm644 "./LICENSE" "${pkgdir}/usr/share/licenses/infisical/LICENSE" - + # completions + mkdir -p "${pkgdir}/usr/share/bash-completion/completions/" + mkdir -p "${pkgdir}/usr/share/zsh/site-functions/" + mkdir -p "${pkgdir}/usr/share/fish/vendor_completions.d/" + install -Dm644 "./completions/infisical.bash" "${pkgdir}/usr/share/bash-completion/completions/infisical" + install -Dm644 "./completions/infisical.zsh" "${pkgdir}/usr/share/zsh/site-functions/infisical" + install -Dm644 "./completions/infisical.fish" "${pkgdir}/usr/share/fish/vendor_completions.d/infisical.fish" + # man pages + install -Dm644 "./manpages/infisical.1.gz" "${pkgdir}/usr/share/man/man1/infisical.1.gz" # dockers: # - dockerfile: goreleaser.dockerfile # goos: linux diff --git a/README.md b/README.md index 292f5fe94..b3bcd7ac6 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ - **[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/Shared** scoping for environment variables -- **[Integrations](https://infisical.com/docs/integrations/overview)** with CI/CD and production infrastructure (Heroku available, more coming soon) +- **[Integrations](https://infisical.com/docs/integrations/overview)** with CI/CD and production infrastructure - ๐Ÿ”œ **1-Click Deploy** to Digital Ocean and Heroku - ๐Ÿ”œ **Authentication/Authorization** for projects (read/write controls soon) - ๐Ÿ”œ **Automatic Secret Rotation** @@ -270,13 +270,13 @@ We're currently setting the foundation and building [integrations](https://infis - - โœ”๏ธ Ruby on Rails + + โœ”๏ธ Vue - - โœ”๏ธ Vue + + โœ”๏ธ Ruby on Rails @@ -292,6 +292,16 @@ We're currently setting the foundation and building [integrations](https://infis + + + + โœ”๏ธ .NET + + + + And more... + + @@ -321,4 +331,10 @@ Infisical officially launched as v.1.0 on November 21st, 2022. However, a lot of - + + +## ๐ŸŒŽ Translations + +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 diff --git a/backend/src/controllers/v1/membershipOrgController.ts b/backend/src/controllers/v1/membershipOrgController.ts index 400147a16..5628cda1a 100644 --- a/backend/src/controllers/v1/membershipOrgController.ts +++ b/backend/src/controllers/v1/membershipOrgController.ts @@ -218,12 +218,6 @@ export const verifyUserToOrganization = async (req: Request, res: Response) => { const { email, code } = req.body; user = await User.findOne({ email }).select('+publicKey'); - if (user && user?.publicKey) { - // case: user has already completed account - return res.status(403).send({ - error: 'Failed email magic link verification for complete account' - }); - } const membershipOrg = await MembershipOrg.findOne({ inviteEmail: email, @@ -238,6 +232,18 @@ export const verifyUserToOrganization = async (req: Request, res: Response) => { code }); + if (user && user?.publicKey) { + // case: user has already completed account + // membership can be approved and redirected to login/dashboard + membershipOrg.status = ACCEPTED; + await membershipOrg.save(); + + return res.status(200).send({ + message: 'Successfully verified email', + user, + }); + } + if (!user) { // initialize user account user = await new User({ diff --git a/backend/src/helpers/membership.ts b/backend/src/helpers/membership.ts index b06460cde..b237803f1 100644 --- a/backend/src/helpers/membership.ts +++ b/backend/src/helpers/membership.ts @@ -26,7 +26,7 @@ const validateMembership = async ({ membership = await Membership.findOne({ user: userId, workspace: workspaceId - }); + }).populate("workspace"); if (!membership) throw new Error('Failed to find membership'); diff --git a/cli/go.mod b/cli/go.mod index 86cc1763f..c48e3e2f9 100644 --- a/cli/go.mod +++ b/cli/go.mod @@ -4,6 +4,8 @@ go 1.19 require ( github.com/99designs/keyring v1.2.2 + github.com/muesli/mango-cobra v1.2.0 + github.com/muesli/roff v0.1.0 github.com/spf13/cobra v1.6.1 golang.org/x/crypto v0.3.0 golang.org/x/term v0.3.0 @@ -22,6 +24,8 @@ require ( github.com/mattn/go-runewidth v0.0.14 // indirect github.com/mitchellh/mapstructure v1.3.3 // indirect github.com/mtibben/percent v0.2.1 // indirect + github.com/muesli/mango v0.1.0 // indirect + github.com/muesli/mango-pflag v0.1.0 // indirect github.com/oklog/ulid v1.3.1 // indirect github.com/rivo/uniseg v0.2.0 // indirect go.mongodb.org/mongo-driver v1.10.0 // indirect diff --git a/cli/go.sum b/cli/go.sum index 3419b8051..d169d7b89 100644 --- a/cli/go.sum +++ b/cli/go.sum @@ -56,6 +56,14 @@ github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs= github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns= +github.com/muesli/mango v0.1.0 h1:DZQK45d2gGbql1arsYA4vfg4d7I9Hfx5rX/GCmzsAvI= +github.com/muesli/mango v0.1.0/go.mod h1:5XFpbC8jY5UUv89YQciiXNlbi+iJgt29VDC5xbzrLL4= +github.com/muesli/mango-cobra v1.2.0 h1:DQvjzAM0PMZr85Iv9LIMaYISpTOliMEg+uMFtNbYvWg= +github.com/muesli/mango-cobra v1.2.0/go.mod h1:vMJL54QytZAJhCT13LPVDfkvCUJ5/4jNUKF/8NC2UjA= +github.com/muesli/mango-pflag v0.1.0 h1:UADqbYgpUyRoBja3g6LUL+3LErjpsOwaC9ywvBWe7Sg= +github.com/muesli/mango-pflag v0.1.0/go.mod h1:YEQomTxaCUp8PrbhFh10UfbhbQrM/xJ4i2PB8VTLLW0= +github.com/muesli/roff v0.1.0 h1:YD0lalCotmYuF5HhZliKWlIx7IEhiXeSfq7hNjFqGF8= +github.com/muesli/roff v0.1.0/go.mod h1:pjAHQM9hdUUwm/krAfrLGgJkXJ+YuhtsfZ42kieB2Ig= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= diff --git a/cli/packages/cmd/man.go b/cli/packages/cmd/man.go new file mode 100644 index 000000000..efe8686b3 --- /dev/null +++ b/cli/packages/cmd/man.go @@ -0,0 +1,35 @@ +/* +Copyright ยฉ 2022 NAME HERE +*/ +package cmd + +import ( + "fmt" + "os" + + mcobra "github.com/muesli/mango-cobra" + "github.com/muesli/roff" + "github.com/spf13/cobra" +) + +var manCmd = &cobra.Command{ + Use: "man", + Short: "generates the manpages", + SilenceUsage: true, + DisableFlagsInUseLine: true, + Hidden: true, + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + manPage, err := mcobra.NewManPage(1, rootCmd) + if err != nil { + return err + } + + _, err = fmt.Fprint(os.Stdout, manPage.Build(roff.NewDocument())) + return err + }, +} + +func init() { + rootCmd.AddCommand(manCmd) +} diff --git a/cli/packages/cmd/root.go b/cli/packages/cmd/root.go index 173df36df..f09f08800 100644 --- a/cli/packages/cmd/root.go +++ b/cli/packages/cmd/root.go @@ -14,7 +14,7 @@ var rootCmd = &cobra.Command{ Use: "infisical", 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{DisableDefaultCmd: true}, + CompletionOptions: cobra.CompletionOptions{HiddenDefaultCmd: true}, Version: "0.1.16", } diff --git a/cli/scripts/completions.sh b/cli/scripts/completions.sh new file mode 100755 index 000000000..6e69a1508 --- /dev/null +++ b/cli/scripts/completions.sh @@ -0,0 +1,8 @@ +#!/bin/sh +set -e +rm -rf completions +mkdir completions +cd cli +for sh in bash zsh fish; do + go run . completion "$sh" > "../completions/infisical.$sh" +done \ No newline at end of file diff --git a/cli/scripts/manpages.sh b/cli/scripts/manpages.sh new file mode 100755 index 000000000..db7e5c1b5 --- /dev/null +++ b/cli/scripts/manpages.sh @@ -0,0 +1,6 @@ +#!/bin/sh +set -e +rm -rf manpages +mkdir manpages +cd cli +go run . man | gzip -c > "../manpages/infisical.1.gz" \ No newline at end of file diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 15a200783..6cf75ad47 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -48,6 +48,8 @@ services: - ./frontend/public:/app/public - ./frontend/styles:/app/styles - ./frontend/components:/app/components + - ./frontend/locales:/app/locales + - ./frontend/next-i18next.config.js:/app/next-i18next.config.js env_file: .env environment: - NEXT_PUBLIC_ENV=development diff --git a/docs/integrations/frameworks/dotnet.mdx b/docs/integrations/frameworks/dotnet.mdx new file mode 100644 index 000000000..7a1d358c1 --- /dev/null +++ b/docs/integrations/frameworks/dotnet.mdx @@ -0,0 +1,27 @@ +--- +title: ".NET" +--- + +Prerequisites: + +- Set up and add envars to [Infisical Cloud](https://app.infisical.com) +- [Install the CLI](/cli/overview) + +## Initialize Infisical for your [.NET](https://dotnet.microsoft.com) app + +```bash +# navigate to the root of your of your project +cd /path/to/project + +# then initialize infisical +infisical init +``` + +## Start your application as usual but with Infisical + +```bash +infisical run -- + +# Example +infisical run -- dotnet run +``` diff --git a/docs/mint.json b/docs/mint.json index cbac56d5a..e94b70a6b 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -166,7 +166,8 @@ "integrations/frameworks/django", "integrations/frameworks/flask", "integrations/frameworks/laravel", - "integrations/frameworks/rails" + "integrations/frameworks/rails", + "integrations/frameworks/dotnet" ] }, { diff --git a/frontend/.gitignore b/frontend/.gitignore index 83e774c8c..5edd5a7fa 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -32,3 +32,5 @@ yarn-error.log* .env.production.local .vercel .env.infisical + +.vscode \ No newline at end of file diff --git a/frontend/components/RouteGuard.js b/frontend/components/RouteGuard.js index d08972b99..c5f6feb35 100644 --- a/frontend/components/RouteGuard.js +++ b/frontend/components/RouteGuard.js @@ -48,6 +48,7 @@ export default function RouteGuard({ children }) { // Check if the user is authenticated const response = await checkAuth(); // #TODO: figure our why sometimes it doesn't output a response + // ANS(akhilmhdh): Because inside the security client the await token() doesn't have try/catch if (!publicPaths.includes(path)) { try { if (response.status !== 200) { diff --git a/frontend/components/basic/Layout.tsx b/frontend/components/basic/Layout.tsx index bf3b8bf51..a53de0b41 100644 --- a/frontend/components/basic/Layout.tsx +++ b/frontend/components/basic/Layout.tsx @@ -1,8 +1,9 @@ /* eslint-disable no-unexpected-multiline */ /* eslint-disable react-hooks/exhaustive-deps */ -import { useEffect, useState } from 'react'; -import Link from 'next/link'; -import { useRouter } from 'next/router'; +import { useEffect, useMemo, useState } from "react"; +import Link from "next/link"; +import { useRouter } from "next/router"; +import { useTranslation } from "next-i18next"; import { faBookOpen, faGear, @@ -10,30 +11,30 @@ import { faMobile, faPlug, faTimeline, - faUser -} from '@fortawesome/free-solid-svg-icons'; -import { faPlus } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; + faUser, +} from "@fortawesome/free-solid-svg-icons"; +import { faPlus } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import getOrganizations from '~/pages/api/organization/getOrgs'; -import getOrganizationUserProjects from '~/pages/api/organization/GetOrgUserProjects'; -import getOrganizationUsers from '~/pages/api/organization/GetOrgUsers'; -import checkUserAction from '~/pages/api/userActions/checkUserAction'; -import addUserToWorkspace from '~/pages/api/workspace/addUserToWorkspace'; -import createWorkspace from '~/pages/api/workspace/createWorkspace'; -import getWorkspaces from '~/pages/api/workspace/getWorkspaces'; -import uploadKeys from '~/pages/api/workspace/uploadKeys'; +import getOrganizations from "~/pages/api/organization/getOrgs"; +import getOrganizationUserProjects from "~/pages/api/organization/GetOrgUserProjects"; +import getOrganizationUsers from "~/pages/api/organization/GetOrgUsers"; +import checkUserAction from "~/pages/api/userActions/checkUserAction"; +import addUserToWorkspace from "~/pages/api/workspace/addUserToWorkspace"; +import createWorkspace from "~/pages/api/workspace/createWorkspace"; +import getWorkspaces from "~/pages/api/workspace/getWorkspaces"; +import uploadKeys from "~/pages/api/workspace/uploadKeys"; -import NavBarDashboard from '../navigation/NavBarDashboard'; -import onboardingCheck from '../utilities/checks/OnboardingCheck'; -import { tempLocalStorage } from '../utilities/checks/tempLocalStorage'; +import NavBarDashboard from "../navigation/NavBarDashboard"; +import onboardingCheck from "../utilities/checks/OnboardingCheck"; +import { tempLocalStorage } from "../utilities/checks/tempLocalStorage"; import { decryptAssymmetric, - encryptAssymmetric -} from '../utilities/cryptography/crypto'; -import Button from './buttons/Button'; -import AddWorkspaceDialog from './dialog/AddWorkspaceDialog'; -import Listbox from './Listbox'; + encryptAssymmetric, +} from "../utilities/cryptography/crypto"; +import Button from "./buttons/Button"; +import AddWorkspaceDialog from "./dialog/AddWorkspaceDialog"; +import Listbox from "./Listbox"; interface LayoutProps { children: React.ReactNode; @@ -42,15 +43,17 @@ interface LayoutProps { export default function Layout({ children }: LayoutProps) { const router = useRouter(); const [workspaceList, setWorkspaceList] = useState([]); - const [workspaceMapping, setWorkspaceMapping] = useState([{ '1': '2' }]); - const [workspaceSelected, setWorkspaceSelected] = useState('โˆž'); - const [newWorkspaceName, setNewWorkspaceName] = useState(''); + const [workspaceMapping, setWorkspaceMapping] = useState([{ "1": "2" }]); + const [workspaceSelected, setWorkspaceSelected] = useState("โˆž"); + const [newWorkspaceName, setNewWorkspaceName] = useState(""); const [isOpen, setIsOpen] = useState(false); const [loading, setLoading] = useState(false); const [error, setError] = useState(false); const [totalOnboardingActionsDone, setTotalOnboardingActionsDone] = useState(0); + const { t } = useTranslation(); + function closeModal() { setIsOpen(false); } @@ -76,35 +79,35 @@ export default function Layout({ children }: LayoutProps) { if (!currentWorkspaces.includes(workspaceName)) { const newWorkspace = await createWorkspace({ workspaceName, - organizationId: tempLocalStorage('orgData.id') + organizationId: tempLocalStorage("orgData.id"), }); const newWorkspaceId = newWorkspace._id; if (addAllUsers) { const orgUsers = await getOrganizationUsers({ - orgId: tempLocalStorage('orgData.id') + orgId: tempLocalStorage("orgData.id"), }); orgUsers.map(async (user: any) => { - if (user.status == 'accepted') { + if (user.status == "accepted") { const result = await addUserToWorkspace( user.user.email, newWorkspaceId ); if (result?.invitee && result?.latestKey) { - const PRIVATE_KEY = tempLocalStorage('PRIVATE_KEY'); + const PRIVATE_KEY = tempLocalStorage("PRIVATE_KEY"); // assymmetrically decrypt symmetric key with local private key const key = decryptAssymmetric({ ciphertext: result.latestKey.encryptedKey, nonce: result.latestKey.nonce, publicKey: result.latestKey.sender.publicKey, - privateKey: PRIVATE_KEY + privateKey: PRIVATE_KEY, }); const { ciphertext, nonce } = encryptAssymmetric({ plaintext: key, publicKey: result.invitee.publicKey, - privateKey: PRIVATE_KEY + privateKey: PRIVATE_KEY, }) as { ciphertext: string; nonce: string }; uploadKeys( @@ -117,11 +120,11 @@ export default function Layout({ children }: LayoutProps) { } }); } - router.push('/dashboard/' + newWorkspaceId + '?Development'); + router.push("/dashboard/" + newWorkspaceId + "?Development"); setIsOpen(false); - setNewWorkspaceName(''); + setNewWorkspaceName(""); } else { - console.error('A project with this name already exists.'); + console.error("A project with this name already exists."); setError(true); setLoading(false); } @@ -132,67 +135,70 @@ export default function Layout({ children }: LayoutProps) { } } - const menuItems = [ - { - href: - '/dashboard/' + - workspaceMapping[workspaceSelected as any] + - '?Development', - title: 'Secrets', - emoji: - }, - { - href: '/users/' + workspaceMapping[workspaceSelected as any], - title: 'Members', - emoji: - }, - { - href: '/integrations/' + workspaceMapping[workspaceSelected as any], - title: 'Integrations', - emoji: - }, - { - href: '/activity/' + workspaceMapping[workspaceSelected as any], - title: 'Activity Logs', - emoji: - }, - { - href: '/settings/project/' + workspaceMapping[workspaceSelected as any], - title: 'Project Settings', - emoji: - } - ]; + const menuItems = useMemo( + () => [ + { + href: + "/dashboard/" + + workspaceMapping[workspaceSelected as any] + + "?Development", + title: t("nav:menu.secrets"), + emoji: , + }, + { + href: "/users/" + workspaceMapping[workspaceSelected as any], + title: t("nav:menu.members"), + emoji: , + }, + { + href: "/integrations/" + workspaceMapping[workspaceSelected as any], + title: t("nav:menu.integrations"), + emoji: , + }, + { + href: '/activity/' + workspaceMapping[workspaceSelected as any], + title: 'Activity Logs', + emoji: + }, + { + href: "/settings/project/" + workspaceMapping[workspaceSelected as any], + title: t("nav:menu.project-settings"), + emoji: , + }, + ], + [t, workspaceMapping, workspaceSelected] + ); useEffect(() => { // Put a user in a workspace if they're not in one yet const putUserInWorkSpace = async () => { - if (tempLocalStorage('orgData.id') === '') { + if (tempLocalStorage("orgData.id") === "") { const userOrgs = await getOrganizations(); - localStorage.setItem('orgData.id', userOrgs[0]._id); + localStorage.setItem("orgData.id", userOrgs[0]._id); } const orgUserProjects = await getOrganizationUserProjects({ - orgId: tempLocalStorage('orgData.id') + orgId: tempLocalStorage("orgData.id"), }); const userWorkspaces = orgUserProjects; if ( userWorkspaces.length == 0 && - router.asPath != '/noprojects' && - !router.asPath.includes('settings') + router.asPath != "/noprojects" && + !router.asPath.includes("settings") ) { - router.push('/noprojects'); - } else if (router.asPath != '/noprojects') { + router.push("/noprojects"); + } else if (router.asPath != "/noprojects") { const intendedWorkspaceId = router.asPath - .split('/') - [router.asPath.split('/').length - 1].split('?')[0]; + .split("/") + [router.asPath.split("/").length - 1].split("?")[0]; // If a user is not a member of a workspace they are trying to access, just push them to one of theirs if ( - intendedWorkspaceId != 'heroku' && + intendedWorkspaceId != "heroku" && !userWorkspaces .map((workspace: { _id: string }) => workspace._id) .includes(intendedWorkspaceId) ) { - router.push('/dashboard/' + userWorkspaces[0]._id + '?Development'); + router.push("/dashboard/" + userWorkspaces[0]._id + "?Development"); } else { setWorkspaceList( userWorkspaces.map((workspace: any) => workspace.name) @@ -201,7 +207,7 @@ export default function Layout({ children }: LayoutProps) { Object.fromEntries( userWorkspaces.map((workspace: any) => [ workspace.name, - workspace._id + workspace._id, ]) ) as any ); @@ -209,12 +215,12 @@ export default function Layout({ children }: LayoutProps) { Object.fromEntries( userWorkspaces.map((workspace: any) => [ workspace._id, - workspace.name + workspace.name, ]) )[ router.asPath - .split('/') - [router.asPath.split('/').length - 1].split('?')[0] + .split("/") + [router.asPath.split("/").length - 1].split("?")[0] ] ); } @@ -230,16 +236,16 @@ export default function Layout({ children }: LayoutProps) { workspaceMapping[workspaceSelected as any] && `${workspaceMapping[workspaceSelected as any]}` !== router.asPath - .split('/') - [router.asPath.split('/').length - 1].split('?')[0] + .split("/") + [router.asPath.split("/").length - 1].split("?")[0] ) { router.push( - '/dashboard/' + + "/dashboard/" + workspaceMapping[workspaceSelected as any] + - '?Development' + "?Development" ); localStorage.setItem( - 'projectData.id', + "projectData.id", `${workspaceMapping[workspaceSelected as any]}` ); } @@ -263,7 +269,7 @@ export default function Layout({ children }: LayoutProps) {
- PROJECT + {t("nav:menu.project")}
{workspaceList.length > 0 ? ( 0 && menuItems.map(({ href, title, emoji }) => (
  • - {router.asPath.split('/')[1] === href.split('/')[1] && - (['project', 'billing', 'org', 'personal'].includes( - router.asPath.split('/')[2] + {router.asPath.split("/")[1] === href.split("/")[1] && + (["project", "billing", "org", "personal"].includes( + router.asPath.split("/")[2] ) - ? router.asPath.split('/')[2] === href.split('/')[2] + ? router.asPath.split("/")[2] === href.split("/")[2] : true) ? (
    {title}
    - ) : router.asPath == '/noprojects' ? ( + ) : router.asPath == "/noprojects" ? (
    @@ -329,7 +335,7 @@ export default function Layout({ children }: LayoutProps) {
    - {router.asPath.split('/')[1] === 'home' ? ( + {router.asPath.split("/")[1] === "home" ? (
    @@ -340,12 +346,12 @@ export default function Layout({ children }: LayoutProps) { Infisical Guide progress bar

    - {' '} - To use Infisical, please log in through a device with larger - dimensions.{' '} + {` ${t("common:no-mobile")} `}

    diff --git a/frontend/components/basic/Toggle.tsx b/frontend/components/basic/Toggle.tsx index 70e32426e..d15aed622 100644 --- a/frontend/components/basic/Toggle.tsx +++ b/frontend/components/basic/Toggle.tsx @@ -64,7 +64,6 @@ export default function Toggle ({ id ]) } else { - setSharedToHide(sharedToHide!.filter(tempId => tempId != id)) deleteOverride(id); } setEnabled(!enabled); diff --git a/frontend/components/basic/dialog/ActivateBotDialog.js b/frontend/components/basic/dialog/ActivateBotDialog.js index 79d8cd693..7b362867f 100644 --- a/frontend/components/basic/dialog/ActivateBotDialog.js +++ b/frontend/components/basic/dialog/ActivateBotDialog.js @@ -1,12 +1,7 @@ import { Fragment } from "react"; +import { useTranslation } from "next-i18next"; import { Dialog, Transition } from "@headlessui/react"; -import setBotActiveStatus from "../../../pages/api/bot/setBotActiveStatus"; -import getLatestFileKey from "../../../pages/api/workspace/getLatestFileKey"; -import { - decryptAssymmetric, - encryptAssymmetric -} from "../../utilities/cryptography/crypto"; import Button from "../buttons/Button"; const ActivateBotDialog = ({ @@ -16,6 +11,7 @@ const ActivateBotDialog = ({ handleBotActivate, handleIntegrationOption }) => { + const { t } = useTranslation(); const submit = async () => { try { @@ -64,18 +60,18 @@ const ActivateBotDialog = ({ as="h3" className="text-lg font-medium leading-6 text-gray-400" > - Grant Infisical access to your secrets + {t("integrations:grant-access-to-secrets")}

    - Most cloud integrations require Infisical to be able to decrypt your secrets so they can be forwarded over. + {t("integrations:why-infisical-needs-access")}

    diff --git a/frontend/components/basic/dialog/AddIncidentContactDialog.js b/frontend/components/basic/dialog/AddIncidentContactDialog.js index fad13cdee..535004045 100644 --- a/frontend/components/basic/dialog/AddIncidentContactDialog.js +++ b/frontend/components/basic/dialog/AddIncidentContactDialog.js @@ -1,4 +1,5 @@ import { Fragment, useState } from "react"; +import { useTranslation } from "next-i18next"; import { Dialog, Transition } from "@headlessui/react"; import addIncidentContact from "~/pages/api/organization/addIncidentContact"; @@ -14,6 +15,7 @@ const AddIncidentContactDialog = ({ setIncidentContacts, }) => { let [incidentContactEmail, setIncidentContactEmail] = useState(""); + const { t } = useTranslation(); const submit = () => { setIncidentContacts( @@ -59,17 +61,16 @@ const AddIncidentContactDialog = ({ as="h3" className="text-lg font-medium leading-6 text-gray-400" > - Add an Incident Contact + {t("section-incident:add-dialog.title")}

    - This contact will be notified in the unlikely event of a - severe incident. + {t("section-incident:add-dialog.description")}

    diff --git a/frontend/components/basic/dialog/AddProjectMemberDialog.js b/frontend/components/basic/dialog/AddProjectMemberDialog.js index 0d18bc3ca..e8dd610de 100644 --- a/frontend/components/basic/dialog/AddProjectMemberDialog.js +++ b/frontend/components/basic/dialog/AddProjectMemberDialog.js @@ -1,5 +1,6 @@ import { Fragment, useState } from "react"; import { useRouter } from "next/router"; +import { Trans, useTranslation } from "next-i18next"; import { Dialog, Transition } from "@headlessui/react"; import Button from "../buttons/Button"; @@ -15,6 +16,7 @@ const AddProjectMemberDialog = ({ setEmail, }) => { const router = useRouter(); + const { t } = useTranslation(); return (
    @@ -49,48 +51,55 @@ const AddProjectMemberDialog = ({ as="h3" className="text-lg font-medium leading-6 text-gray-400 z-50" > - Add a member to your project + {t("section-members:add-dialog.add-member-to-project")} ) : ( - All the users in your organization are already invited. + {t("section-members:add-dialog.already-all-invited")} )}
    {data?.length > 0 ? (

    - The user will receive an email with the instructions. + {t("section-members:add-dialog.user-will-email")}

    - - + + router.push( + "/settings/org/" + router.query.id + ) + } + />, + // eslint-disable-next-line react/jsx-key +
    ) : (

    - Add more users to the organization first. + {t("section-members:add-dialog.add-user-org-first")}

    )}
    @@ -110,7 +119,7 @@ const AddProjectMemberDialog = ({
    @@ -120,7 +129,7 @@ const AddProjectMemberDialog = ({ router.push("/settings/org/" + router.query.id) } color="mineshaft" - text="Add Users to Organization" + text={t("section-members:add-dialog.add-user-to-org")} size="md" /> )} diff --git a/frontend/components/basic/dialog/AddServiceTokenDialog.js b/frontend/components/basic/dialog/AddServiceTokenDialog.js index ddb20cce3..b02a804b7 100644 --- a/frontend/components/basic/dialog/AddServiceTokenDialog.js +++ b/frontend/components/basic/dialog/AddServiceTokenDialog.js @@ -1,40 +1,42 @@ -import { Fragment, useState } from 'react'; -import { faCheck, faCopy } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Dialog, Transition } from '@headlessui/react'; -import nacl from 'tweetnacl'; +import { Fragment, useState } from "react"; +import { useTranslation } from "next-i18next"; +import { faCheck, faCopy } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { Dialog, Transition } from "@headlessui/react"; +import nacl from "tweetnacl"; -import addServiceToken from '~/pages/api/serviceToken/addServiceToken'; -import getLatestFileKey from '~/pages/api/workspace/getLatestFileKey'; +import addServiceToken from "~/pages/api/serviceToken/addServiceToken"; +import getLatestFileKey from "~/pages/api/workspace/getLatestFileKey"; -import { envMapping } from '../../../public/data/frequentConstants'; +import { envMapping } from "../../../public/data/frequentConstants"; import { decryptAssymmetric, - encryptAssymmetric -} from '../../utilities/cryptography/crypto'; -import Button from '../buttons/Button'; -import InputField from '../InputField'; -import ListBox from '../Listbox'; + encryptAssymmetric, +} from "../../utilities/cryptography/crypto"; +import Button from "../buttons/Button"; +import InputField from "../InputField"; +import ListBox from "../Listbox"; const expiryMapping = { - '1 day': 86400, - '7 days': 604800, - '1 month': 2592000, - '6 months': 15552000, - '12 months': 31104000 + "1 day": 86400, + "7 days": 604800, + "1 month": 2592000, + "6 months": 15552000, + "12 months": 31104000, }; const AddServiceTokenDialog = ({ isOpen, closeModal, workspaceId, - workspaceName + workspaceName, }) => { - const [serviceToken, setServiceToken] = useState(''); - const [serviceTokenName, setServiceTokenName] = useState(''); - const [serviceTokenEnv, setServiceTokenEnv] = useState('Development'); - const [serviceTokenExpiresIn, setServiceTokenExpiresIn] = useState('1 day'); + const [serviceToken, setServiceToken] = useState(""); + const [serviceTokenName, setServiceTokenName] = useState(""); + const [serviceTokenEnv, setServiceTokenEnv] = useState("Development"); + const [serviceTokenExpiresIn, setServiceTokenExpiresIn] = useState("1 day"); const [serviceTokenCopied, setServiceTokenCopied] = useState(false); + const { t } = useTranslation(); const generateServiceToken = async () => { const latestFileKey = await getLatestFileKey({ workspaceId }); @@ -43,7 +45,7 @@ const AddServiceTokenDialog = ({ ciphertext: latestFileKey.latestKey.encryptedKey, nonce: latestFileKey.latestKey.nonce, publicKey: latestFileKey.latestKey.sender.publicKey, - privateKey: localStorage.getItem('PRIVATE_KEY') + privateKey: localStorage.getItem("PRIVATE_KEY"), }); // generate new public/private key pair @@ -55,7 +57,7 @@ const AddServiceTokenDialog = ({ const { ciphertext: encryptedKey, nonce } = encryptAssymmetric({ plaintext: key, publicKey, - privateKey + privateKey, }); let newServiceToken = await addServiceToken({ @@ -65,16 +67,16 @@ const AddServiceTokenDialog = ({ expiresIn: expiryMapping[serviceTokenExpiresIn], publicKey, encryptedKey, - nonce + nonce, }); - const serviceToken = newServiceToken + ',' + privateKey; + const serviceToken = newServiceToken + "," + privateKey; setServiceToken(serviceToken); }; function copyToClipboard() { // Get the text field - var copyText = document.getElementById('serviceToken'); + var copyText = document.getElementById("serviceToken"); // Select the text field copyText.select(); @@ -91,8 +93,8 @@ const AddServiceTokenDialog = ({ const closeAddServiceTokenModal = () => { closeModal(); - setServiceTokenName(''); - setServiceToken(''); + setServiceTokenName(""); + setServiceToken(""); }; return ( @@ -122,27 +124,26 @@ const AddServiceTokenDialog = ({ leaveFrom="opacity-100 scale-100" leaveTo="opacity-0 scale-95" > - {serviceToken == '' ? ( + {serviceToken == "" ? ( - Add a service token for {workspaceName} + {t("section-token:add-dialog.title", { + target: workspaceName, + })}

    - 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. + {t("section-token:add-dialog.description")}

    @@ -169,14 +170,14 @@ const AddServiceTokenDialog = ({ selected={serviceTokenExpiresIn} onChange={setServiceTokenExpiresIn} data={[ - '1 day', - '7 days', - '1 month', - '6 months', - '12 months' + "1 day", + "7 days", + "1 month", + "6 months", + "12 months", ]} - isFull={true} - text="Expires in: " + width="full" + text={`${t("common:expired-in")}: `} />
    @@ -184,10 +185,10 @@ const AddServiceTokenDialog = ({
    @@ -198,13 +199,14 @@ const AddServiceTokenDialog = ({ as="h3" className="text-lg font-medium leading-6 text-gray-400 z-50" > - Copy your service token + {t("section-token:add-dialog.copy-service-token")}

    - Once you close this popup, you will never see your - service token again + {t( + "section-token:add-dialog.copy-service-token-description" + )}

    @@ -234,7 +236,7 @@ const AddServiceTokenDialog = ({ )} - Click to Copy + {t("common.click-to-copy")}
  • diff --git a/frontend/components/dashboard/CommentField.tsx b/frontend/components/dashboard/CommentField.tsx index 35f524a69..ea29aa73c 100644 --- a/frontend/components/dashboard/CommentField.tsx +++ b/frontend/components/dashboard/CommentField.tsx @@ -1,9 +1,13 @@ +import { useTranslation } from "next-i18next"; + /** * This is the text field where people can add comments to particular secrets. */ const CommentField = ({ comment, modifyComment, position }: { comment: string; modifyComment: (value: string, posistion: number) => void; position: number;}) => { + const { t } = useTranslation(); + return
    -

    Comments & notes

    +

    {t("dashboard:sidebar.comments")}