mirror of https://github.com/coder/coder.git
ci: enable nestif linter (#9363)
This commit is contained in:
parent
d29696296f
commit
8f3b4075c7
|
@ -131,7 +131,8 @@ linters-settings:
|
|||
- trialer
|
||||
|
||||
nestif:
|
||||
min-complexity: 4 # Min complexity of if statements (def 5, goal 4)
|
||||
# goal: 10
|
||||
min-complexity: 20
|
||||
|
||||
revive:
|
||||
# see https://github.com/mgechev/revive#available-rules for details.
|
||||
|
@ -237,6 +238,7 @@ linters:
|
|||
# create a good culture around cognitive complexity.
|
||||
# - gocyclo
|
||||
- gocognit
|
||||
- nestif
|
||||
- goimports
|
||||
- gomodguard
|
||||
- gosec
|
||||
|
|
165
cli/login.go
165
cli/login.go
|
@ -37,6 +37,95 @@ func init() {
|
|||
browser.Stdout = io.Discard
|
||||
}
|
||||
|
||||
func promptFirstUsername(inv *clibase.Invocation) (string, error) {
|
||||
currentUser, err := user.Current()
|
||||
if err != nil {
|
||||
return "", xerrors.Errorf("get current user: %w", err)
|
||||
}
|
||||
username, err := cliui.Prompt(inv, cliui.PromptOptions{
|
||||
Text: "What " + cliui.DefaultStyles.Field.Render("username") + " would you like?",
|
||||
Default: currentUser.Username,
|
||||
})
|
||||
if errors.Is(err, cliui.Canceled) {
|
||||
return "", nil
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return username, nil
|
||||
}
|
||||
|
||||
func promptFirstPassword(inv *clibase.Invocation) (string, error) {
|
||||
retry:
|
||||
password, err := cliui.Prompt(inv, cliui.PromptOptions{
|
||||
Text: "Enter a " + cliui.DefaultStyles.Field.Render("password") + ":",
|
||||
Secret: true,
|
||||
Validate: func(s string) error {
|
||||
return userpassword.Validate(s)
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return "", xerrors.Errorf("specify password prompt: %w", err)
|
||||
}
|
||||
confirm, err := cliui.Prompt(inv, cliui.PromptOptions{
|
||||
Text: "Confirm " + cliui.DefaultStyles.Field.Render("password") + ":",
|
||||
Secret: true,
|
||||
Validate: cliui.ValidateNotEmpty,
|
||||
})
|
||||
if err != nil {
|
||||
return "", xerrors.Errorf("confirm password prompt: %w", err)
|
||||
}
|
||||
|
||||
if confirm != password {
|
||||
_, _ = fmt.Fprintln(inv.Stdout, cliui.DefaultStyles.Error.Render("Passwords do not match"))
|
||||
goto retry
|
||||
}
|
||||
|
||||
return password, nil
|
||||
}
|
||||
|
||||
func (r *RootCmd) loginWithPassword(
|
||||
inv *clibase.Invocation,
|
||||
client *codersdk.Client,
|
||||
email, password string,
|
||||
) error {
|
||||
resp, err := client.LoginWithPassword(inv.Context(), codersdk.LoginWithPasswordRequest{
|
||||
Email: email,
|
||||
Password: password,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("login with password: %w", err)
|
||||
}
|
||||
|
||||
sessionToken := resp.SessionToken
|
||||
config := r.createConfig()
|
||||
err = config.Session().Write(sessionToken)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("write session token: %w", err)
|
||||
}
|
||||
|
||||
client.SetSessionToken(sessionToken)
|
||||
|
||||
// Nice side-effect: validates the token.
|
||||
u, err := client.User(inv.Context(), "me")
|
||||
if err != nil {
|
||||
return xerrors.Errorf("get user: %w", err)
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintf(
|
||||
inv.Stdout,
|
||||
cliui.DefaultStyles.Paragraph.Render(
|
||||
fmt.Sprintf(
|
||||
"Welcome to Coder, %s! You're authenticated.",
|
||||
cliui.DefaultStyles.Keyword.Render(u.Username),
|
||||
),
|
||||
)+"\n",
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RootCmd) login() *clibase.Cmd {
|
||||
const firstUserTrialEnv = "CODER_FIRST_USER_TRIAL"
|
||||
|
||||
|
@ -91,41 +180,30 @@ func (r *RootCmd) login() *clibase.Cmd {
|
|||
_, _ = fmt.Fprintln(inv.Stderr, cliui.DefaultStyles.Warn.Render(err.Error()))
|
||||
}
|
||||
|
||||
hasInitialUser, err := client.HasFirstUser(ctx)
|
||||
hasFirstUser, err := client.HasFirstUser(ctx)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("Failed to check server %q for first user, is the URL correct and is coder accessible from your browser? Error - has initial user: %w", serverURL.String(), err)
|
||||
}
|
||||
if !hasInitialUser {
|
||||
if !hasFirstUser {
|
||||
_, _ = fmt.Fprintf(inv.Stdout, Caret+"Your Coder deployment hasn't been set up!\n")
|
||||
|
||||
if username == "" {
|
||||
if !isTTY(inv) {
|
||||
return xerrors.New("the initial user cannot be created in non-interactive mode. use the API")
|
||||
}
|
||||
|
||||
_, err := cliui.Prompt(inv, cliui.PromptOptions{
|
||||
Text: "Would you like to create the first user?",
|
||||
Default: cliui.ConfirmYes,
|
||||
IsConfirm: true,
|
||||
})
|
||||
if errors.Is(err, cliui.Canceled) {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
currentUser, err := user.Current()
|
||||
|
||||
username, err = promptFirstUsername(inv)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("get current user: %w", err)
|
||||
}
|
||||
username, err = cliui.Prompt(inv, cliui.PromptOptions{
|
||||
Text: "What " + cliui.DefaultStyles.Field.Render("username") + " would you like?",
|
||||
Default: currentUser.Username,
|
||||
})
|
||||
if errors.Is(err, cliui.Canceled) {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return xerrors.Errorf("pick username prompt: %w", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -141,37 +219,14 @@ func (r *RootCmd) login() *clibase.Cmd {
|
|||
},
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("specify email prompt: %w", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if password == "" {
|
||||
var matching bool
|
||||
|
||||
for !matching {
|
||||
password, err = cliui.Prompt(inv, cliui.PromptOptions{
|
||||
Text: "Enter a " + cliui.DefaultStyles.Field.Render("password") + ":",
|
||||
Secret: true,
|
||||
Validate: func(s string) error {
|
||||
return userpassword.Validate(s)
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("specify password prompt: %w", err)
|
||||
}
|
||||
confirm, err := cliui.Prompt(inv, cliui.PromptOptions{
|
||||
Text: "Confirm " + cliui.DefaultStyles.Field.Render("password") + ":",
|
||||
Secret: true,
|
||||
Validate: cliui.ValidateNotEmpty,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("confirm password prompt: %w", err)
|
||||
}
|
||||
|
||||
matching = confirm == password
|
||||
if !matching {
|
||||
_, _ = fmt.Fprintln(inv.Stdout, cliui.DefaultStyles.Error.Render("Passwords do not match"))
|
||||
}
|
||||
password, err = promptFirstPassword(inv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -193,29 +248,19 @@ func (r *RootCmd) login() *clibase.Cmd {
|
|||
if err != nil {
|
||||
return xerrors.Errorf("create initial user: %w", err)
|
||||
}
|
||||
resp, err := client.LoginWithPassword(ctx, codersdk.LoginWithPasswordRequest{
|
||||
Email: email,
|
||||
Password: password,
|
||||
})
|
||||
|
||||
err := r.loginWithPassword(inv, client, email, password)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("login with password: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
sessionToken := resp.SessionToken
|
||||
config := r.createConfig()
|
||||
err = config.Session().Write(sessionToken)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("write session token: %w", err)
|
||||
}
|
||||
err = config.URL().Write(serverURL.String())
|
||||
err = r.createConfig().URL().Write(serverURL.String())
|
||||
if err != nil {
|
||||
return xerrors.Errorf("write server url: %w", err)
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintf(inv.Stdout,
|
||||
cliui.DefaultStyles.Paragraph.Render(fmt.Sprintf("Welcome to Coder, %s! You're authenticated.", cliui.DefaultStyles.Keyword.Render(username)))+"\n")
|
||||
|
||||
_, _ = fmt.Fprintf(inv.Stdout,
|
||||
_, _ = fmt.Fprintf(
|
||||
inv.Stdout,
|
||||
cliui.DefaultStyles.Paragraph.Render("Get started by creating a template: "+cliui.DefaultStyles.Code.Render("coder templates init"))+"\n")
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -97,16 +97,15 @@ func TestLogin(t *testing.T) {
|
|||
t.Run("InitialUserFlags", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, nil)
|
||||
doneChan := make(chan struct{})
|
||||
root, _ := clitest.New(t, "login", client.URL.String(), "--first-user-username", "testuser", "--first-user-email", "user@coder.com", "--first-user-password", "SomeSecurePassword!", "--first-user-trial")
|
||||
pty := ptytest.New(t).Attach(root)
|
||||
go func() {
|
||||
defer close(doneChan)
|
||||
err := root.Run()
|
||||
assert.NoError(t, err)
|
||||
}()
|
||||
inv, _ := clitest.New(
|
||||
t, "login", client.URL.String(),
|
||||
"--first-user-username", "testuser", "--first-user-email", "user@coder.com",
|
||||
"--first-user-password", "SomeSecurePassword!", "--first-user-trial",
|
||||
)
|
||||
pty := ptytest.New(t).Attach(inv)
|
||||
w := clitest.StartWithWaiter(t, inv)
|
||||
pty.ExpectMatch("Welcome to Coder")
|
||||
<-doneChan
|
||||
w.RequireSuccess()
|
||||
})
|
||||
|
||||
t.Run("InitialUserTTYConfirmPasswordFailAndReprompt", func(t *testing.T) {
|
||||
|
|
163
site/site.go
163
site/site.go
|
@ -279,10 +279,15 @@ func (h *Handler) serveHTML(resp http.ResponseWriter, request *http.Request, req
|
|||
return false
|
||||
}
|
||||
|
||||
func execTmpl(tmpl *template.Template, state htmlState) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
err := tmpl.Execute(&buf, state)
|
||||
return buf.Bytes(), err
|
||||
}
|
||||
|
||||
// renderWithState will render the file using the given nonce if the file exists
|
||||
// as a template. If it does not, it will return an error.
|
||||
func (h *Handler) renderHTMLWithState(r *http.Request, filePath string, state htmlState) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
if filePath == "" {
|
||||
filePath = "index.html"
|
||||
}
|
||||
|
@ -307,96 +312,94 @@ func (h *Handler) renderHTMLWithState(r *http.Request, filePath string, state ht
|
|||
RedirectToLogin: false,
|
||||
SessionTokenFunc: nil,
|
||||
})
|
||||
if ok && apiKey != nil && actor != nil {
|
||||
ctx := dbauthz.As(r.Context(), actor.Actor)
|
||||
if !ok || apiKey == nil || actor == nil {
|
||||
return execTmpl(tmpl, state)
|
||||
}
|
||||
|
||||
var eg errgroup.Group
|
||||
var user database.User
|
||||
orgIDs := []uuid.UUID{}
|
||||
eg.Go(func() error {
|
||||
var err error
|
||||
user, err = h.opts.Database.GetUserByID(ctx, apiKey.UserID)
|
||||
return err
|
||||
})
|
||||
eg.Go(func() error {
|
||||
memberIDs, err := h.opts.Database.GetOrganizationIDsByMemberIDs(ctx, []uuid.UUID{apiKey.UserID})
|
||||
if errors.Is(err, sql.ErrNoRows) || len(memberIDs) == 0 {
|
||||
return nil
|
||||
ctx := dbauthz.As(r.Context(), actor.Actor)
|
||||
|
||||
var eg errgroup.Group
|
||||
var user database.User
|
||||
orgIDs := []uuid.UUID{}
|
||||
eg.Go(func() error {
|
||||
var err error
|
||||
user, err = h.opts.Database.GetUserByID(ctx, apiKey.UserID)
|
||||
return err
|
||||
})
|
||||
eg.Go(func() error {
|
||||
memberIDs, err := h.opts.Database.GetOrganizationIDsByMemberIDs(ctx, []uuid.UUID{apiKey.UserID})
|
||||
if errors.Is(err, sql.ErrNoRows) || len(memberIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
orgIDs = memberIDs[0].OrganizationIDs
|
||||
return err
|
||||
})
|
||||
err := eg.Wait()
|
||||
if err == nil {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
user, err := json.Marshal(db2sdk.User(user, orgIDs))
|
||||
if err == nil {
|
||||
state.User = html.EscapeString(string(user))
|
||||
}
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
orgIDs = memberIDs[0].OrganizationIDs
|
||||
return err
|
||||
})
|
||||
err := eg.Wait()
|
||||
if err == nil {
|
||||
var wg sync.WaitGroup
|
||||
}()
|
||||
entitlements := h.Entitlements.Load()
|
||||
if entitlements != nil {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
user, err := json.Marshal(db2sdk.User(user, orgIDs))
|
||||
entitlements, err := json.Marshal(entitlements)
|
||||
if err == nil {
|
||||
state.User = html.EscapeString(string(user))
|
||||
state.Entitlements = html.EscapeString(string(entitlements))
|
||||
}
|
||||
}()
|
||||
entitlements := h.Entitlements.Load()
|
||||
if entitlements != nil {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
entitlements, err := json.Marshal(entitlements)
|
||||
if err == nil {
|
||||
state.Entitlements = html.EscapeString(string(entitlements))
|
||||
}
|
||||
}()
|
||||
}
|
||||
if h.AppearanceFetcher != nil {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
cfg, err := h.AppearanceFetcher(ctx)
|
||||
if err == nil {
|
||||
appearance, err := json.Marshal(cfg)
|
||||
if err == nil {
|
||||
state.Appearance = html.EscapeString(string(appearance))
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
if h.RegionsFetcher != nil {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
regions, err := h.RegionsFetcher(ctx)
|
||||
if err == nil {
|
||||
regions, err := json.Marshal(regions)
|
||||
if err == nil {
|
||||
state.Regions = html.EscapeString(string(regions))
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
experiments := h.Experiments.Load()
|
||||
if experiments != nil {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
experiments, err := json.Marshal(experiments)
|
||||
if err == nil {
|
||||
state.Experiments = html.EscapeString(string(experiments))
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
if h.AppearanceFetcher != nil {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
cfg, err := h.AppearanceFetcher(ctx)
|
||||
if err == nil {
|
||||
appearance, err := json.Marshal(cfg)
|
||||
if err == nil {
|
||||
state.Appearance = html.EscapeString(string(appearance))
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
if h.RegionsFetcher != nil {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
regions, err := h.RegionsFetcher(ctx)
|
||||
if err == nil {
|
||||
regions, err := json.Marshal(regions)
|
||||
if err == nil {
|
||||
state.Regions = html.EscapeString(string(regions))
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
experiments := h.Experiments.Load()
|
||||
if experiments != nil {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
experiments, err := json.Marshal(experiments)
|
||||
if err == nil {
|
||||
state.Experiments = html.EscapeString(string(experiments))
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
err := tmpl.Execute(&buf, state)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
return execTmpl(tmpl, state)
|
||||
}
|
||||
|
||||
// noopResponseWriter is a response writer that does nothing.
|
||||
|
|
Loading…
Reference in New Issue