Update package-lock.json

This commit is contained in:
Tuan Dang
2023-04-22 11:50:26 +03:00
25 changed files with 1540 additions and 986 deletions

1860
backend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,10 @@
{
"dependencies": {
"@aws-sdk/client-secrets-manager": "^3.299.0",
"@aws-sdk/client-secrets-manager": "^3.303.0",
"@godaddy/terminus": "^4.11.2",
"@octokit/rest": "^19.0.5",
"@sentry/node": "^7.45.0",
"@sentry/tracing": "^7.45.0",
"@sentry/tracing": "^7.46.0",
"@sentry/node": "^7.41.0",
"@types/crypto-js": "^4.1.1",
"@types/libsodium-wrappers": "^0.7.10",

View File

@@ -10,9 +10,11 @@ import (
"errors"
"fmt"
"net/url"
"regexp"
"github.com/Infisical/infisical-merge/packages/api"
"github.com/Infisical/infisical-merge/packages/config"
"github.com/Infisical/infisical-merge/packages/crypto"
"github.com/Infisical/infisical-merge/packages/models"
"github.com/Infisical/infisical-merge/packages/srp"
@@ -33,6 +35,10 @@ type params struct {
keyLength uint32
}
const ADD_USER = "Add a new account login"
const REPLACE_USER = "Override current logged in user"
const EXIT_USER_MENU = "Exit"
// loginCmd represents the login command
var loginCmd = &cobra.Command{
Use: "login",
@@ -49,7 +55,7 @@ var loginCmd = &cobra.Command{
}
if currentLoggedInUserDetails.IsUserLoggedIn && !currentLoggedInUserDetails.LoginExpired && len(currentLoggedInUserDetails.UserCredentials.PrivateKey) != 0 {
shouldOverride, err := shouldOverrideLoginPrompt(currentLoggedInUserDetails.UserCredentials.Email)
shouldOverride, err := userLoginMenu(currentLoggedInUserDetails.UserCredentials.Email)
if err != nil {
util.HandleError(err)
}
@@ -59,6 +65,31 @@ var loginCmd = &cobra.Command{
}
}
//override domain
domainQuery := true
if config.INFISICAL_URL_MANUAL_OVERRIDE != util.INFISICAL_DEFAULT_API_URL {
overrideDomain, err := DomainOverridePrompt()
if err != nil {
util.HandleError(err)
}
//if not override set INFISICAL_URL to exported var
//set domainQuery to false
if !overrideDomain {
domainQuery = false
config.INFISICAL_URL = config.INFISICAL_URL_MANUAL_OVERRIDE
}
}
//prompt user to select domain between Infisical cloud and self hosting
if domainQuery {
err = askForDomain()
if err != nil {
util.HandleError(err, "Unable to parse domain url")
}
}
email, password, err := askForLoginCredentials()
if err != nil {
util.HandleError(err, "Unable to parse email and password for authentication")
@@ -252,6 +283,77 @@ func init() {
rootCmd.AddCommand(loginCmd)
}
func DomainOverridePrompt() (bool, error) {
const (
PRESET = "Use Domain"
OVERRIDE = "Change Domain"
)
options := []string{PRESET, OVERRIDE}
optionsPrompt := promptui.Select{
Label: fmt.Sprintf("Current INFISICAL_API_URL Domain Override: %s", config.INFISICAL_URL_MANUAL_OVERRIDE),
Items: options,
Size: 2,
}
_, selectedOption, err := optionsPrompt.Run()
if err != nil {
return false, err
}
return selectedOption == OVERRIDE, err
}
func askForDomain() error {
//query user to choose between Infisical cloud or self hosting
const (
INFISICAL_CLOUD = "Infisical Cloud"
SELF_HOSTING = "Self Hosting"
)
options := []string{INFISICAL_CLOUD, SELF_HOSTING}
optionsPrompt := promptui.Select{
Label: "Select your hosting option",
Items: options,
Size: 2,
}
_, selectedHostingOption, err := optionsPrompt.Run()
if err != nil {
return err
}
if selectedHostingOption == INFISICAL_CLOUD {
//cloud option
config.INFISICAL_URL = util.INFISICAL_DEFAULT_API_URL
return nil
}
urlValidation := func(input string) error {
_, err := url.ParseRequestURI(input)
if err != nil {
return errors.New("this is an invalid url")
}
return nil
}
domainPrompt := promptui.Prompt{
Label: "Domain",
Validate: urlValidation,
Default: "Example - https://my-self-hosted-instance.com/api",
}
domain, err := domainPrompt.Run()
if err != nil {
return err
}
//set api url
config.INFISICAL_URL = domain
//return nil
return nil
}
func askForLoginCredentials() (email string, password string, err error) {
validateEmail := func(input string) error {
matched, err := regexp.MatchString("^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$", input)
@@ -342,16 +444,18 @@ func getFreshUserCredentials(email string, password string) (*api.GetLoginOneV2R
return &loginOneResponseResult, &loginTwoResponseResult, nil
}
func shouldOverrideLoginPrompt(currentLoggedInUserEmail string) (bool, error) {
func userLoginMenu(currentLoggedInUserEmail string) (bool, error) {
label := fmt.Sprintf("Current logged in user email: %s on domain: %s", currentLoggedInUserEmail, config.INFISICAL_URL)
prompt := promptui.Select{
Label: fmt.Sprintf("There seems to be a user already logged in with the email: %s. Would you like to override that login? Select[Yes/No]", currentLoggedInUserEmail),
Items: []string{"No", "Yes"},
Label: label,
Items: []string{ADD_USER, REPLACE_USER, EXIT_USER_MENU},
}
_, result, err := prompt.Run()
if err != nil {
return false, err
}
return result == "Yes", err
return result != EXIT_USER_MENU, err
}
func generateFromPassword(password string, salt []byte, p *params) (hash []byte, err error) {

252
cli/packages/cmd/user.go Normal file
View File

@@ -0,0 +1,252 @@
package cmd
import (
"errors"
"net/url"
"github.com/Infisical/infisical-merge/packages/config"
"github.com/Infisical/infisical-merge/packages/models"
"github.com/Infisical/infisical-merge/packages/util"
"github.com/manifoldco/promptui"
"github.com/spf13/cobra"
)
var userCmd = &cobra.Command{
Use: "user",
Short: "Used to manage user credentials",
DisableFlagsInUseLine: true,
Example: "infisical user",
Args: cobra.ExactArgs(0),
Run: func(cmd *cobra.Command, args []string) {
cmd.Help()
},
}
var switchCmd = &cobra.Command{
Use: "switch",
Short: "Used to switch between Infisical profiles",
DisableFlagsInUseLine: true,
Example: "infisical switch",
Args: cobra.ExactArgs(0),
PreRun: func(cmd *cobra.Command, args []string) {
util.RequireLogin()
},
Run: func(cmd *cobra.Command, args []string) {
//get previous logged in profiles
loggedInProfiles, err := getLoggedInUsers()
if err != nil {
util.HandleError(err, "[infisical user switch]: Unable to get logged Profiles")
}
//prompt user
profile, err := LoggedInUsersPrompt(loggedInProfiles)
if err != nil {
util.HandleError(err, "[infisical user switch]: Prompt error")
}
//write to config file
configFile, err := util.GetConfigFile()
if err != nil {
util.HandleError(err, "[infisical user switch]: Unable to get config file")
}
configFile.LoggedInUserEmail = profile
//set logged in user domain
ok := util.ConfigContainsEmail(configFile.LoggedInUsers, profile)
if !ok {
//profile not in loggedInUsers
configFile.LoggedInUsers = append(configFile.LoggedInUsers, models.LoggedInUser{
Email: profile,
Domain: config.INFISICAL_URL,
})
//set logged in user domain
configFile.LoggedInUserDomain = config.INFISICAL_URL
} else {
//exists, set logged in user domain
for _, v := range configFile.LoggedInUsers {
if profile == v.Email {
configFile.LoggedInUserDomain = v.Domain
break
}
}
}
err = util.WriteConfigFile(&configFile)
if err != nil {
util.HandleError(err, "")
}
},
}
var updateCmd = &cobra.Command{
Use: "update",
Short: "Used to update properties of an Infisical profile",
DisableFlagsInUseLine: true,
Example: "infisical user update",
Args: cobra.ExactArgs(0),
Run: func(cmd *cobra.Command, args []string) {
cmd.Help()
},
}
var domainCmd = &cobra.Command{
Use: "domain",
Short: "Used to update the domain of an Infisical profile",
DisableFlagsInUseLine: true,
Example: "infisical user update domain",
Args: cobra.ExactArgs(0),
PreRun: func(cmd *cobra.Command, args []string) {
util.RequireLogin()
},
Run: func(cmd *cobra.Command, args []string) {
//prompt for profiles selection
loggedInProfiles, err := getLoggedInUsers()
if err != nil {
util.HandleError(err, "[infisical user update domain]: Unable to get logged Profiles")
}
//prompt user
profile, err := LoggedInUsersPrompt(loggedInProfiles)
if err != nil {
util.HandleError(err, "[infisical user update domain]: Prompt error")
}
domain := ""
domainQuery := true
if config.INFISICAL_URL_MANUAL_OVERRIDE != util.INFISICAL_DEFAULT_API_URL {
override, err := DomainOverridePrompt()
if err != nil {
util.HandleError(err, "[infisical user update domain]: Domain override prompt error")
}
if !override {
domainQuery = false
domain = config.INFISICAL_URL_MANUAL_OVERRIDE
}
}
if domainQuery {
//prompt to update domain
domain, err = NewDomainPrompt()
if err != nil {
util.HandleError(err, "[infisical user update domain]: Prompt error")
}
}
//write to config file
configFile, err := util.GetConfigFile()
if err != nil {
util.HandleError(err, "[infisical user update domain]: Unable to get config file")
}
//check if profile in logged in profiles
//if not add new profile loggedInUsers
//else update profile from loggedinUsers slice
ok := util.ConfigContainsEmail(configFile.LoggedInUsers, profile)
if !ok {
configFile.LoggedInUsers = append(configFile.LoggedInUsers, models.LoggedInUser{
Email: profile,
Domain: domain,
})
} else {
//exists, set logged in user domain
for idx, v := range configFile.LoggedInUsers {
if profile == v.Email {
configFile.LoggedInUsers[idx].Domain = domain //inplace
break
}
}
}
//check if current loggedinuser is selected profile
//if yes set current domain to changed domain
if configFile.LoggedInUserEmail == profile {
configFile.LoggedInUserDomain = domain
}
err = util.WriteConfigFile(&configFile)
if err != nil {
util.HandleError(err, "")
}
},
}
func init() {
updateCmd.AddCommand(domainCmd)
userCmd.AddCommand(updateCmd)
userCmd.AddCommand(switchCmd)
rootCmd.AddCommand(userCmd)
}
// This returns all logged in user emails from the config file.
// If none, it returns the current logged in user in a slice
func getLoggedInUsers() ([]string, error) {
loggedInProfiles := []string{}
if util.ConfigFileExists() {
configFile, err := util.GetConfigFile()
if err != nil {
return loggedInProfiles, err
}
//get logged in profiles
//
if len(configFile.LoggedInUsers) > 0 {
for _, v := range configFile.LoggedInUsers {
loggedInProfiles = append(loggedInProfiles, v.Email)
}
} else {
loggedInProfiles = append(loggedInProfiles, configFile.LoggedInUserEmail)
}
return loggedInProfiles, nil
} else {
//empty
return loggedInProfiles, errors.New("couldn't retrieve config file")
}
}
func NewDomainPrompt() (string, error) {
urlValidation := func(input string) error {
_, err := url.ParseRequestURI(input)
if err != nil {
return errors.New("this is an invalid url")
}
return nil
}
//else run prompt to enter domain
domainPrompt := promptui.Prompt{
Label: "New Domain",
Validate: urlValidation,
Default: "Example - https://my-self-hosted-instance.com/api",
}
domain, err := domainPrompt.Run()
if err != nil {
return "", err
}
return domain, nil
}
func LoggedInUsersPrompt(profiles []string) (string, error) {
prompt := promptui.Select{Label: "Which of your Infisical profiles would you like to use",
Items: profiles,
Size: 7,
}
idx, _, err := prompt.Run()
if err != nil {
return "", err
}
return profiles[idx], nil
}

View File

@@ -1,3 +1,4 @@
package config
var INFISICAL_URL string
var INFISICAL_URL_MANUAL_OVERRIDE string

View File

@@ -12,8 +12,15 @@ type UserCredentials struct {
// The file struct for Infisical config file
type ConfigFile struct {
LoggedInUserEmail string `json:"loggedInUserEmail"`
VaultBackendType keyring.BackendType `json:"vaultBackendType"`
LoggedInUserEmail string `json:"loggedInUserEmail"`
LoggedInUserDomain string `json:"LoggedInUserDomain,omitempty"`
VaultBackendType keyring.BackendType `json:"vaultBackendType"`
LoggedInUsers []LoggedInUser `json:"loggedInUsers,omitempty"`
}
type LoggedInUser struct {
Email string `json:"email"`
Domain string `json:"domain"`
}
type SingleEnvironmentVariable struct {

View File

@@ -23,8 +23,5 @@ func WriteToFile(fileName string, dataToWrite []byte, filePerm os.FileMode) erro
func CheckIsConnectedToInternet() (ok bool) {
_, err := http.Get("http://clients3.google.com/generate_204")
if err != nil {
return false
}
return true
return err == nil
}

View File

@@ -7,6 +7,7 @@ import (
"os"
"path/filepath"
"github.com/Infisical/infisical-merge/packages/config"
"github.com/Infisical/infisical-merge/packages/models"
log "github.com/sirupsen/logrus"
)
@@ -31,9 +32,28 @@ func WriteInitalConfig(userCredentials *models.UserCredentials) error {
return fmt.Errorf("writeInitalConfig: unable to write config file because [err=%s]", err)
}
//if profiles exists
loggedInUser := models.LoggedInUser{
Email: userCredentials.Email,
Domain: config.INFISICAL_URL,
}
//if empty or if email not in loggedinUsers
if len(existingConfigFile.LoggedInUsers) == 0 || !ConfigContainsEmail(existingConfigFile.LoggedInUsers, userCredentials.Email) {
existingConfigFile.LoggedInUsers = append(existingConfigFile.LoggedInUsers, loggedInUser)
} else {
//if exists update domain of loggedin users
for idx, user := range existingConfigFile.LoggedInUsers {
if user.Email == userCredentials.Email {
existingConfigFile.LoggedInUsers[idx] = loggedInUser
}
}
}
configFile := models.ConfigFile{
LoggedInUserEmail: userCredentials.Email,
VaultBackendType: existingConfigFile.VaultBackendType,
LoggedInUserEmail: userCredentials.Email,
LoggedInUserDomain: config.INFISICAL_URL,
VaultBackendType: existingConfigFile.VaultBackendType,
LoggedInUsers: existingConfigFile.LoggedInUsers,
}
configFileMarshalled, err := json.Marshal(configFile)
@@ -176,7 +196,7 @@ func GetConfigFile() (models.ConfigFile, error) {
return configFile, nil
}
// Write a ConfigFile to disk. Raise error if unable to save the model to ask
// Write a ConfigFile to disk. Raise error if unable to save the model to disk
func WriteConfigFile(configFile *models.ConfigFile) error {
fullConfigFilePath, fullConfigFileDirPath, err := GetFullConfigFilePath()
if err != nil {

View File

@@ -6,6 +6,7 @@ import (
"github.com/99designs/keyring"
"github.com/Infisical/infisical-merge/packages/api"
"github.com/Infisical/infisical-merge/packages/config"
"github.com/Infisical/infisical-merge/packages/models"
"github.com/go-resty/resty/v2"
)
@@ -87,6 +88,13 @@ func GetCurrentLoggedInUserDetails() (LoggedInUserDetails, error) {
SetAuthToken(userCreds.JTWToken).
SetHeader("Accept", "application/json")
config.INFISICAL_URL_MANUAL_OVERRIDE = config.INFISICAL_URL
//configFile.LoggedInUserDomain
//if not empty set as infisical url
if configFile.LoggedInUserDomain != "" {
config.INFISICAL_URL = configFile.LoggedInUserDomain
}
isAuthenticated := api.CallIsAuthenticated(httpClient)
if !isAuthenticated {
return LoggedInUserDetails{

View File

@@ -9,6 +9,8 @@ import (
"os/exec"
"path"
"strings"
"github.com/Infisical/infisical-merge/packages/models"
)
type DecodedSymmetricEncryptionDetails = struct {
@@ -61,6 +63,16 @@ func IsSecretTypeValid(s string) bool {
return false
}
// Checks if the passed in email already exists in the users slice
func ConfigContainsEmail(users []models.LoggedInUser, email string) bool {
for _, value := range users {
if value.Email == email {
return true
}
}
return false
}
func RequireLogin() {
currentUserDetails, err := GetCurrentLoggedInUserDetails()

View File

@@ -17,10 +17,10 @@ import (
"github.com/go-resty/resty/v2"
)
func GetPlainTextSecretsViaServiceToken(fullServiceToken string) ([]models.SingleEnvironmentVariable, error) {
func GetPlainTextSecretsViaServiceToken(fullServiceToken string) ([]models.SingleEnvironmentVariable, api.GetServiceTokenDetailsResponse, error) {
serviceTokenParts := strings.SplitN(fullServiceToken, ".", 4)
if len(serviceTokenParts) < 4 {
return nil, fmt.Errorf("invalid service token entered. Please double check your service token and try again")
return nil, api.GetServiceTokenDetailsResponse{}, fmt.Errorf("invalid service token entered. Please double check your service token and try again")
}
serviceToken := fmt.Sprintf("%v.%v.%v", serviceTokenParts[0], serviceTokenParts[1], serviceTokenParts[2])
@@ -32,7 +32,7 @@ func GetPlainTextSecretsViaServiceToken(fullServiceToken string) ([]models.Singl
serviceTokenDetails, err := api.CallGetServiceTokenDetailsV2(httpClient)
if err != nil {
return nil, fmt.Errorf("unable to get service token details. [err=%v]", err)
return nil, api.GetServiceTokenDetailsResponse{}, fmt.Errorf("unable to get service token details. [err=%v]", err)
}
encryptedSecrets, err := api.CallGetSecretsV2(httpClient, api.GetEncryptedSecretsV2Request{
@@ -41,25 +41,25 @@ func GetPlainTextSecretsViaServiceToken(fullServiceToken string) ([]models.Singl
})
if err != nil {
return nil, err
return nil, api.GetServiceTokenDetailsResponse{}, err
}
decodedSymmetricEncryptionDetails, err := GetBase64DecodedSymmetricEncryptionDetails(serviceTokenParts[3], serviceTokenDetails.EncryptedKey, serviceTokenDetails.Iv, serviceTokenDetails.Tag)
if err != nil {
return nil, fmt.Errorf("unable to decode symmetric encryption details [err=%v]", err)
return nil, api.GetServiceTokenDetailsResponse{}, fmt.Errorf("unable to decode symmetric encryption details [err=%v]", err)
}
plainTextWorkspaceKey, err := crypto.DecryptSymmetric([]byte(serviceTokenParts[3]), decodedSymmetricEncryptionDetails.Cipher, decodedSymmetricEncryptionDetails.Tag, decodedSymmetricEncryptionDetails.IV)
if err != nil {
return nil, fmt.Errorf("unable to decrypt the required workspace key")
return nil, api.GetServiceTokenDetailsResponse{}, fmt.Errorf("unable to decrypt the required workspace key")
}
plainTextSecrets, err := GetPlainTextSecrets(plainTextWorkspaceKey, encryptedSecrets)
if err != nil {
return nil, fmt.Errorf("unable to decrypt your secrets [err=%v]", err)
return nil, api.GetServiceTokenDetailsResponse{}, fmt.Errorf("unable to decrypt your secrets [err=%v]", err)
}
return plainTextSecrets, nil
return plainTextSecrets, serviceTokenDetails, nil
}
func GetPlainTextSecretsViaJTW(JTWToken string, receiversPrivateKey string, workspaceId string, environmentName string, tagSlugs string) ([]models.SingleEnvironmentVariable, error) {
@@ -131,6 +131,7 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters) ([]models
isConnected := CheckIsConnectedToInternet()
var secretsToReturn []models.SingleEnvironmentVariable
var serviceTokenDetails api.GetServiceTokenDetailsResponse
var errorToReturn error
if infisicalToken == "" {
@@ -182,7 +183,11 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters) ([]models
} else {
log.Debug("Trying to fetch secrets using service token")
secretsToReturn, errorToReturn = GetPlainTextSecretsViaServiceToken(infisicalToken)
secretsToReturn, serviceTokenDetails, errorToReturn = GetPlainTextSecretsViaServiceToken(infisicalToken)
if serviceTokenDetails.Environment != params.Environment {
PrintErrorMessageAndExit(fmt.Sprintf("Fetch secrets failed: token allows [%s] environment access, not [%s]. Service tokens are environment-specific; no need for --env flag.", params.Environment, serviceTokenDetails.Environment))
}
}
return secretsToReturn, errorToReturn

View File

@@ -9,4 +9,7 @@ infisical login
## Description
The CLI uses authentication to verify your identity. When you enter the correct email and password for your account, a token is generated and saved in your system Keyring to allow you to make future interactions with the CLI.
If you want to change where the login credentials are stored, visit the [vaults command](./vault)
To change where the login credentials are stored, visit the [vaults command](./vault).
If you have added multiple users, you can switch between the users by using the [user command](./user).

View File

@@ -0,0 +1,29 @@
---
title: "infisical user"
description: "Manage logged in users"
---
```bash
infisical user
```
## Description
This command allows you to manage the current logged in users on the CLI
### Sub-commands
<Accordion title="infisical user switch" defaultOpen="true">
Use this command to switch between profiles that are currently logged into the CLI
```bash
infisical user switch
```
</Accordion>
<Accordion title="infisical user update domain">
With this command, you can modify the backend API that is utilized for all requests associated with a specific profile.
For instance, you have the option to point the profile to use either the Infisical Cloud or your own self-hosted Infisical instance.
```bash
infisical user update domain
```
</Accordion>

View File

@@ -1,6 +1,6 @@
---
title: "FAQ"
description: "Frequently Asked Questions about Infisical"
description: "Frequently Asked Questions about Infisical CLI"
---
Frequently asked questions about the CLI can be found on this page.

View File

@@ -109,7 +109,10 @@ infisical login
<Accordion title="Optional: point CLI to self-hosted">
The CLI is set to connect to Infisical Cloud by default, but if you're running your own instance of Infisical, you can direct the CLI to it using one of the methods provided below.
#### Method 1: Export environment variable
#### Method 1: Use the updated CLI
Beginning with CLI version V0.4.0, it is now possible to choose between logging in through the Infisical cloud or your own self-hosted instance. Simply execute the `infisical login` command and follow the on-screen instructions.
#### Method 2: Export environment variable
You can point the CLI to the self hosted Infisical instance by exporting the environment variable `INFISICAL_API_URL` in your terminal.
<Tabs>
@@ -135,7 +138,7 @@ You can point the CLI to the self hosted Infisical instance by exporting the env
</Tab>
</Tabs>
#### Method 2: Set manually on every command
#### Method 3: Set manually on every command
Another option to point the CLI to your self hosted Infisical instance is to set it via a flag on every command you run.
```bash

View File

@@ -101,7 +101,8 @@
"pages": [
"self-hosting/overview",
"self-hosting/configuration/envars",
"self-hosting/configuration/email"
"self-hosting/configuration/email",
"self-hosting/faq"
]
},
{
@@ -118,6 +119,7 @@
"cli/commands/secrets",
"cli/commands/export",
"cli/commands/vault",
"cli/commands/user",
"cli/commands/reset"
]
},

27
docs/self-hosting/faq.mdx Normal file
View File

@@ -0,0 +1,27 @@
---
title: "FAQ"
description: "Frequently Asked Questions about Infisical self hosting"
---
Frequently asked questions about self hosted instance of Infisical can be found on this page.
If you can't find the answer you are looking for, please create an issue on our GitHub repository or join our Slack channel for additional support.
<Accordion title="When I refresh any page, it logs me out" defaultOpen="true">
This issue is typically seen when you haven't set up SSL for your self hosted instance of Infisical. When SSL is not enabled, you can't receive secure cookies, preventing the session data to not be saved.
To fix this, we highly recommend that you set up SSL for your instance.
However, in the event you choose to use Infisical without SSL, you can do so by setting the `HTTPS_ENABLED` environment variable to `"false"` for the backend application.
[Learn more about secure cookies](https://really-simple-ssl.com/definition/what-are-secure-cookies/)
</Accordion>
<Accordion title="Is self hosted Infisical HA?">
Infisical leverages the robust container orchestration capabilities of Kubernetes and the inherent high availability features of Bitnami MongoDB to ensure resilience and fault tolerance.
By deploying multiple replicas of Infisical application on Kubernetes, operations can continue even if a single instance fails.
Additionally, Bitnami MongoDB supports replica sets, which provide data redundancy and automatic failover for the underlying database.
Kubernetes Services facilitate load balancing, effectively distributing traffic across your application's instances and ensuring optimal performance.
The combination of Kubernetes' self-healing mechanisms and Bitnami MongoDB's failover capabilities work together to create a highly available and fault-tolerant application capable of recovering gracefully from unexpected failures.
To further increase data redundancy, we recommend that you use a managed MongoDB service for your self hosted instance of Infisical.
</Accordion>

View File

@@ -53,9 +53,9 @@ const Button = ({
'group m-auto md:m-0 inline-block rounded-md duration-200',
// Setting background colors and hover modes
color === 'mineshaft' && activityStatus && 'bg-mineshaft-700 hover:bg-primary',
color === 'mineshaft' && activityStatus && 'bg-mineshaft-800 border border-mineshaft-600 hover:bg-primary/[0.15] hover:border-primary/60',
color === 'mineshaft' && !activityStatus && 'bg-mineshaft',
(color === 'primary' || !color) && activityStatus && 'bg-primary hover:opacity-80',
(color === 'primary' || !color) && activityStatus && 'bg-primary border border-primary-400 opacity-80 hover:opacity-100',
(color === 'primary' || !color) && !activityStatus && 'bg-primary',
color === 'red' && 'bg-red',
@@ -74,11 +74,11 @@ const Button = ({
'relative font-medium flex items-center',
// Setting the text color for the text and icon
color === 'mineshaft' && 'text-gray-400',
color === 'mineshaft' && 'text-bunker-200',
color !== 'mineshaft' && color !== 'red' && color !== 'none' && 'text-black',
color === 'red' && 'text-gray-200',
color === 'none' && 'text-gray-200 text-xl',
activityStatus && color !== 'red' && color !== 'none' ? 'group-hover:text-black' : '',
activityStatus && color !== 'red' && color !== 'mineshaft' && color !== 'none' ? 'group-hover:text-black' : '',
size === 'icon' && 'flex items-center justify-center'
);

View File

@@ -101,7 +101,7 @@ const KeyPair = ({
}`}
>
<div className="relative flex flex-row justify-between w-full mr-auto max-h-14 items-center">
<div className="w-1/5 border-r border-mineshaft-600 flex flex-row items-center">
<div className="w-[23%] border-r border-mineshaft-600 flex flex-row items-center">
<div className='text-bunker-400 text-xs flex items-center justify-center w-14 h-10 cursor-default'>{keyPair.pos + 1}</div>
<div className="flex items-center max-h-16 w-full">
<DashboardInputField

View File

@@ -0,0 +1,90 @@
import { useRouter } from 'next/router';
import { faAngleRight } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { useOrganization, useWorkspace } from '@app/context';
import { Select, SelectItem, Tooltip } from '../v2';
/**
* This is the component at the top of almost every page.
* It shows how to navigate to a certain page.
* It future these links should also be clickable and hoverable
* @param {object} obj
* @param {string} obj.pageName - Name of the page
* @param {boolean} obj.isProjectRelated - whether or not this page is related to project (determine if it's 2 or 3 navigation steps)
* @param {boolean} obj.isOrganizationRelated - whether or not this page is related to organization (determine if it's 2 or 3 navigation steps)
* @param {string} obj.currentEnv - current environment inside a project
* @param {string} obj.userAvailableEnvs - environments that are available to a user in this project (used for the dropdown)
* @param {string} obj.onEnvChange - the action that happens when an env is changed
* @returns
*/
export default function NavHeaderSecrets({
pageName,
isProjectRelated,
isOrganizationRelated,
isSnapshot,
currentEnv,
userAvailableEnvs,
onEnvChange
}: {
pageName: string;
isProjectRelated?: boolean;
isOrganizationRelated?: boolean;
isSnapshot: boolean;
currentEnv?: string;
userAvailableEnvs?: any[];
onEnvChange?: (slug: string) => void;
}): JSX.Element {
const { currentWorkspace } = useWorkspace();
const { currentOrg } = useOrganization();
const router = useRouter()
return (
<div className={`${!isSnapshot && "absolute"} ml-6 flex flex-row items-center pt-6 cursor-default`}>
<div className="mr-3 flex h-6 w-6 items-center justify-center rounded-md bg-primary-900 text-mineshaft-100">
{currentOrg?.name?.charAt(0)}
</div>
<div className="text-md font-medium text-bunker-300">{currentOrg?.name}</div>
{isProjectRelated && (
<>
<FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-3 text-sm text-gray-400" />
<div className="text-md font-medium text-bunker-300">{currentWorkspace?.name}</div>
</>
)}
{isOrganizationRelated && (
<>
<FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-3 text-sm text-gray-400" />
<div className="text-md font-medium text-bunker-300">Organization Settings</div>
</>
)}
<FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-3 text-sm text-gray-400" />
{pageName === 'Secrets'
? <a className="text-md font-medium text-primary/80 hover:text-primary" href={`${router.asPath.split("?")[0]}`}>{pageName}</a>
: <div className="text-md text-gray-400">{pageName}</div>}
{currentEnv &&
<>
<FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-1.5 text-sm text-gray-400" />
<div className='pl-3 rounded-md hover:bg-bunker-100/10'>
<Tooltip content="Select environment">
<Select
value={userAvailableEnvs?.filter(uae => uae.name === currentEnv)[0]?.slug}
onValueChange={(value) => {
if (value && onEnvChange) onEnvChange(value);
}}
className="text-md pl-0 font-medium text-primary/80 hover:text-primary bg-transparent"
dropdownContainerClassName="text-bunker-200 bg-mineshaft-800 border border-mineshaft-600 drop-shadow-2xl"
>
{userAvailableEnvs?.map(({ name, slug }) => (
<SelectItem value={slug} key={slug}>
{name}
</SelectItem>
))}
</Select>
</Tooltip>
</div>
</>}
</div>
);
}

View File

@@ -35,7 +35,7 @@ export const Tooltip = ({
sideOffset={5}
{...props}
className={twMerge(
`z-20 select-none rounded-md bg-mineshaft-500 py-2 px-4 text-sm text-bunker-200 shadow-md
`z-20 select-none max-w-[15rem] rounded-md bg-mineshaft-800 border border-mineshaft-600 py-2 px-4 font-light text-sm text-bunker-200 shadow-md
data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade
data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade
data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade
@@ -45,7 +45,7 @@ data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade
)}
>
{content}
<TooltipPrimitive.Arrow width={11} height={5} className="fill-mineshaft-500" />
<TooltipPrimitive.Arrow width={11} height={5} className="fill-mineshaft-600" />
</TooltipPrimitive.Content>
</TooltipPrimitive.Root>
);

View File

@@ -19,8 +19,8 @@ import {
} from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tag } from 'public/data/frequentInterfaces';
import queryString from 'query-string';
// import queryString from 'query-string';
import Button from '@app/components/basic/buttons/Button';
import BottonRightPopup from '@app/components/basic/popups/BottomRightPopup';
import { useNotificationContext } from '@app/components/context/Notifications/NotificationProvider';
@@ -29,7 +29,7 @@ import DownloadSecretMenu from '@app/components/dashboard/DownloadSecretsMenu';
import DropZone from '@app/components/dashboard/DropZone';
import KeyPair from '@app/components/dashboard/KeyPair';
import SideBar from '@app/components/dashboard/SideBar';
import NavHeader from '@app/components/navigation/NavHeader';
import NavHeaderSecrets from '@app/components/navigation/NavHeaderSecrets';
import { decryptAssymmetric, decryptSymmetric } from '@app/components/utilities/cryptography/crypto';
import guidGenerator from '@app/components/utilities/randomId';
import encryptSecrets from '@app/components/utilities/secrets/encryptSecrets';
@@ -173,7 +173,7 @@ export default function Dashboard() {
const { createNotification } = useNotificationContext();
const router = useRouter();
// const envInURL = queryString.parse(router.asPath.split('?')[1])?.env;
const envInURL = queryString.parse(router.asPath.split('?')[1])?.env;
const workspaceId = router.query.id as string;
const [workspaceEnvs, setWorkspaceEnvs] = useState<WorkspaceEnv[]>([]);
@@ -818,8 +818,8 @@ export default function Dashboard() {
};
return <div>
{false
? <DashboardEnvOverview />
{!envInURL
? <DashboardEnvOverview onEnvChange={handleOnEnvironmentChange} />
: (data ? (
<div className="bg-bunker-800 max-h-screen h-full relative flex flex-col justify-between text-white dark">
<Head>
@@ -845,11 +845,12 @@ export default function Dashboard() {
.map((duplicate) => duplicate.key) ?? []
}
/>
<div className="w-full max-h-96 pb-2 dark:[color-scheme:dark]">
<NavHeader
<div className="w-full max-h-96 dark:[color-scheme:dark]">
<NavHeaderSecrets
pageName={t('dashboard:title')}
currentEnv={selectedEnv?.name || ''}
isProjectRelated
isSnapshot={snapshotData !== undefined}
userAvailableEnvs={workspaceEnvs}
onEnvChange={handleOnEnvironmentChange}
/>
@@ -878,7 +879,7 @@ export default function Dashboard() {
)}
<div className="flex flex-row justify-start items-center text-3xl">
<div className="font-semibold mr-4 mt-1 flex flex-row items-center">
<p>{snapshotData ? 'Secret Snapshot' : t('dashboard:title')}</p>
<p>{snapshotData ? 'Secret Snapshot' : ''}</p>
{snapshotData && (
<span className="bg-primary-800 text-xs ml-4 mt-1 px-1.5 rounded-md w-min">
{new Date(snapshotData.createdAt).toLocaleString()}
@@ -970,7 +971,7 @@ export default function Dashboard() {
<div className="w-full flex flex-row items-start">
{(snapshotData || data?.length !== 0) && selectedEnv && (
<>
<div className="h-10 w-full bg-mineshaft-700 hover:bg-white/10 rounded-md flex flex-row items-center">
<div className="h-10 w-full bg-mineshaft-800 border border-mineshaft-600 hover:bg-mineshaft-700 duration-200 rounded-md flex flex-row items-center">
<FontAwesomeIcon
className="bg-transparent rounded-l-md py-[0.7rem] pl-4 pr-2 text-bunker-300 text-sm"
icon={faMagnifyingGlass}
@@ -1034,7 +1035,7 @@ export default function Dashboard() {
<div ref={secretsTop} />
<div className="group flex flex-col items-center bg-mineshaft-800 border-b-2 border-mineshaft-500 duration-100 sticky top-0 z-[60]">
<div className="relative flex flex-row justify-between w-full mr-auto max-h-14 items-center">
<div className="w-1/5 border-r border-mineshaft-600 flex flex-row items-center">
<div className="w-[23%] border-r border-mineshaft-600 flex flex-row items-center">
<div className='text-transparent text-xs flex items-center justify-center w-12 h-10 cursor-default'>0</div>
<span className='px-2 text-bunker-300 font-semibold'>Key</span>
{!snapshotData && <IconButton

View File

@@ -102,7 +102,7 @@ export default function PersonalSettings() {
</div>
</div>
<SecuritySection />
<div className="bg-white/5 rounded-md px-6 flex flex-col items-start w-full mt-2 mb-8 pt-2">
<div className="bg-white/5 rounded-md px-6 flex flex-col items-start w-full mt-2 mb-8 pt-4">
<div className="flex flex-row justify-between w-full">
<div className="flex flex-col w-full">
<p className="text-xl font-semibold mb-3">
@@ -112,7 +112,7 @@ export default function PersonalSettings() {
{t('settings-personal:api-keys.description')}
</p>
</div>
<div className="w-48 mt-2">
<div className="w-40 mt-2">
<Button
text={String(t('settings-personal:api-keys.add-new'))}
onButtonPressed={() => {

View File

@@ -31,7 +31,7 @@ import {
} from './DashboardPage.utils';
export const DashboardEnvOverview = () => {
export const DashboardEnvOverview = ({onEnvChange}: {onEnvChange: any;}) => {
const { t } = useTranslation();
const router = useRouter();
const { createNotification } = useNotificationContext();
@@ -243,7 +243,8 @@ export const DashboardEnvOverview = () => {
{userAvailableEnvs?.map(env => {
return <div key={`button-${env.slug}`} className="flex flex-row w-full justify-center h-10 items-center border-none mb-1 mx-2 min-w-[10rem]">
<Button
onClick={() => router.push(`${router.asPath }?env=${env.slug}`)}
onClick={() => onEnvChange(env.slug)}
// router.push(`${router.asPath }?env=${env.slug}`)
variant="outline_bg"
colorSchema="primary"
isFullWidth

View File

@@ -118,7 +118,7 @@ export const EnvComparisonRow = ({
<tr className="group min-w-full flex flex-row items-center hover:bg-bunker-700">
<td className="w-10 h-10 px-4 flex items-center justify-center border-none"><div className='text-center w-10 text-xs text-bunker-400'>{index + 1}</div></td>
<td className="flex flex-row justify-between items-center h-full min-w-[200px] lg:min-w-[220px] xl:min-w-[250px]">
<div className="flex flex-row items-center h-8 cursor-default">{secret?.key || ''}</div>
<div className="flex flex-row items-center h-8 cursor-default">{secrets![0].key || ''}</div>
<button type="button" className='mr-2 text-bunker-400 hover:text-bunker-300 invisible group-hover:visible' onClick={() => setAreValuesHiddenThisRow(!areValuesHiddenThisRow)}>
<FontAwesomeIcon icon={areValuesHiddenThisRow ? faEye : faEyeSlash} />
</button>