mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Update package-lock.json
This commit is contained in:
1860
backend/package-lock.json
generated
1860
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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",
|
||||
|
||||
@@ -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
252
cli/packages/cmd/user.go
Normal 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
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
package config
|
||||
|
||||
var INFISICAL_URL string
|
||||
var INFISICAL_URL_MANUAL_OVERRIDE string
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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).
|
||||
29
docs/cli/commands/user.mdx
Normal file
29
docs/cli/commands/user.mdx
Normal 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>
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
27
docs/self-hosting/faq.mdx
Normal 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>
|
||||
@@ -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'
|
||||
);
|
||||
|
||||
@@ -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
|
||||
|
||||
90
frontend/src/components/navigation/NavHeaderSecrets.tsx
Normal file
90
frontend/src/components/navigation/NavHeaderSecrets.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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={() => {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user