mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Merge remote-tracking branch 'origin' into stripe-error
This commit is contained in:
@@ -10,7 +10,7 @@ export const apiLimiter = rateLimit({
|
|||||||
// errorHandler: console.error.bind(null, 'rate-limit-mongo')
|
// errorHandler: console.error.bind(null, 'rate-limit-mongo')
|
||||||
// }),
|
// }),
|
||||||
windowMs: 60 * 1000,
|
windowMs: 60 * 1000,
|
||||||
max: 240,
|
max: 350,
|
||||||
standardHeaders: true,
|
standardHeaders: true,
|
||||||
legacyHeaders: false,
|
legacyHeaders: false,
|
||||||
skip: (request) => {
|
skip: (request) => {
|
||||||
@@ -30,7 +30,7 @@ const authLimit = rateLimit({
|
|||||||
// collectionName: "expressRateRecords-authLimit",
|
// collectionName: "expressRateRecords-authLimit",
|
||||||
// }),
|
// }),
|
||||||
windowMs: 60 * 1000,
|
windowMs: 60 * 1000,
|
||||||
max: 10,
|
max: 100,
|
||||||
standardHeaders: true,
|
standardHeaders: true,
|
||||||
legacyHeaders: false,
|
legacyHeaders: false,
|
||||||
keyGenerator: (req, res) => {
|
keyGenerator: (req, res) => {
|
||||||
|
|||||||
10
cli/go.mod
10
cli/go.mod
@@ -5,6 +5,7 @@ go 1.19
|
|||||||
require (
|
require (
|
||||||
github.com/99designs/keyring v1.2.2
|
github.com/99designs/keyring v1.2.2
|
||||||
github.com/charmbracelet/lipgloss v0.5.0
|
github.com/charmbracelet/lipgloss v0.5.0
|
||||||
|
github.com/denisbrodbeck/machineid v1.0.1
|
||||||
github.com/fatih/semgroup v1.2.0
|
github.com/fatih/semgroup v1.2.0
|
||||||
github.com/gitleaks/go-gitdiff v0.8.0
|
github.com/gitleaks/go-gitdiff v0.8.0
|
||||||
github.com/h2non/filetype v1.1.3
|
github.com/h2non/filetype v1.1.3
|
||||||
@@ -14,12 +15,13 @@ require (
|
|||||||
github.com/muesli/reflow v0.3.0
|
github.com/muesli/reflow v0.3.0
|
||||||
github.com/muesli/roff v0.1.0
|
github.com/muesli/roff v0.1.0
|
||||||
github.com/petar-dambovaliev/aho-corasick v0.0.0-20211021192214-5ab2d9280aa9
|
github.com/petar-dambovaliev/aho-corasick v0.0.0-20211021192214-5ab2d9280aa9
|
||||||
|
github.com/posthog/posthog-go v0.0.0-20221221115252-24dfed35d71a
|
||||||
github.com/rs/zerolog v1.26.1
|
github.com/rs/zerolog v1.26.1
|
||||||
github.com/spf13/cobra v1.6.1
|
github.com/spf13/cobra v1.6.1
|
||||||
github.com/spf13/viper v1.8.1
|
github.com/spf13/viper v1.8.1
|
||||||
github.com/stretchr/testify v1.8.0
|
github.com/stretchr/testify v1.8.0
|
||||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d
|
||||||
golang.org/x/term v0.5.0
|
golang.org/x/term v0.9.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -28,7 +30,6 @@ require (
|
|||||||
github.com/chzyer/readline v1.5.1 // indirect
|
github.com/chzyer/readline v1.5.1 // indirect
|
||||||
github.com/danieljoos/wincred v1.1.2 // indirect
|
github.com/danieljoos/wincred v1.1.2 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/denisbrodbeck/machineid v1.0.1 // indirect
|
|
||||||
github.com/dvsekhvalnov/jose2go v1.5.0 // indirect
|
github.com/dvsekhvalnov/jose2go v1.5.0 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
||||||
github.com/go-openapi/errors v0.20.2 // indirect
|
github.com/go-openapi/errors v0.20.2 // indirect
|
||||||
@@ -47,9 +48,10 @@ require (
|
|||||||
github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0 // indirect
|
github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0 // indirect
|
||||||
github.com/oklog/ulid v1.3.1 // indirect
|
github.com/oklog/ulid v1.3.1 // indirect
|
||||||
github.com/pelletier/go-toml v1.9.3 // indirect
|
github.com/pelletier/go-toml v1.9.3 // indirect
|
||||||
|
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/posthog/posthog-go v0.0.0-20221221115252-24dfed35d71a // indirect
|
|
||||||
github.com/rivo/uniseg v0.2.0 // indirect
|
github.com/rivo/uniseg v0.2.0 // indirect
|
||||||
|
github.com/rs/cors v1.9.0 // indirect
|
||||||
github.com/spf13/afero v1.6.0 // indirect
|
github.com/spf13/afero v1.6.0 // indirect
|
||||||
github.com/spf13/cast v1.3.1 // indirect
|
github.com/spf13/cast v1.3.1 // indirect
|
||||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||||
@@ -58,7 +60,7 @@ require (
|
|||||||
go.mongodb.org/mongo-driver v1.10.0 // indirect
|
go.mongodb.org/mongo-driver v1.10.0 // indirect
|
||||||
golang.org/x/net v0.7.0 // indirect
|
golang.org/x/net v0.7.0 // indirect
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||||
golang.org/x/sys v0.5.0 // indirect
|
golang.org/x/sys v0.9.0 // indirect
|
||||||
golang.org/x/text v0.7.0 // indirect
|
golang.org/x/text v0.7.0 // indirect
|
||||||
gopkg.in/ini.v1 v1.62.0 // indirect
|
gopkg.in/ini.v1 v1.62.0 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
|||||||
@@ -283,6 +283,8 @@ github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5d
|
|||||||
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||||
github.com/petar-dambovaliev/aho-corasick v0.0.0-20211021192214-5ab2d9280aa9 h1:lL+y4Xv20pVlCGyLzNHRC0I0rIHhIL1lTvHizoS/dU8=
|
github.com/petar-dambovaliev/aho-corasick v0.0.0-20211021192214-5ab2d9280aa9 h1:lL+y4Xv20pVlCGyLzNHRC0I0rIHhIL1lTvHizoS/dU8=
|
||||||
github.com/petar-dambovaliev/aho-corasick v0.0.0-20211021192214-5ab2d9280aa9/go.mod h1:EHPiTAKtiFmrMldLUNswFwfZ2eJIYBHktdaUTZxYWRw=
|
github.com/petar-dambovaliev/aho-corasick v0.0.0-20211021192214-5ab2d9280aa9/go.mod h1:EHPiTAKtiFmrMldLUNswFwfZ2eJIYBHktdaUTZxYWRw=
|
||||||
|
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
|
||||||
|
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
||||||
@@ -297,6 +299,8 @@ github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
|||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
|
github.com/rs/cors v1.9.0 h1:l9HGsTsHJcvW14Nk7J9KFz8bzeAWXn3CG6bgt7LsrAE=
|
||||||
|
github.com/rs/cors v1.9.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
|
||||||
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||||
github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc=
|
github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc=
|
||||||
github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc=
|
github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc=
|
||||||
@@ -520,15 +524,20 @@ golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
|
||||||
|
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
|
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
|
golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28=
|
||||||
|
golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
|||||||
@@ -6,10 +6,15 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
@@ -22,10 +27,13 @@ import (
|
|||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
"github.com/go-resty/resty/v2"
|
"github.com/go-resty/resty/v2"
|
||||||
"github.com/manifoldco/promptui"
|
"github.com/manifoldco/promptui"
|
||||||
|
"github.com/pkg/browser"
|
||||||
"github.com/posthog/posthog-go"
|
"github.com/posthog/posthog-go"
|
||||||
|
"github.com/rs/cors"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/crypto/argon2"
|
"golang.org/x/crypto/argon2"
|
||||||
|
"golang.org/x/term"
|
||||||
)
|
)
|
||||||
|
|
||||||
type params struct {
|
type params struct {
|
||||||
@@ -39,6 +47,7 @@ type params struct {
|
|||||||
const ADD_USER = "Add a new account login"
|
const ADD_USER = "Add a new account login"
|
||||||
const REPLACE_USER = "Override current logged in user"
|
const REPLACE_USER = "Override current logged in user"
|
||||||
const EXIT_USER_MENU = "Exit"
|
const EXIT_USER_MENU = "Exit"
|
||||||
|
const QUIT_BROWSER_LOGIN = "q"
|
||||||
|
|
||||||
// loginCmd represents the login command
|
// loginCmd represents the login command
|
||||||
var loginCmd = &cobra.Command{
|
var loginCmd = &cobra.Command{
|
||||||
@@ -89,177 +98,35 @@ var loginCmd = &cobra.Command{
|
|||||||
util.HandleError(err, "Unable to parse domain url")
|
util.HandleError(err, "Unable to parse domain url")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var userCredentialsToBeStored models.UserCredentials
|
||||||
|
|
||||||
email, password, err := askForLoginCredentials()
|
interactiveLogin := false
|
||||||
if err != nil {
|
if cmd.Flags().Changed("interactive") {
|
||||||
util.HandleError(err, "Unable to parse email and password for authentication")
|
interactiveLogin = true
|
||||||
|
cliDefaultLogin(&userCredentialsToBeStored)
|
||||||
}
|
}
|
||||||
|
|
||||||
loginOneResponse, loginTwoResponse, err := getFreshUserCredentials(email, password)
|
//call browser login function
|
||||||
if err != nil {
|
if !interactiveLogin {
|
||||||
log.Warn().Msg("Unable to authenticate with the provided credentials, please ensure your email and password are correct")
|
fmt.Printf("\nLogging in via browser... Hit '%s' to cancel\n", QUIT_BROWSER_LOGIN)
|
||||||
log.Debug().Err(err)
|
userCredentialsToBeStored, err = browserCliLogin()
|
||||||
return
|
if err != nil {
|
||||||
}
|
//default to cli login on error
|
||||||
|
cliDefaultLogin(&userCredentialsToBeStored)
|
||||||
if loginTwoResponse.MfaEnabled {
|
|
||||||
i := 1
|
|
||||||
for i < 6 {
|
|
||||||
mfaVerifyCode := askForMFACode()
|
|
||||||
|
|
||||||
httpClient := resty.New()
|
|
||||||
httpClient.SetAuthToken(loginTwoResponse.Token)
|
|
||||||
verifyMFAresponse, mfaErrorResponse, requestError := api.CallVerifyMfaToken(httpClient, api.VerifyMfaTokenRequest{
|
|
||||||
Email: email,
|
|
||||||
MFAToken: mfaVerifyCode,
|
|
||||||
})
|
|
||||||
|
|
||||||
if requestError != nil {
|
|
||||||
util.HandleError(err)
|
|
||||||
break
|
|
||||||
} else if mfaErrorResponse != nil {
|
|
||||||
if mfaErrorResponse.Context.Code == "mfa_invalid" {
|
|
||||||
msg := fmt.Sprintf("Incorrect, verification code. You have %v attempts left", 5-i)
|
|
||||||
fmt.Println(msg)
|
|
||||||
if i == 5 {
|
|
||||||
util.PrintErrorMessageAndExit("No tries left, please try again in a bit")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if mfaErrorResponse.Context.Code == "mfa_expired" {
|
|
||||||
util.PrintErrorMessageAndExit("Your 2FA verification code has expired, please try logging in again")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
} else {
|
|
||||||
loginTwoResponse.EncryptedPrivateKey = verifyMFAresponse.EncryptedPrivateKey
|
|
||||||
loginTwoResponse.EncryptionVersion = verifyMFAresponse.EncryptionVersion
|
|
||||||
loginTwoResponse.Iv = verifyMFAresponse.Iv
|
|
||||||
loginTwoResponse.ProtectedKey = verifyMFAresponse.ProtectedKey
|
|
||||||
loginTwoResponse.ProtectedKeyIV = verifyMFAresponse.ProtectedKeyIV
|
|
||||||
loginTwoResponse.ProtectedKeyTag = verifyMFAresponse.ProtectedKeyTag
|
|
||||||
loginTwoResponse.PublicKey = verifyMFAresponse.PublicKey
|
|
||||||
loginTwoResponse.Tag = verifyMFAresponse.Tag
|
|
||||||
loginTwoResponse.Token = verifyMFAresponse.Token
|
|
||||||
loginTwoResponse.EncryptionVersion = verifyMFAresponse.EncryptionVersion
|
|
||||||
loginTwoResponse.RefreshToken = verifyMFAresponse.RefreshToken
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var decryptedPrivateKey []byte
|
err = util.StoreUserCredsInKeyRing(&userCredentialsToBeStored)
|
||||||
|
|
||||||
if loginTwoResponse.EncryptionVersion == 1 {
|
|
||||||
log.Debug().Msg("Login version 1")
|
|
||||||
encryptedPrivateKey, _ := base64.StdEncoding.DecodeString(loginTwoResponse.EncryptedPrivateKey)
|
|
||||||
tag, err := base64.StdEncoding.DecodeString(loginTwoResponse.Tag)
|
|
||||||
if err != nil {
|
|
||||||
util.HandleError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
IV, err := base64.StdEncoding.DecodeString(loginTwoResponse.Iv)
|
|
||||||
if err != nil {
|
|
||||||
util.HandleError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
paddedPassword := fmt.Sprintf("%032s", password)
|
|
||||||
key := []byte(paddedPassword)
|
|
||||||
|
|
||||||
computedDecryptedPrivateKey, err := crypto.DecryptSymmetric(key, encryptedPrivateKey, tag, IV)
|
|
||||||
if err != nil || len(computedDecryptedPrivateKey) == 0 {
|
|
||||||
util.HandleError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
decryptedPrivateKey = computedDecryptedPrivateKey
|
|
||||||
|
|
||||||
} else if loginTwoResponse.EncryptionVersion == 2 {
|
|
||||||
log.Debug().Msg("Login version 2")
|
|
||||||
protectedKey, err := base64.StdEncoding.DecodeString(loginTwoResponse.ProtectedKey)
|
|
||||||
if err != nil {
|
|
||||||
util.HandleError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
protectedKeyTag, err := base64.StdEncoding.DecodeString(loginTwoResponse.ProtectedKeyTag)
|
|
||||||
if err != nil {
|
|
||||||
util.HandleError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
protectedKeyIV, err := base64.StdEncoding.DecodeString(loginTwoResponse.ProtectedKeyIV)
|
|
||||||
if err != nil {
|
|
||||||
util.HandleError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
nonProtectedTag, err := base64.StdEncoding.DecodeString(loginTwoResponse.Tag)
|
|
||||||
if err != nil {
|
|
||||||
util.HandleError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
nonProtectedIv, err := base64.StdEncoding.DecodeString(loginTwoResponse.Iv)
|
|
||||||
if err != nil {
|
|
||||||
util.HandleError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
parameters := ¶ms{
|
|
||||||
memory: 64 * 1024,
|
|
||||||
iterations: 3,
|
|
||||||
parallelism: 1,
|
|
||||||
keyLength: 32,
|
|
||||||
}
|
|
||||||
|
|
||||||
derivedKey, err := generateFromPassword(password, []byte(loginOneResponse.Salt), parameters)
|
|
||||||
if err != nil {
|
|
||||||
util.HandleError(fmt.Errorf("unable to generate argon hash from password [err=%s]", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
decryptedProtectedKey, err := crypto.DecryptSymmetric(derivedKey, protectedKey, protectedKeyTag, protectedKeyIV)
|
|
||||||
if err != nil {
|
|
||||||
util.HandleError(fmt.Errorf("unable to get decrypted protected key [err=%s]", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
encryptedPrivateKey, err := base64.StdEncoding.DecodeString(loginTwoResponse.EncryptedPrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
util.HandleError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
decryptedProtectedKeyInHex, err := hex.DecodeString(string(decryptedProtectedKey))
|
|
||||||
if err != nil {
|
|
||||||
util.HandleError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
computedDecryptedPrivateKey, err := crypto.DecryptSymmetric(decryptedProtectedKeyInHex, encryptedPrivateKey, nonProtectedTag, nonProtectedIv)
|
|
||||||
if err != nil {
|
|
||||||
util.HandleError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
decryptedPrivateKey = computedDecryptedPrivateKey
|
|
||||||
} else {
|
|
||||||
util.PrintErrorMessageAndExit("Insufficient details to decrypt private key")
|
|
||||||
}
|
|
||||||
|
|
||||||
if string(decryptedPrivateKey) == "" || email == "" || loginTwoResponse.Token == "" {
|
|
||||||
log.Debug().Msgf("[decryptedPrivateKey=%s] [email=%s] [loginTwoResponse.Token=%s]", string(decryptedPrivateKey), email, loginTwoResponse.Token)
|
|
||||||
util.PrintErrorMessageAndExit("We were unable to fetch required details to complete your login. Run with -d to see more info")
|
|
||||||
}
|
|
||||||
|
|
||||||
userCredentialsToBeStored := &models.UserCredentials{
|
|
||||||
Email: email,
|
|
||||||
PrivateKey: string(decryptedPrivateKey),
|
|
||||||
JTWToken: loginTwoResponse.Token,
|
|
||||||
RefreshToken: loginTwoResponse.RefreshToken,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = util.StoreUserCredsInKeyRing(userCredentialsToBeStored)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
currentVault, _ := util.GetCurrentVaultBackend()
|
currentVault, _ := util.GetCurrentVaultBackend()
|
||||||
log.Error().Msgf("Unable to store your credentials in system vault [%s]. Rerun with flag -d to see full logs", currentVault)
|
log.Error().Msgf("Unable to store your credentials in system vault [%s]. Rerun with flag -d to see full logs", currentVault)
|
||||||
log.Error().Msgf("\nTo trouble shoot further, read https://infisical.com/docs/cli/faq")
|
log.Error().Msgf("\nTo trouble shoot further, read https://infisical.com/docs/cli/faq")
|
||||||
log.Debug().Err(err)
|
log.Debug().Err(err)
|
||||||
return
|
//return here
|
||||||
|
util.HandleError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = util.WriteInitalConfig(userCredentialsToBeStored)
|
err = util.WriteInitalConfig(&userCredentialsToBeStored)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.HandleError(err, "Unable to write write to Infisical Config file. Please try again")
|
util.HandleError(err, "Unable to write write to Infisical Config file. Please try again")
|
||||||
}
|
}
|
||||||
@@ -269,8 +136,9 @@ var loginCmd = &cobra.Command{
|
|||||||
|
|
||||||
whilte := color.New(color.FgGreen)
|
whilte := color.New(color.FgGreen)
|
||||||
boldWhite := whilte.Add(color.Bold)
|
boldWhite := whilte.Add(color.Bold)
|
||||||
|
time.Sleep(time.Second * 1)
|
||||||
boldWhite.Printf(">>>> Welcome to Infisical!")
|
boldWhite.Printf(">>>> Welcome to Infisical!")
|
||||||
boldWhite.Printf(" You are now logged in as %v <<<< \n", email)
|
boldWhite.Printf(" You are now logged in as %v <<<< \n", userCredentialsToBeStored.Email)
|
||||||
|
|
||||||
plainBold := color.New(color.Bold)
|
plainBold := color.New(color.Bold)
|
||||||
|
|
||||||
@@ -281,8 +149,170 @@ var loginCmd = &cobra.Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func cliDefaultLogin(userCredentialsToBeStored *models.UserCredentials) {
|
||||||
|
email, password, err := askForLoginCredentials()
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err, "Unable to parse email and password for authentication")
|
||||||
|
}
|
||||||
|
|
||||||
|
loginOneResponse, loginTwoResponse, err := getFreshUserCredentials(email, password)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Unable to authenticate with the provided credentials, please try again")
|
||||||
|
log.Debug().Err(err)
|
||||||
|
//return here
|
||||||
|
util.HandleError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if loginTwoResponse.MfaEnabled {
|
||||||
|
i := 1
|
||||||
|
for i < 6 {
|
||||||
|
mfaVerifyCode := askForMFACode()
|
||||||
|
|
||||||
|
httpClient := resty.New()
|
||||||
|
httpClient.SetAuthToken(loginTwoResponse.Token)
|
||||||
|
verifyMFAresponse, mfaErrorResponse, requestError := api.CallVerifyMfaToken(httpClient, api.VerifyMfaTokenRequest{
|
||||||
|
Email: email,
|
||||||
|
MFAToken: mfaVerifyCode,
|
||||||
|
})
|
||||||
|
|
||||||
|
if requestError != nil {
|
||||||
|
util.HandleError(err)
|
||||||
|
break
|
||||||
|
} else if mfaErrorResponse != nil {
|
||||||
|
if mfaErrorResponse.Context.Code == "mfa_invalid" {
|
||||||
|
msg := fmt.Sprintf("Incorrect, verification code. You have %v attempts left", 5-i)
|
||||||
|
fmt.Println(msg)
|
||||||
|
if i == 5 {
|
||||||
|
util.PrintErrorMessageAndExit("No tries left, please try again in a bit")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if mfaErrorResponse.Context.Code == "mfa_expired" {
|
||||||
|
util.PrintErrorMessageAndExit("Your 2FA verification code has expired, please try logging in again")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
} else {
|
||||||
|
loginTwoResponse.EncryptedPrivateKey = verifyMFAresponse.EncryptedPrivateKey
|
||||||
|
loginTwoResponse.EncryptionVersion = verifyMFAresponse.EncryptionVersion
|
||||||
|
loginTwoResponse.Iv = verifyMFAresponse.Iv
|
||||||
|
loginTwoResponse.ProtectedKey = verifyMFAresponse.ProtectedKey
|
||||||
|
loginTwoResponse.ProtectedKeyIV = verifyMFAresponse.ProtectedKeyIV
|
||||||
|
loginTwoResponse.ProtectedKeyTag = verifyMFAresponse.ProtectedKeyTag
|
||||||
|
loginTwoResponse.PublicKey = verifyMFAresponse.PublicKey
|
||||||
|
loginTwoResponse.Tag = verifyMFAresponse.Tag
|
||||||
|
loginTwoResponse.Token = verifyMFAresponse.Token
|
||||||
|
loginTwoResponse.EncryptionVersion = verifyMFAresponse.EncryptionVersion
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var decryptedPrivateKey []byte
|
||||||
|
|
||||||
|
if loginTwoResponse.EncryptionVersion == 1 {
|
||||||
|
log.Debug().Msg("Login version 1")
|
||||||
|
encryptedPrivateKey, _ := base64.StdEncoding.DecodeString(loginTwoResponse.EncryptedPrivateKey)
|
||||||
|
tag, err := base64.StdEncoding.DecodeString(loginTwoResponse.Tag)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
IV, err := base64.StdEncoding.DecodeString(loginTwoResponse.Iv)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
paddedPassword := fmt.Sprintf("%032s", password)
|
||||||
|
key := []byte(paddedPassword)
|
||||||
|
|
||||||
|
computedDecryptedPrivateKey, err := crypto.DecryptSymmetric(key, encryptedPrivateKey, tag, IV)
|
||||||
|
if err != nil || len(computedDecryptedPrivateKey) == 0 {
|
||||||
|
util.HandleError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
decryptedPrivateKey = computedDecryptedPrivateKey
|
||||||
|
|
||||||
|
} else if loginTwoResponse.EncryptionVersion == 2 {
|
||||||
|
log.Debug().Msg("Login version 2")
|
||||||
|
protectedKey, err := base64.StdEncoding.DecodeString(loginTwoResponse.ProtectedKey)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
protectedKeyTag, err := base64.StdEncoding.DecodeString(loginTwoResponse.ProtectedKeyTag)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
protectedKeyIV, err := base64.StdEncoding.DecodeString(loginTwoResponse.ProtectedKeyIV)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
nonProtectedTag, err := base64.StdEncoding.DecodeString(loginTwoResponse.Tag)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
nonProtectedIv, err := base64.StdEncoding.DecodeString(loginTwoResponse.Iv)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
parameters := ¶ms{
|
||||||
|
memory: 64 * 1024,
|
||||||
|
iterations: 3,
|
||||||
|
parallelism: 1,
|
||||||
|
keyLength: 32,
|
||||||
|
}
|
||||||
|
|
||||||
|
derivedKey, err := generateFromPassword(password, []byte(loginOneResponse.Salt), parameters)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(fmt.Errorf("unable to generate argon hash from password [err=%s]", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
decryptedProtectedKey, err := crypto.DecryptSymmetric(derivedKey, protectedKey, protectedKeyTag, protectedKeyIV)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(fmt.Errorf("unable to get decrypted protected key [err=%s]", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptedPrivateKey, err := base64.StdEncoding.DecodeString(loginTwoResponse.EncryptedPrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
decryptedProtectedKeyInHex, err := hex.DecodeString(string(decryptedProtectedKey))
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
computedDecryptedPrivateKey, err := crypto.DecryptSymmetric(decryptedProtectedKeyInHex, encryptedPrivateKey, nonProtectedTag, nonProtectedIv)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
decryptedPrivateKey = computedDecryptedPrivateKey
|
||||||
|
} else {
|
||||||
|
util.PrintErrorMessageAndExit("Insufficient details to decrypt private key")
|
||||||
|
}
|
||||||
|
|
||||||
|
if string(decryptedPrivateKey) == "" || email == "" || loginTwoResponse.Token == "" {
|
||||||
|
log.Debug().Msgf("[decryptedPrivateKey=%s] [email=%s] [loginTwoResponse.Token=%s]", string(decryptedPrivateKey), email, loginTwoResponse.Token)
|
||||||
|
util.PrintErrorMessageAndExit("We were unable to fetch required details to complete your login. Run with -d to see more info")
|
||||||
|
}
|
||||||
|
|
||||||
|
//updating usercredentials
|
||||||
|
userCredentialsToBeStored.Email = email
|
||||||
|
userCredentialsToBeStored.PrivateKey = string(decryptedPrivateKey)
|
||||||
|
userCredentialsToBeStored.JTWToken = loginTwoResponse.Token
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(loginCmd)
|
rootCmd.AddCommand(loginCmd)
|
||||||
|
loginCmd.Flags().BoolP("interactive", "i", false, "login via the command line")
|
||||||
}
|
}
|
||||||
|
|
||||||
func DomainOverridePrompt() (bool, error) {
|
func DomainOverridePrompt() (bool, error) {
|
||||||
@@ -327,7 +357,8 @@ func askForDomain() error {
|
|||||||
|
|
||||||
if selectedHostingOption == INFISICAL_CLOUD {
|
if selectedHostingOption == INFISICAL_CLOUD {
|
||||||
//cloud option
|
//cloud option
|
||||||
config.INFISICAL_URL = util.INFISICAL_DEFAULT_API_URL
|
config.INFISICAL_URL = fmt.Sprintf("%s/api", util.INFISICAL_DEFAULT_URL)
|
||||||
|
config.INFISICAL_LOGIN_URL = fmt.Sprintf("%s/login", util.INFISICAL_DEFAULT_URL)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -342,7 +373,7 @@ func askForDomain() error {
|
|||||||
domainPrompt := promptui.Prompt{
|
domainPrompt := promptui.Prompt{
|
||||||
Label: "Domain",
|
Label: "Domain",
|
||||||
Validate: urlValidation,
|
Validate: urlValidation,
|
||||||
Default: "Example - https://my-self-hosted-instance.com/api",
|
Default: "Example - https://my-self-hosted-instance.com",
|
||||||
}
|
}
|
||||||
|
|
||||||
domain, err := domainPrompt.Run()
|
domain, err := domainPrompt.Run()
|
||||||
@@ -350,8 +381,9 @@ func askForDomain() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
//set api url
|
//set api and login url
|
||||||
config.INFISICAL_URL = domain
|
config.INFISICAL_URL = fmt.Sprintf("%s/api", domain)
|
||||||
|
config.INFISICAL_LOGIN_URL = fmt.Sprintf("%s/login", domain)
|
||||||
//return nil
|
//return nil
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -365,6 +397,7 @@ func askForLoginCredentials() (email string, password string, err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Println("Enter Credentials...")
|
||||||
emailPrompt := promptui.Prompt{
|
emailPrompt := promptui.Prompt{
|
||||||
Label: "Email",
|
Label: "Email",
|
||||||
Validate: validateEmail,
|
Validate: validateEmail,
|
||||||
@@ -477,3 +510,121 @@ func askForMFACode() string {
|
|||||||
|
|
||||||
return mfaVerifyCode
|
return mfaVerifyCode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Manages the browser login flow.
|
||||||
|
// Returns a UserCredentials object on success and an error on failure
|
||||||
|
func browserCliLogin() (models.UserCredentials, error) {
|
||||||
|
SERVER_TIMEOUT := 60 * 10
|
||||||
|
|
||||||
|
//create listener
|
||||||
|
listener, err := net.Listen("tcp", "localhost:0")
|
||||||
|
if err != nil {
|
||||||
|
return models.UserCredentials{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
//get callback port
|
||||||
|
callbackPort := listener.Addr().(*net.TCPAddr).Port
|
||||||
|
url := fmt.Sprintf("%s?callback_port=%d", config.INFISICAL_LOGIN_URL, callbackPort)
|
||||||
|
|
||||||
|
//open browser and login
|
||||||
|
err = browser.OpenURL(url)
|
||||||
|
if err != nil {
|
||||||
|
return models.UserCredentials{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
//flow channels
|
||||||
|
success := make(chan models.UserCredentials)
|
||||||
|
failure := make(chan error)
|
||||||
|
timeout := time.After(time.Second * time.Duration(SERVER_TIMEOUT))
|
||||||
|
quit := make(chan bool)
|
||||||
|
|
||||||
|
//terminal state
|
||||||
|
var oldState term.State
|
||||||
|
|
||||||
|
//create handler
|
||||||
|
c := cors.New(cors.Options{
|
||||||
|
AllowedOrigins: []string{strings.ReplaceAll(config.INFISICAL_LOGIN_URL, "/login", "")},
|
||||||
|
AllowCredentials: true,
|
||||||
|
AllowedMethods: []string{"POST", "OPTIONS"},
|
||||||
|
AllowedHeaders: []string{"Content-Type"},
|
||||||
|
Debug: false,
|
||||||
|
})
|
||||||
|
corsHandler := c.Handler(browserLoginHandler(success, failure))
|
||||||
|
|
||||||
|
log.Debug().Msgf("Callback server listening on port %d", callbackPort)
|
||||||
|
go quitBrowserLogin(quit, &oldState)
|
||||||
|
go http.Serve(listener, corsHandler)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case loginResponse := <-success:
|
||||||
|
err = closeListener(&listener)
|
||||||
|
restoreTerminal(&oldState)
|
||||||
|
return loginResponse, nil
|
||||||
|
|
||||||
|
case err = <-failure:
|
||||||
|
err = closeListener(&listener)
|
||||||
|
restoreTerminal(&oldState)
|
||||||
|
return models.UserCredentials{}, err
|
||||||
|
|
||||||
|
case _ = <-timeout:
|
||||||
|
err = closeListener(&listener)
|
||||||
|
restoreTerminal(&oldState)
|
||||||
|
return models.UserCredentials{}, errors.New("server timeout")
|
||||||
|
|
||||||
|
case _ = <-quit:
|
||||||
|
return models.UserCredentials{}, errors.New("quitting browser login, defaulting to cli...")
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func restoreTerminal(oldState *term.State) {
|
||||||
|
term.Restore(int(os.Stdin.Fd()), oldState)
|
||||||
|
}
|
||||||
|
|
||||||
|
// listens to 'q' input on terminal and
|
||||||
|
// sends 'true' to 'quit' channel
|
||||||
|
func quitBrowserLogin(quit chan bool, oState *term.State) {
|
||||||
|
//
|
||||||
|
oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
*oState = *oldState
|
||||||
|
defer restoreTerminal(oldState)
|
||||||
|
b := make([]byte, 1)
|
||||||
|
for {
|
||||||
|
_, _ = os.Stdin.Read(b)
|
||||||
|
if string(b) == QUIT_BROWSER_LOGIN {
|
||||||
|
quit <- true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func closeListener(listener *net.Listener) error {
|
||||||
|
err := (*listener).Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Debug().Msg("Callback server shutdown successfully")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func browserLoginHandler(success chan models.UserCredentials, failure chan error) http.HandlerFunc {
|
||||||
|
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var loginResponse models.UserCredentials
|
||||||
|
|
||||||
|
decoder := json.NewDecoder(r.Body)
|
||||||
|
err := decoder.Decode(&loginResponse)
|
||||||
|
if err != nil {
|
||||||
|
failure <- err
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
success <- loginResponse
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,3 +2,4 @@ package config
|
|||||||
|
|
||||||
var INFISICAL_URL string
|
var INFISICAL_URL string
|
||||||
var INFISICAL_URL_MANUAL_OVERRIDE string
|
var INFISICAL_URL_MANUAL_OVERRIDE string
|
||||||
|
var INFISICAL_LOGIN_URL string
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ const (
|
|||||||
CONFIG_FILE_NAME = "infisical-config.json"
|
CONFIG_FILE_NAME = "infisical-config.json"
|
||||||
CONFIG_FOLDER_NAME = ".infisical"
|
CONFIG_FOLDER_NAME = ".infisical"
|
||||||
INFISICAL_DEFAULT_API_URL = "https://app.infisical.com/api"
|
INFISICAL_DEFAULT_API_URL = "https://app.infisical.com/api"
|
||||||
|
INFISICAL_DEFAULT_URL = "https://app.infisical.com"
|
||||||
INFISICAL_WORKSPACE_CONFIG_FILE_NAME = ".infisical.json"
|
INFISICAL_WORKSPACE_CONFIG_FILE_NAME = ".infisical.json"
|
||||||
INFISICAL_TOKEN_NAME = "INFISICAL_TOKEN"
|
INFISICAL_TOKEN_NAME = "INFISICAL_TOKEN"
|
||||||
SECRET_TYPE_PERSONAL = "personal"
|
SECRET_TYPE_PERSONAL = "personal"
|
||||||
|
|||||||
@@ -51,16 +51,10 @@ spec:
|
|||||||
# The host that should be used to pull secrets from. If left empty, the value specified in Global configuration will be used
|
# The host that should be used to pull secrets from. If left empty, the value specified in Global configuration will be used
|
||||||
hostAPI: https://app.infisical.com/api
|
hostAPI: https://app.infisical.com/api
|
||||||
authentication:
|
authentication:
|
||||||
serviceToken: # <-- option 1
|
serviceToken:
|
||||||
serviceTokenSecretReference:
|
serviceTokenSecretReference:
|
||||||
secretName: service-token
|
secretName: service-token
|
||||||
secretNamespace: option
|
secretNamespace: option
|
||||||
serviceAccount: # <-- method 2
|
|
||||||
serviceAccountSecretReference:
|
|
||||||
secretName: service-account
|
|
||||||
secretNamespace: default
|
|
||||||
projectId: "6439ec224cfbf7ea2a95b651"
|
|
||||||
environmentName: "dev"
|
|
||||||
managedSecretReference:
|
managedSecretReference:
|
||||||
secretName: managed-secret # <-- the name of kubernetes secret that will be created
|
secretName: managed-secret # <-- the name of kubernetes secret that will be created
|
||||||
secretNamespace: default # <-- where the kubernetes secret that will be created
|
secretNamespace: default # <-- where the kubernetes secret that will be created
|
||||||
@@ -86,7 +80,7 @@ spec:
|
|||||||
</Accordion>
|
</Accordion>
|
||||||
|
|
||||||
<Accordion title="authentication">
|
<Accordion title="authentication">
|
||||||
The `authentication` property tells the operator where it should look to find credentials needed to fetch secrets from Infisical. You can authenticate via two methods as described below.
|
The `authentication` property tells the operator where it should look to find credentials needed to fetch secrets from Infisical.
|
||||||
|
|
||||||
<Tabs>
|
<Tabs>
|
||||||
<Tab title="Service Token">
|
<Tab title="Service Token">
|
||||||
@@ -94,7 +88,7 @@ spec:
|
|||||||
|
|
||||||
#### 1. Generate service token
|
#### 1. Generate service token
|
||||||
|
|
||||||
You can generate a service token for an Infisical project by heading over to the Infisical dashboard then to Project Settings.
|
You can generate a [service token](../../documentation/platform/token) for an Infisical project by heading over to the Infisical dashboard then to Project Settings.
|
||||||
|
|
||||||
#### 2. Create Kubernetes secret containing service token
|
#### 2. Create Kubernetes secret containing service token
|
||||||
|
|
||||||
@@ -123,52 +117,6 @@ spec:
|
|||||||
secretNamespace: option # <-- namespace of the Kubernetes secret that stores our service token
|
secretNamespace: option # <-- namespace of the Kubernetes secret that stores our service token
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
</Tab>
|
|
||||||
<Tab title="Service Account">
|
|
||||||
We recommend authenticating with service account credentials when you have a large number of services. With this method, instead of creating a service token for each Infisical project you'd like to
|
|
||||||
fetch secrets from, you can fetch secrets from a number of Infisical projects with just one set of credentials.
|
|
||||||
|
|
||||||
#### 1. Generate service account
|
|
||||||
|
|
||||||
You can generate a service account by heading over to the organization settings. Once you create the service account, keep the credentials at hand for the next steps.
|
|
||||||
|
|
||||||
#### 2. Grant service account access to Infisical projects
|
|
||||||
|
|
||||||
Click on the pencil icon on the service account you just created and add the projects you'd like to be accessible via that service account.
|
|
||||||
|
|
||||||
#### 3. Store service account credentials in K8 secret
|
|
||||||
Next, we'll need to store the service account credentials in a kubernetes secret so that we can reference it in our InfisicalSecret CRD.
|
|
||||||
|
|
||||||
We recommend you create this kubernetes secret in a new namespace since you may need to reference it many times for each InfisicalSecret CRD you create.
|
|
||||||
|
|
||||||
To quickly create a Kubernetes secret containing the service account details, you can execute the command below after replacing it with your own service account credentials.
|
|
||||||
|
|
||||||
```
|
|
||||||
kubectl create secret generic service-token --from-literal=serviceAccountAccessKey=[REPLACE] --from-literal=serviceAccountPrivateKey=[REPLACE] --from-literal=serviceAccountPublicKey=[REPLACE]
|
|
||||||
```
|
|
||||||
|
|
||||||
Regardless of how you create the kubernetes secret containing the service account credentials, you will need to define values for the following keys in the secret: `serviceAccountAccessKey`, `serviceAccountPrivateKey`, and `serviceAccountPublicKey`
|
|
||||||
|
|
||||||
Once the secret is created, add the name and namespace of the secret that was just created under `authentication.serviceAccount.serviceAccountSecretReference` field in the InfisicalSecret CRD.
|
|
||||||
|
|
||||||
#### 4. Add projectId and environment from which to fetch secrets from
|
|
||||||
Add the Infisical project id and environment from which to fetch secrets for by providing values under `authentication.serviceAccount.projectId` and `authentication.serviceAccount.environmentName`.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
```yaml
|
|
||||||
apiVersion: secrets.infisical.com/v1alpha1
|
|
||||||
kind: InfisicalSecret
|
|
||||||
metadata:
|
|
||||||
name: infisicalsecret-sample-crd
|
|
||||||
spec:
|
|
||||||
serviceAccount:
|
|
||||||
serviceAccountSecretReference:
|
|
||||||
secretName: service-account
|
|
||||||
secretNamespace: default
|
|
||||||
projectId: "6439ec224cfbf7ea2a95b651"
|
|
||||||
environmentName: "dev"
|
|
||||||
```
|
|
||||||
</Tab>
|
</Tab>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const ContentSecurityPolicy = `
|
|||||||
style-src 'self' https://rsms.me 'unsafe-inline';
|
style-src 'self' https://rsms.me 'unsafe-inline';
|
||||||
child-src https://api.stripe.com;
|
child-src https://api.stripe.com;
|
||||||
frame-src https://js.stripe.com/ https://api.stripe.com https://www.youtube.com/;
|
frame-src https://js.stripe.com/ https://api.stripe.com https://www.youtube.com/;
|
||||||
connect-src 'self' wss://nexus-websocket-a.intercom.io https://api-iam.intercom.io https://api.heroku.com/ https://id.heroku.com/oauth/authorize https://id.heroku.com/oauth/token https://checkout.stripe.com https://app.posthog.com https://api.stripe.com;
|
connect-src 'self' wss://nexus-websocket-a.intercom.io https://api-iam.intercom.io https://api.heroku.com/ https://id.heroku.com/oauth/authorize https://id.heroku.com/oauth/token https://checkout.stripe.com https://app.posthog.com https://api.stripe.com http://localhost:*;
|
||||||
img-src 'self' https://static.intercomassets.com https://js.intercomcdn.com https://downloads.intercomcdn.com https://*.stripe.com https://i.ytimg.com/ data:;
|
img-src 'self' https://static.intercomassets.com https://js.intercomcdn.com https://downloads.intercomcdn.com https://*.stripe.com https://i.ytimg.com/ data:;
|
||||||
media-src https://js.intercomcdn.com;
|
media-src https://js.intercomcdn.com;
|
||||||
font-src 'self' https://fonts.intercomcdn.com/ https://maxcdn.bootstrapcdn.com https://rsms.me https://fonts.gstatic.com;
|
font-src 'self' https://fonts.intercomcdn.com/ https://maxcdn.bootstrapcdn.com https://rsms.me https://fonts.gstatic.com;
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ import { useState } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import axios from "axios"
|
||||||
|
|
||||||
import attemptLogin from "@app/components/utilities/attemptLogin";
|
import attemptLogin from "@app/components/utilities/attemptLogin";
|
||||||
|
|
||||||
import Error from "../basic/Error";
|
import Error from "../basic/Error";
|
||||||
|
import attemptCliLogin from "../utilities/attemptCliLogin";
|
||||||
// import { faGoogle } from '@fortawesome/free-brands-svg-icons';
|
// import { faGoogle } from '@fortawesome/free-brands-svg-icons';
|
||||||
// import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
// import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { Button, Input } from "../v2";
|
import { Button, Input } from "../v2";
|
||||||
@@ -31,33 +33,68 @@ export default function InitialLoginStep({
|
|||||||
|
|
||||||
const handleLogin = async () => {
|
const handleLogin = async () => {
|
||||||
try {
|
try {
|
||||||
if (!email || !password) {
|
if (!email || !password) {
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
setIsLoading(true);
|
|
||||||
const isLoginSuccessful = await attemptLogin({
|
|
||||||
email,
|
|
||||||
password,
|
|
||||||
});
|
|
||||||
if (isLoginSuccessful && isLoginSuccessful.success) {
|
|
||||||
// case: login was successful
|
|
||||||
|
|
||||||
if (isLoginSuccessful.mfaEnabled) {
|
|
||||||
// case: login requires MFA step
|
|
||||||
setStep(2);
|
|
||||||
setIsLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// case: login does not require MFA step
|
setIsLoading(true);
|
||||||
router.push(`/dashboard/${localStorage.getItem("projectData.id")}`);
|
const queryParams = new URLSearchParams(window.location.search)
|
||||||
}
|
if (queryParams && queryParams.get("callback_port")) {
|
||||||
|
const callbackPort = queryParams.get("callback_port")
|
||||||
|
|
||||||
|
// attemptCliLogin
|
||||||
|
const isCliLoginSuccessful = await attemptCliLogin({
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (isCliLoginSuccessful && isCliLoginSuccessful.success) {
|
||||||
|
|
||||||
|
if (isCliLoginSuccessful.mfaEnabled) {
|
||||||
|
// case: login requires MFA step
|
||||||
|
setStep(2);
|
||||||
|
setIsLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// case: login was successful
|
||||||
|
const cliUrl = `http://localhost:${callbackPort}`
|
||||||
|
|
||||||
|
// send request to server endpoint
|
||||||
|
const instance = axios.create()
|
||||||
|
const cliResp = await instance.post(cliUrl, { ...isCliLoginSuccessful.loginResponse })
|
||||||
|
console.log(cliResp)
|
||||||
|
|
||||||
|
// cli page
|
||||||
|
router.push("/cli-redirect");
|
||||||
|
|
||||||
|
// on success, router.push to cli Login Successful page
|
||||||
|
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const isLoginSuccessful = await attemptLogin({
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
});
|
||||||
|
if (isLoginSuccessful && isLoginSuccessful.success) {
|
||||||
|
// case: login was successful
|
||||||
|
|
||||||
|
if (isLoginSuccessful.mfaEnabled) {
|
||||||
|
// case: login requires MFA step
|
||||||
|
setStep(2);
|
||||||
|
setIsLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// case: login does not require MFA step
|
||||||
|
router.push(`/dashboard/${localStorage.getItem("projectData.id")}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setLoginError(true);
|
setLoginError(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,18 +127,18 @@ export default function InitialLoginStep({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="relative pt-2 md:pt-0 md:px-1.5 flex items-center justify-center w-1/4 lg:w-1/6 min-w-[21.3rem] md:min-w-[22rem] mx-auto rounded-lg max-h-24 md:max-h-28">
|
<div className="relative pt-2 md:pt-0 md:px-1.5 flex items-center justify-center w-1/4 lg:w-1/6 min-w-[21.3rem] md:min-w-[22rem] mx-auto rounded-lg max-h-24 md:max-h-28">
|
||||||
<div className="flex items-center justify-center w-full md:p-2 rounded-lg max-h-24 md:max-h-28">
|
<div className="flex items-center justify-center w-full md:p-2 rounded-lg max-h-24 md:max-h-28">
|
||||||
<Input
|
<Input
|
||||||
value={password}
|
value={password}
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="Enter your password..."
|
placeholder="Enter your password..."
|
||||||
isRequired
|
isRequired
|
||||||
autoComplete="current-password"
|
autoComplete="current-password"
|
||||||
id="current-password"
|
id="current-password"
|
||||||
className="h-12 select:-webkit-autofill:focus"
|
className="h-12 select:-webkit-autofill:focus"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{!isLoading && loginError && <Error text={t("login.error-login") ?? ""} />}
|
{!isLoading && loginError && <Error text={t("login.error-login") ?? ""} />}
|
||||||
<div className='lg:w-1/6 w-1/4 min-w-[21.2rem] md:min-w-[20.1rem] text-center rounded-md mt-4'>
|
<div className='lg:w-1/6 w-1/4 min-w-[21.2rem] md:min-w-[20.1rem] text-center rounded-md mt-4'>
|
||||||
@@ -116,18 +153,18 @@ export default function InitialLoginStep({
|
|||||||
> Login </Button>
|
> Login </Button>
|
||||||
</div>
|
</div>
|
||||||
<div className='lg:w-1/6 w-1/4 min-w-[20rem] flex flex-row items-center mt-4 py-2'>
|
<div className='lg:w-1/6 w-1/4 min-w-[20rem] flex flex-row items-center mt-4 py-2'>
|
||||||
<div className='w-1/2 border-t border-mineshaft-500'/>
|
<div className='w-1/2 border-t border-mineshaft-500' />
|
||||||
<span className='px-4 text-sm text-bunker-400'>or</span>
|
<span className='px-4 text-sm text-bunker-400'>or</span>
|
||||||
<div className='w-1/2 border-t border-mineshaft-500'/>
|
<div className='w-1/2 border-t border-mineshaft-500' />
|
||||||
</div>
|
</div>
|
||||||
<div className='lg:w-1/6 w-1/4 min-w-[20rem] text-center rounded-md mt-4'>
|
<div className='lg:w-1/6 w-1/4 min-w-[20rem] text-center rounded-md mt-4'>
|
||||||
<Button
|
<Button
|
||||||
colorSchema="primary"
|
colorSchema="primary"
|
||||||
variant="outline_bg"
|
variant="outline_bg"
|
||||||
onClick={() => router.push("/saml-sso")}
|
onClick={() => router.push("/saml-sso")}
|
||||||
isFullWidth
|
isFullWidth
|
||||||
className="h-14 w-full mx-0"
|
className="h-14 w-full mx-0"
|
||||||
>
|
>
|
||||||
Continue with SAML SSO
|
Continue with SAML SSO
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ import React, { useState } from "react";
|
|||||||
import ReactCodeInput from "react-code-input";
|
import ReactCodeInput from "react-code-input";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import axios from "axios"
|
||||||
|
|
||||||
|
import attemptCliLoginMfa from "@app/components/utilities/attemptCliLoginMfa"
|
||||||
import attemptLoginMfa from "@app/components/utilities/attemptLoginMfa";
|
import attemptLoginMfa from "@app/components/utilities/attemptLoginMfa";
|
||||||
import { useSendMfaToken } from "@app/hooks/api/auth";
|
import { useSendMfaToken } from "@app/hooks/api/auth";
|
||||||
|
|
||||||
@@ -76,17 +78,43 @@ export default function MFAStep({
|
|||||||
}
|
}
|
||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
const isLoginSuccessful = await attemptLoginMfa({
|
const queryParams = new URLSearchParams(window.location.search)
|
||||||
email,
|
if (queryParams && queryParams.get("callback_port")){
|
||||||
password,
|
const callbackPort = queryParams.get("callback_port")
|
||||||
providerAuthToken,
|
|
||||||
mfaToken: mfaCode
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isLoginSuccessful) {
|
// attemptCliLogin
|
||||||
setIsLoading(false);
|
const isCliLoginSuccessful = await attemptCliLoginMfa({
|
||||||
router.push(`/dashboard/${localStorage.getItem("projectData.id")}`);
|
email,
|
||||||
|
password,
|
||||||
|
providerAuthToken,
|
||||||
|
mfaToken: mfaCode
|
||||||
|
})
|
||||||
|
|
||||||
|
if (isCliLoginSuccessful && isCliLoginSuccessful.success){
|
||||||
|
// case: login was successful
|
||||||
|
const cliUrl = `http://localhost:${callbackPort}`
|
||||||
|
|
||||||
|
// send request to server endpoint
|
||||||
|
const instance = axios.create()
|
||||||
|
await instance.post(cliUrl,{...isCliLoginSuccessful.loginResponse,email})
|
||||||
|
|
||||||
|
// cli page
|
||||||
|
router.push("/cli-redirect");
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
const isLoginSuccessful = await attemptLoginMfa({
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
providerAuthToken,
|
||||||
|
mfaToken: mfaCode
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isLoginSuccessful) {
|
||||||
|
setIsLoading(false);
|
||||||
|
router.push(`/dashboard/${localStorage.getItem("projectData.id")}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const error = err as VerifyMfaTokenError;
|
const error = err as VerifyMfaTokenError;
|
||||||
|
|
||||||
|
|||||||
164
frontend/src/components/utilities/attemptCliLogin.ts
Normal file
164
frontend/src/components/utilities/attemptCliLogin.ts
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
/* eslint-disable prefer-destructuring */
|
||||||
|
import jsrp from "jsrp";
|
||||||
|
|
||||||
|
import login1 from "@app/pages/api/auth/Login1";
|
||||||
|
import login2 from "@app/pages/api/auth/Login2";
|
||||||
|
import getOrganizations from "@app/pages/api/organization/getOrgs";
|
||||||
|
import getOrganizationUserProjects from "@app/pages/api/organization/GetOrgUserProjects";
|
||||||
|
import KeyService from "@app/services/KeyService";
|
||||||
|
|
||||||
|
import Telemetry from "./telemetry/Telemetry";
|
||||||
|
import { saveTokenToLocalStorage } from "./saveTokenToLocalStorage";
|
||||||
|
import SecurityClient from "./SecurityClient";
|
||||||
|
|
||||||
|
// eslint-disable-next-line new-cap
|
||||||
|
const client = new jsrp.client();
|
||||||
|
|
||||||
|
interface IsCliLoginSuccessful {
|
||||||
|
mfaEnabled: boolean;
|
||||||
|
loginResponse?: {
|
||||||
|
email: string;
|
||||||
|
privateKey: string;
|
||||||
|
JTWToken: string;
|
||||||
|
};
|
||||||
|
success: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return whether or not login is successful for user with email [email]
|
||||||
|
* and password [password]
|
||||||
|
* @param {string} email - email of user to log in
|
||||||
|
* @param {string} password - password of user to log in
|
||||||
|
*/
|
||||||
|
const attemptLogin = async (
|
||||||
|
{
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
providerAuthToken,
|
||||||
|
}: {
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
providerAuthToken?: string;
|
||||||
|
}
|
||||||
|
): Promise<IsCliLoginSuccessful> => {
|
||||||
|
|
||||||
|
const telemetry = new Telemetry().getInstance();
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
client.init(
|
||||||
|
{
|
||||||
|
username: email,
|
||||||
|
password
|
||||||
|
},
|
||||||
|
async () => {
|
||||||
|
try {
|
||||||
|
const clientPublicKey = client.getPublicKey();
|
||||||
|
const { serverPublicKey, salt } = await login1({
|
||||||
|
email,
|
||||||
|
clientPublicKey,
|
||||||
|
providerAuthToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
client.setSalt(salt);
|
||||||
|
client.setServerPublicKey(serverPublicKey);
|
||||||
|
const clientProof = client.getProof(); // called M1
|
||||||
|
|
||||||
|
const {
|
||||||
|
mfaEnabled,
|
||||||
|
encryptionVersion,
|
||||||
|
protectedKey,
|
||||||
|
protectedKeyIV,
|
||||||
|
protectedKeyTag,
|
||||||
|
token,
|
||||||
|
publicKey,
|
||||||
|
encryptedPrivateKey,
|
||||||
|
iv,
|
||||||
|
tag
|
||||||
|
} = await login2(
|
||||||
|
{
|
||||||
|
email,
|
||||||
|
clientProof,
|
||||||
|
providerAuthToken,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (mfaEnabled) {
|
||||||
|
// case: MFA is enabled
|
||||||
|
|
||||||
|
// set temporary (MFA) JWT token
|
||||||
|
SecurityClient.setMfaToken(token);
|
||||||
|
|
||||||
|
resolve({
|
||||||
|
mfaEnabled,
|
||||||
|
success: true
|
||||||
|
});
|
||||||
|
} else if (
|
||||||
|
!mfaEnabled &&
|
||||||
|
encryptionVersion &&
|
||||||
|
encryptedPrivateKey &&
|
||||||
|
iv &&
|
||||||
|
tag &&
|
||||||
|
token
|
||||||
|
) {
|
||||||
|
// case: MFA is not enabled
|
||||||
|
|
||||||
|
// unset provider auth token in case it was used
|
||||||
|
SecurityClient.setProviderAuthToken("");
|
||||||
|
// set JWT token
|
||||||
|
SecurityClient.setToken(token);
|
||||||
|
|
||||||
|
const privateKey = await KeyService.decryptPrivateKey({
|
||||||
|
encryptionVersion,
|
||||||
|
encryptedPrivateKey,
|
||||||
|
iv,
|
||||||
|
tag,
|
||||||
|
password,
|
||||||
|
salt,
|
||||||
|
protectedKey,
|
||||||
|
protectedKeyIV,
|
||||||
|
protectedKeyTag
|
||||||
|
});
|
||||||
|
|
||||||
|
saveTokenToLocalStorage({
|
||||||
|
publicKey,
|
||||||
|
encryptedPrivateKey,
|
||||||
|
iv,
|
||||||
|
tag,
|
||||||
|
privateKey
|
||||||
|
});
|
||||||
|
|
||||||
|
const userOrgs = await getOrganizations();
|
||||||
|
const orgId = userOrgs[0]._id;
|
||||||
|
localStorage.setItem("orgData.id", orgId);
|
||||||
|
|
||||||
|
const orgUserProjects = await getOrganizationUserProjects({
|
||||||
|
orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (orgUserProjects.length > 0) {
|
||||||
|
localStorage.setItem("projectData.id", orgUserProjects[0]._id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (email) {
|
||||||
|
telemetry.identify(email, email);
|
||||||
|
telemetry.capture("User Logged In");
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve({
|
||||||
|
mfaEnabled: false,
|
||||||
|
loginResponse: {
|
||||||
|
email,
|
||||||
|
privateKey,
|
||||||
|
JTWToken: token
|
||||||
|
},
|
||||||
|
success: true
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default attemptLogin;
|
||||||
122
frontend/src/components/utilities/attemptCliLoginMfa.ts
Normal file
122
frontend/src/components/utilities/attemptCliLoginMfa.ts
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
/* eslint-disable prefer-destructuring */
|
||||||
|
import jsrp from "jsrp";
|
||||||
|
|
||||||
|
import login1 from "@app/pages/api/auth/Login1";
|
||||||
|
import verifyMfaToken from "@app/pages/api/auth/verifyMfaToken";
|
||||||
|
import getOrganizations from "@app/pages/api/organization/getOrgs";
|
||||||
|
import getOrganizationUserProjects from "@app/pages/api/organization/GetOrgUserProjects";
|
||||||
|
import KeyService from "@app/services/KeyService";
|
||||||
|
|
||||||
|
import { saveTokenToLocalStorage } from "./saveTokenToLocalStorage";
|
||||||
|
import SecurityClient from "./SecurityClient";
|
||||||
|
|
||||||
|
// eslint-disable-next-line new-cap
|
||||||
|
const client = new jsrp.client();
|
||||||
|
|
||||||
|
interface IsMfaLoginSuccessful {
|
||||||
|
success: boolean;
|
||||||
|
loginResponse:{
|
||||||
|
privateKey: string;
|
||||||
|
JTWToken: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return whether or not MFA-login is successful for user with email [email]
|
||||||
|
* and MFA token [mfaToken]
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {String} obj.email - email of user
|
||||||
|
* @param {String} obj.mfaToken - MFA code/token
|
||||||
|
*/
|
||||||
|
const attemptLoginMfa = async ({
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
providerAuthToken,
|
||||||
|
mfaToken
|
||||||
|
}: {
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
providerAuthToken?: string,
|
||||||
|
mfaToken: string;
|
||||||
|
}): Promise<IsMfaLoginSuccessful> => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
client.init({
|
||||||
|
username: email,
|
||||||
|
password
|
||||||
|
}, async () => {
|
||||||
|
try {
|
||||||
|
const clientPublicKey = client.getPublicKey();
|
||||||
|
const { salt } = await login1({
|
||||||
|
email,
|
||||||
|
clientPublicKey,
|
||||||
|
providerAuthToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
encryptionVersion,
|
||||||
|
protectedKey,
|
||||||
|
protectedKeyIV,
|
||||||
|
protectedKeyTag,
|
||||||
|
token,
|
||||||
|
publicKey,
|
||||||
|
encryptedPrivateKey,
|
||||||
|
iv,
|
||||||
|
tag
|
||||||
|
} = await verifyMfaToken({
|
||||||
|
email,
|
||||||
|
mfaToken
|
||||||
|
});
|
||||||
|
|
||||||
|
// unset temporary (MFA) JWT token and set JWT token
|
||||||
|
SecurityClient.setMfaToken("");
|
||||||
|
SecurityClient.setToken(token);
|
||||||
|
SecurityClient.setProviderAuthToken("");
|
||||||
|
|
||||||
|
const privateKey = await KeyService.decryptPrivateKey({
|
||||||
|
encryptionVersion,
|
||||||
|
encryptedPrivateKey,
|
||||||
|
iv,
|
||||||
|
tag,
|
||||||
|
password,
|
||||||
|
salt,
|
||||||
|
protectedKey,
|
||||||
|
protectedKeyIV,
|
||||||
|
protectedKeyTag
|
||||||
|
});
|
||||||
|
|
||||||
|
saveTokenToLocalStorage({
|
||||||
|
publicKey,
|
||||||
|
encryptedPrivateKey,
|
||||||
|
iv,
|
||||||
|
tag,
|
||||||
|
privateKey
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: in the future - move this logic elsewhere
|
||||||
|
// because this function is about logging the user in
|
||||||
|
// and not initializing the login details
|
||||||
|
const userOrgs = await getOrganizations();
|
||||||
|
const orgId = userOrgs[0]._id;
|
||||||
|
localStorage.setItem("orgData.id", orgId);
|
||||||
|
|
||||||
|
const orgUserProjects = await getOrganizationUserProjects({
|
||||||
|
orgId
|
||||||
|
});
|
||||||
|
localStorage.setItem("projectData.id", orgUserProjects[0]._id);
|
||||||
|
|
||||||
|
resolve({
|
||||||
|
success: true,
|
||||||
|
loginResponse:{
|
||||||
|
privateKey,
|
||||||
|
JTWToken: token
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default attemptLoginMfa;
|
||||||
@@ -24,7 +24,7 @@ const userKeys = {
|
|||||||
getOrgUsers: (orgId: string) => [{ orgId }, "user"]
|
getOrgUsers: (orgId: string) => [{ orgId }, "user"]
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchUserDetails = async () => {
|
export const fetchUserDetails = async () => {
|
||||||
const { data } = await apiRequest.get<{ user: User }>("/api/v1/user");
|
const { data } = await apiRequest.get<{ user: User }>("/api/v1/user");
|
||||||
|
|
||||||
return data.user;
|
return data.user;
|
||||||
|
|||||||
18
frontend/src/pages/cli-redirect.tsx
Normal file
18
frontend/src/pages/cli-redirect.tsx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import Head from "next/head";
|
||||||
|
|
||||||
|
export default function CliRedirect() {
|
||||||
|
return (
|
||||||
|
<div className='bg-bunker-800 md:h-screen flex flex-col justify-between'>
|
||||||
|
<Head>
|
||||||
|
<title>Infisical Cli | Login Successful!</title>
|
||||||
|
<link rel='icon' href='/infisical.ico' />
|
||||||
|
</Head>
|
||||||
|
<div className='flex flex-col items-center justify-center text-gray-200 h-screen w-screen'>
|
||||||
|
<p className='text-4xl mt-32'>Head back to your terminal!</p>
|
||||||
|
<p className='mt-2 mb-1 text-lg'>
|
||||||
|
You've successfully logged into infisical-cli
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -4,13 +4,16 @@ import Head from "next/head";
|
|||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import axios from "axios"
|
||||||
|
|
||||||
// import ListBox from '@app/components/basic/Listbox';
|
// import ListBox from '@app/components/basic/Listbox';
|
||||||
import InitialLoginStep from "@app/components/login/InitialLoginStep";
|
import InitialLoginStep from "@app/components/login/InitialLoginStep";
|
||||||
import MFAStep from "@app/components/login/MFAStep";
|
import MFAStep from "@app/components/login/MFAStep";
|
||||||
import PasswordInputStep from "@app/components/login/PasswordInputStep";
|
import PasswordInputStep from "@app/components/login/PasswordInputStep";
|
||||||
import { useProviderAuth } from "@app/hooks/useProviderAuth";
|
import { useProviderAuth } from "@app/hooks/useProviderAuth";
|
||||||
import { isLoggedIn } from "@app/reactQuery";
|
import { getAuthToken, isLoggedIn } from "@app/reactQuery";
|
||||||
|
|
||||||
|
import { fetchUserDetails } from "~/hooks/api/users/queries";
|
||||||
|
|
||||||
import getWorkspaces from "./api/workspace/getWorkspaces";
|
import getWorkspaces from "./api/workspace/getWorkspaces";
|
||||||
|
|
||||||
@@ -20,6 +23,7 @@ export default function Login() {
|
|||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
const [step, setStep] = useState(1);
|
const [step, setStep] = useState(1);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
// const lang = router.locale ?? 'en';
|
// const lang = router.locale ?? 'en';
|
||||||
const {
|
const {
|
||||||
providerAuthToken,
|
providerAuthToken,
|
||||||
@@ -44,6 +48,20 @@ export default function Login() {
|
|||||||
try {
|
try {
|
||||||
const userWorkspaces = await getWorkspaces();
|
const userWorkspaces = await getWorkspaces();
|
||||||
userWorkspace = userWorkspaces[0] && userWorkspaces[0]._id;
|
userWorkspace = userWorkspaces[0] && userWorkspaces[0]._id;
|
||||||
|
|
||||||
|
// user details
|
||||||
|
const userDetails = await fetchUserDetails()
|
||||||
|
// send details back to client
|
||||||
|
|
||||||
|
const queryParams = new URLSearchParams(window.location.search)
|
||||||
|
if (queryParams && queryParams.get("callback_port")) {
|
||||||
|
const callbackPort = queryParams.get("callback_port")
|
||||||
|
|
||||||
|
// send post request to cli with details
|
||||||
|
const cliUrl = `http://localhost:${callbackPort}`
|
||||||
|
const instance = axios.create()
|
||||||
|
await instance.post(cliUrl, { email: userDetails.email, privateKey: localStorage.getItem("PRIVATE_KEY"), JTWToken: getAuthToken() })
|
||||||
|
}
|
||||||
router.push(`/dashboard/${userWorkspace}`);
|
router.push(`/dashboard/${userWorkspace}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error - Not logged in yet");
|
console.log("Error - Not logged in yet");
|
||||||
@@ -69,7 +87,7 @@ export default function Login() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (loginStep === 1) {
|
if (loginStep === 1) {
|
||||||
return <InitialLoginStep
|
return <InitialLoginStep
|
||||||
setStep={setStep}
|
setStep={setStep}
|
||||||
email={email}
|
email={email}
|
||||||
setEmail={setEmail}
|
setEmail={setEmail}
|
||||||
|
|||||||
Reference in New Issue
Block a user