Skip to content

Commit d9fdad9

Browse files
committed
Merge branch 'main' into colin/audit-exporter
2 parents 9cff344 + ac27f64 commit d9fdad9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+1048
-171
lines changed

cli/cliui/agent.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"context"
55
"fmt"
66
"io"
7+
"os"
8+
"os/signal"
79
"sync"
810
"time"
911

@@ -46,6 +48,23 @@ func Agent(ctx context.Context, writer io.Writer, opts AgentOptions) error {
4648
spin.Start()
4749
defer spin.Stop()
4850

51+
ctx, cancelFunc := context.WithCancel(ctx)
52+
defer cancelFunc()
53+
stopSpin := make(chan os.Signal, 1)
54+
signal.Notify(stopSpin, os.Interrupt)
55+
defer signal.Stop(stopSpin)
56+
go func() {
57+
select {
58+
case <-ctx.Done():
59+
return
60+
case <-stopSpin:
61+
}
62+
signal.Stop(stopSpin)
63+
spin.Stop()
64+
// nolint:revive
65+
os.Exit(1)
66+
}()
67+
4968
ticker := time.NewTicker(opts.FetchInterval)
5069
defer ticker.Stop()
5170
timer := time.NewTimer(opts.WarnInterval)

cli/login.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,19 @@ func login() *cobra.Command {
117117
if err != nil {
118118
return xerrors.Errorf("specify password prompt: %w", err)
119119
}
120+
_, err = cliui.Prompt(cmd, cliui.PromptOptions{
121+
Text: "Confirm " + cliui.Styles.Field.Render("password") + ":",
122+
Secret: true,
123+
Validate: func(s string) error {
124+
if s != password {
125+
return xerrors.Errorf("Passwords do not match")
126+
}
127+
return nil
128+
},
129+
})
130+
if err != nil {
131+
return xerrors.Errorf("confirm password prompt: %w", err)
132+
}
120133

121134
_, err = client.CreateFirstUser(cmd.Context(), codersdk.CreateFirstUserRequest{
122135
Email: email,

cli/login_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ func TestLogin(t *testing.T) {
4343
"username", "testuser",
4444
"email", "user@coder.com",
4545
"password", "password",
46+
"password", "password", // Confirm.
4647
}
4748
for i := 0; i < len(matches); i += 2 {
4849
match := matches[i]
@@ -54,6 +55,44 @@ func TestLogin(t *testing.T) {
5455
<-doneChan
5556
})
5657

58+
t.Run("InitialUserTTYConfirmPasswordFailAndReprompt", func(t *testing.T) {
59+
t.Parallel()
60+
ctx, cancel := context.WithCancel(context.Background())
61+
defer cancel()
62+
client := coderdtest.New(t, nil)
63+
// The --force-tty flag is required on Windows, because the `isatty` library does not
64+
// accurately detect Windows ptys when they are not attached to a process:
65+
// https://github.com/mattn/go-isatty/issues/59
66+
doneChan := make(chan struct{})
67+
root, _ := clitest.New(t, "login", "--force-tty", client.URL.String())
68+
pty := ptytest.New(t)
69+
root.SetIn(pty.Input())
70+
root.SetOut(pty.Output())
71+
go func() {
72+
defer close(doneChan)
73+
err := root.ExecuteContext(ctx)
74+
require.ErrorIs(t, err, context.Canceled)
75+
}()
76+
77+
matches := []string{
78+
"first user?", "yes",
79+
"username", "testuser",
80+
"email", "user@coder.com",
81+
"password", "mypass",
82+
"password", "wrongpass", // Confirm.
83+
}
84+
for i := 0; i < len(matches); i += 2 {
85+
match := matches[i]
86+
value := matches[i+1]
87+
pty.ExpectMatch(match)
88+
pty.WriteLine(value)
89+
}
90+
pty.ExpectMatch("Passwords do not match")
91+
pty.ExpectMatch("password") // Re-prompt password.
92+
cancel()
93+
<-doneChan
94+
})
95+
5796
t.Run("ExistingUserValidTokenTTY", func(t *testing.T) {
5897
t.Parallel()
5998
client := coderdtest.New(t, nil)

cli/root.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,8 @@ func Root() *cobra.Command {
9090
workspaceAgent(),
9191
)
9292

93-
cliflag.String(cmd.PersistentFlags(), varURL, "", "CODER_URL", "", "Specify the URL to your deployment.")
94-
cliflag.String(cmd.PersistentFlags(), varToken, "", "CODER_TOKEN", "", "Specify an authentication token.")
93+
cmd.PersistentFlags().String(varURL, "", "Specify the URL to your deployment.")
94+
cmd.PersistentFlags().String(varToken, "", "Specify an authentication token.")
9595
cliflag.String(cmd.PersistentFlags(), varAgentToken, "", "CODER_AGENT_TOKEN", "", "Specify an agent authentication token.")
9696
cliflag.String(cmd.PersistentFlags(), varAgentURL, "", "CODER_AGENT_URL", "", "Specify the URL for an agent to access your deployment.")
9797
cliflag.String(cmd.PersistentFlags(), varGlobalConfig, "", "CODER_CONFIG_DIR", configdir.LocalConfig("coderv2"), "Specify the path to the global `coder` config directory.")

cli/server.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,12 @@ func server() *cobra.Command {
342342
return xerrors.Errorf("notify systemd: %w", err)
343343
}
344344

345+
// Because the graceful shutdown includes cleaning up workspaces in dev mode, we're
346+
// going to make it harder to accidentally skip the graceful shutdown by hitting ctrl+c
347+
// two or more times. So the stopChan is unlimited in size and we don't call
348+
// signal.Stop() until graceful shutdown finished--this means we swallow additional
349+
// SIGINT after the first. To get out of a graceful shutdown, the user can send SIGQUIT
350+
// with ctrl+\ or SIGTERM with `kill`.
345351
stopChan := make(chan os.Signal, 1)
346352
defer signal.Stop(stopChan)
347353
signal.Notify(stopChan, os.Interrupt)
@@ -358,12 +364,13 @@ func server() *cobra.Command {
358364
return err
359365
case <-stopChan:
360366
}
361-
signal.Stop(stopChan)
362367
_, err = daemon.SdNotify(false, daemon.SdNotifyStopping)
363368
if err != nil {
364369
return xerrors.Errorf("notify systemd: %w", err)
365370
}
366-
_, _ = fmt.Fprintln(cmd.OutOrStdout(), "\n\n"+cliui.Styles.Bold.Render("Interrupt caught. Gracefully exiting..."))
371+
_, _ = fmt.Fprintln(cmd.OutOrStdout(), "\n\n"+
372+
cliui.Styles.Bold.Render(
373+
"Interrupt caught, gracefully exiting. Use ctrl+\\ to force quit"))
367374

368375
if dev {
369376
organizations, err := client.OrganizationsByUser(cmd.Context(), codersdk.Me)

cli/server_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,12 @@ func TestServer(t *testing.T) {
271271
require.NoError(t, err)
272272
err = currentProcess.Signal(os.Interrupt)
273273
require.NoError(t, err)
274+
// Send a two more signal, which should be ignored. Send 2 because the channel has a buffer
275+
// of 1 and we want to make sure that nothing strange happens if we exceed the buffer.
276+
err = currentProcess.Signal(os.Interrupt)
277+
require.NoError(t, err)
278+
err = currentProcess.Signal(os.Interrupt)
279+
require.NoError(t, err)
274280
<-done
275281
})
276282
t.Run("DatadogTracerNoLeak", func(t *testing.T) {

coderd/coderd.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,10 @@ func New(options *Options) (http.Handler, func()) {
240240
r.Get("/", api.userByName)
241241
r.Put("/profile", api.putUserProfile)
242242
r.Put("/suspend", api.putUserSuspend)
243+
r.Route("/password", func(r chi.Router) {
244+
r.Use(httpmw.WithRBACObject(rbac.ResourceUserPasswordRole))
245+
r.Put("/", authorize(api.putUserPassword, rbac.ActionUpdate))
246+
})
243247
r.Get("/organizations", api.organizationsByUser)
244248
r.Post("/organizations", api.postOrganizationsByUser)
245249
// These roles apply to the site wide permissions.

coderd/coderdtest/coderdtest.go

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -174,21 +174,22 @@ func NewProvisionerDaemon(t *testing.T, client *codersdk.Client) io.Closer {
174174
return closer
175175
}
176176

177+
var FirstUserParams = codersdk.CreateFirstUserRequest{
178+
Email: "testuser@coder.com",
179+
Username: "testuser",
180+
Password: "testpass",
181+
OrganizationName: "testorg",
182+
}
183+
177184
// CreateFirstUser creates a user with preset credentials and authenticates
178185
// with the passed in codersdk client.
179186
func CreateFirstUser(t *testing.T, client *codersdk.Client) codersdk.CreateFirstUserResponse {
180-
req := codersdk.CreateFirstUserRequest{
181-
Email: "testuser@coder.com",
182-
Username: "testuser",
183-
Password: "testpass",
184-
OrganizationName: "testorg",
185-
}
186-
resp, err := client.CreateFirstUser(context.Background(), req)
187+
resp, err := client.CreateFirstUser(context.Background(), FirstUserParams)
187188
require.NoError(t, err)
188189

189190
login, err := client.LoginWithPassword(context.Background(), codersdk.LoginWithPasswordRequest{
190-
Email: req.Email,
191-
Password: req.Password,
191+
Email: FirstUserParams.Email,
192+
Password: FirstUserParams.Password,
192193
})
193194
require.NoError(t, err)
194195
client.SessionToken = login.SessionToken

coderd/database/databasefake/databasefake.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1314,6 +1314,21 @@ func (q *fakeQuerier) UpdateUserStatus(_ context.Context, arg database.UpdateUse
13141314
return database.User{}, sql.ErrNoRows
13151315
}
13161316

1317+
func (q *fakeQuerier) UpdateUserHashedPassword(_ context.Context, arg database.UpdateUserHashedPasswordParams) error {
1318+
q.mutex.Lock()
1319+
defer q.mutex.Unlock()
1320+
1321+
for i, user := range q.users {
1322+
if user.ID != arg.ID {
1323+
continue
1324+
}
1325+
user.HashedPassword = arg.HashedPassword
1326+
q.users[i] = user
1327+
return nil
1328+
}
1329+
return sql.ErrNoRows
1330+
}
1331+
13171332
func (q *fakeQuerier) InsertWorkspace(_ context.Context, arg database.InsertWorkspaceParams) (database.Workspace, error) {
13181333
q.mutex.Lock()
13191334
defer q.mutex.Unlock()

coderd/database/querier.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)