diff --git a/README.md b/README.md index 3851b3c53..292f5fe94 100644 --- a/README.md +++ b/README.md @@ -321,4 +321,4 @@ Infisical officially launched as v.1.0 on November 21st, 2022. However, a lot of - + diff --git a/backend/src/controllers/signupController.ts b/backend/src/controllers/signupController.ts index 90fad4744..42bef4973 100644 --- a/backend/src/controllers/signupController.ts +++ b/backend/src/controllers/signupController.ts @@ -10,6 +10,7 @@ import { } from '../helpers/signup'; import { issueTokens, createToken } from '../helpers/auth'; import { INVITED, ACCEPTED } from '../variables'; +import axios from 'axios'; /** * Signup step 1: Initialize account for user under email [email] and send a verification code @@ -179,6 +180,21 @@ export const completeAccountSignup = async (req: Request, res: Response) => { token = tokens.token; refreshToken = tokens.refreshToken; + + // sending a welcome email to new users + if (process.env.LOOPS_API_KEY) { + await axios.post("https://app.loops.so/api/v1/events/send", { + "email": email, + "eventName": "Sign Up", + "firstName": firstName, + "lastName": lastName + }, { + headers: { + "Accept": "application/json", + "Authorization": "Bearer " + process.env.LOOPS_API_KEY + }, + }); + } } catch (err) { Sentry.setUser(null); Sentry.captureException(err); diff --git a/cli/packages/cmd/root.go b/cli/packages/cmd/root.go index 6a0bdb80c..3aa9c5658 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{DisableDefaultCmd: true}, - Version: "0.1.14", + Version: "0.1.15", } // Execute adds all child commands to the root command and sets flags appropriately. diff --git a/cli/packages/cmd/run.go b/cli/packages/cmd/run.go index 7518fe98d..6e0133c8e 100644 --- a/cli/packages/cmd/run.go +++ b/cli/packages/cmd/run.go @@ -60,6 +60,13 @@ var runCmd = &cobra.Command{ return } + secretOverriding, err := cmd.Flags().GetBool("secret-overriding") + if err != nil { + log.Errorln("Unable to parse the secret-overriding flag") + log.Debugln(err) + return + } + shouldExpandSecrets, err := cmd.Flags().GetBool("expand") if err != nil { log.Errorln("Unable to parse the substitute flag") @@ -84,6 +91,10 @@ var runCmd = &cobra.Command{ secrets = util.SubstituteSecrets(secrets) } + if secretOverriding { + secrets = util.OverrideWithPersonalSecrets(secrets) + } + if cmd.Flags().Changed("command") { command := cmd.Flag("command").Value.String() err = executeMultipleCommandWithEnvs(command, secrets) @@ -108,6 +119,7 @@ func init() { runCmd.Flags().StringP("env", "e", "dev", "Set the environment (dev, prod, etc.) from which your secrets should be pulled from") runCmd.Flags().String("projectId", "", "The project ID from which your secrets should be pulled from") runCmd.Flags().Bool("expand", true, "Parse shell parameter expansions in your secrets") + runCmd.Flags().Bool("secret-overriding", true, "Prioritizes personal secrets with the same name over shared secrets") runCmd.Flags().StringP("command", "c", "", "chained commands to execute (e.g. \"npm install && npm run dev; echo ...\")") } diff --git a/cli/packages/models/cli.go b/cli/packages/models/cli.go index 0d1f96a05..8ba1c4627 100644 --- a/cli/packages/models/cli.go +++ b/cli/packages/models/cli.go @@ -17,6 +17,7 @@ type ConfigFile struct { type SingleEnvironmentVariable struct { Key string `json:"key"` Value string `json:"value"` + Type string `json:"type"` } type WorkspaceConfigFile struct { diff --git a/cli/packages/util/secrets.go b/cli/packages/util/secrets.go index c127111b1..f88aed96b 100644 --- a/cli/packages/util/secrets.go +++ b/cli/packages/util/secrets.go @@ -14,6 +14,9 @@ import ( "golang.org/x/crypto/nacl/box" ) +const PERSONAL_SECRET_TYPE_NAME = "personal" +const SHARED_SECRET_TYPE_NAME = "shared" + func getSecretsByWorkspaceIdAndEnvName(httpClient resty.Client, envName string, workspace models.WorkspaceConfigFile, userCreds models.UserCredentials) (listOfSecrets []models.SingleEnvironmentVariable, err error) { var pullSecretsRequestResponse models.PullSecretsResponse response, err := httpClient. @@ -78,6 +81,7 @@ func getSecretsByWorkspaceIdAndEnvName(httpClient resty.Client, envName string, env := models.SingleEnvironmentVariable{ Key: string(plainTextKey), Value: string(plainTextValue), + Type: string(secret.Type), } listOfEnv = append(listOfEnv, env) @@ -187,6 +191,7 @@ func GetSecretsFromAPIUsingInfisicalToken(infisicalToken string, envName string, env := models.SingleEnvironmentVariable{ Key: string(plainTextKey), Value: string(plainTextValue), + Type: string(secret.Type), } listOfEnv = append(listOfEnv, env) @@ -335,9 +340,48 @@ func SubstituteSecrets(secrets []models.SingleEnvironmentVariable) []models.Sing expandedSecrets = append(expandedSecrets, models.SingleEnvironmentVariable{ Key: secret.Key, Value: expandedVariable, + Type: secret.Type, }) } return expandedSecrets } + +// if two secrets with the same name are found, the one that has type `personal` will be in the returned list +func OverrideWithPersonalSecrets(secrets []models.SingleEnvironmentVariable) []models.SingleEnvironmentVariable { + personalSecret := make(map[string]models.SingleEnvironmentVariable) + sharedSecret := make(map[string]models.SingleEnvironmentVariable) + secretsToReturn := []models.SingleEnvironmentVariable{} + + for _, secret := range secrets { + if secret.Type == PERSONAL_SECRET_TYPE_NAME { + personalSecret[secret.Key] = models.SingleEnvironmentVariable{ + Key: secret.Key, + Value: secret.Value, + Type: secret.Type, + } + } + + if secret.Type == SHARED_SECRET_TYPE_NAME { + sharedSecret[secret.Key] = models.SingleEnvironmentVariable{ + Key: secret.Key, + Value: secret.Value, + Type: secret.Type, + } + } + } + + for _, secret := range secrets { + personalValue, personalExists := personalSecret[secret.Key] + sharedValue, sharedExists := sharedSecret[secret.Key] + + if personalExists && sharedExists || personalExists && !sharedExists { + secretsToReturn = append(secretsToReturn, personalValue) + } else { + secretsToReturn = append(secretsToReturn, sharedValue) + } + } + + return secretsToReturn +} diff --git a/docs/cli/commands/run.mdx b/docs/cli/commands/run.mdx index 2c65ef53e..4afd7586d 100644 --- a/docs/cli/commands/run.mdx +++ b/docs/cli/commands/run.mdx @@ -34,3 +34,4 @@ Inject environment variables from the platform into an application process. | `--projectId` | Used to link a local project to the platform (required only if injecting via the service token method) | None | | `--expand` | Parse shell parameter expansions in your secrets (e.g., `${DOMAIN}`) | `true` | | `--command` | Pass secrets into chained commands (e.g., `"first-command && second-command; more-commands..."`) | None | +| `--secret-overriding`| Prioritizes personal secrets with the same name over shared secrets | `true` | diff --git a/docs/cli/usage.mdx b/docs/cli/usage.mdx index aac5c1b74..fe08b60b3 100644 --- a/docs/cli/usage.mdx +++ b/docs/cli/usage.mdx @@ -38,13 +38,7 @@ infisical init infisical run -- [your application start command] ``` -Options you can specify: - -| Option | Description | Default value | -| ------------- | ----------------------------------------------------------------------------------------------------------- | ------------- | -| `--env` | Used to set the environment that secrets are pulled from. Accepted values: `dev`, `staging`, `test`, `prod` | `dev` | -| `--projectId` | Used to link a local project to the platform (required only if injecting via the service token method) | `None` | -| `--expand` | Parse shell parameter expansions in your secrets (e.g., `${DOMAIN}`) | `true` | +View all available options for `run` command [here](./commands/run) ## Examples: diff --git a/frontend/components/basic/InputField.tsx b/frontend/components/basic/InputField.tsx index 08afa975d..241410959 100644 --- a/frontend/components/basic/InputField.tsx +++ b/frontend/components/basic/InputField.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { memo,useState } from 'react'; import { faCircle, faEye, faEyeSlash } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; @@ -140,4 +140,4 @@ const InputField = ( } }; -export default React.memo(InputField); +export default memo(InputField); diff --git a/frontend/components/basic/Toggle.tsx b/frontend/components/basic/Toggle.tsx new file mode 100644 index 000000000..fa69e4419 --- /dev/null +++ b/frontend/components/basic/Toggle.tsx @@ -0,0 +1,81 @@ +import React from "react"; +import { Switch } from "@headlessui/react"; + + +interface OverrideProps { + id: string; + keyName: string; + value: string; + pos: number; +} + +interface ToggleProps { + enabled: boolean; + setEnabled: (value: boolean) => void; + addOverride: (value: OverrideProps) => void; + keyName: string; + value: string; + pos: number; + id: string; + deleteOverride: (id: string) => void; + sharedToHide: string[]; + setSharedToHide: (values: string[]) => void; +} + +/** + * This is a typical 'iPhone' toggle (e.g., user for overriding secrets with personal values) + * @param obj + * @param {boolean} obj.enabled - whether the toggle is turned on or off + * @param {function} obj.setEnabled - change the state of the toggle + * @param {function} obj.addOverride - a function that adds an override to a certain secret + * @param {string} obj.keyName - key of a certain secret + * @param {string} obj.value - value of a certain secret + * @param {number} obj.pos - position of a certain secret + #TODO: make the secret id persistent? + * @param {string} obj.id - id of a certain secret + * @param {function} obj.deleteOverride - a function that deleted an override for a certain secret + * @param {string[]} obj.sharedToHide - an array of shared secrets that we want to hide visually because they are overriden. + * @param {function} obj.setSharedToHide - a function that updates the array of secrets that we want to hide visually + * @returns + */ +export default function Toggle ({ + enabled, + setEnabled, + addOverride, + keyName, + value, + pos, + id, + deleteOverride, + sharedToHide, + setSharedToHide +}: ToggleProps): JSX.Element { + return ( + { + if (enabled == false) { + addOverride({ id, keyName, value, pos }); + setSharedToHide([ + ...sharedToHide!, + id + ]) + } else { + setSharedToHide(sharedToHide!.filter(tempId => tempId != id)) + deleteOverride(id); + } + setEnabled(!enabled); + }} + className={`${ + enabled ? 'bg-primary' : 'bg-bunker-400' + } relative inline-flex h-5 w-9 items-center rounded-full`} + > + Enable notifications + + + ) +} diff --git a/frontend/components/basic/buttons/Button.tsx b/frontend/components/basic/buttons/Button.tsx index 81672adc1..9197ccf13 100644 --- a/frontend/components/basic/buttons/Button.tsx +++ b/frontend/components/basic/buttons/Button.tsx @@ -16,7 +16,7 @@ type ButtonProps = { size: string; icon?: IconProp; active?: boolean; - iconDisabled?: string; + iconDisabled?: IconProp; textDisabled?: string; type?: ButtonHTMLAttributes['type']; }; @@ -73,15 +73,16 @@ export default function Button(props: ButtonProps): JSX.Element { // Setting the text color for the text and icon props.color == "mineshaft" && "text-gray-400", - props.color != "mineshaft" && props.color != "red" && "text-black", + props.color != "mineshaft" && props.color != "red" && props.color != "none" && "text-black", props.color == "red" && "text-gray-200", - activityStatus && props.color != "red" ? "group-hover:text-black" : "", + props.color == "none" && "text-gray-200 text-xl", + activityStatus && props.color != "red" && props.color != "none" ? "group-hover:text-black" : "", props.size == "icon" && "flex items-center justify-center" ); const textStyle = classNames( - "relative duration-200", + "relative duration-200 text-center w-full", // Show the loading sign if the loading indicator is on props.loading ? "opacity-0" : "opacity-100", diff --git a/frontend/components/basic/popups/BottomRightPopup.js b/frontend/components/basic/popups/BottomRightPopup.tsx similarity index 71% rename from frontend/components/basic/popups/BottomRightPopup.js rename to frontend/components/basic/popups/BottomRightPopup.tsx index 85aaba725..e6ea60f92 100644 --- a/frontend/components/basic/popups/BottomRightPopup.js +++ b/frontend/components/basic/popups/BottomRightPopup.tsx @@ -3,6 +3,16 @@ import { faXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +interface PopupProps { + buttonText: string; + buttonLink: string; + titleText: string; + emoji: string; + textLine1: string; + textLine2: string; + setCheckDocsPopUpVisible: (value: boolean) => void; +} + /** * This is the notification that pops up at the bottom right when a user performs a certain action * @param {object} org @@ -23,16 +33,16 @@ export default function BottonRightPopup({ textLine1, textLine2, setCheckDocsPopUpVisible, -}) { +}: PopupProps): JSX.Element { return (