Skip to content

feat: Add reset-password command #1380

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
May 12, 2022
Merged

feat: Add reset-password command #1380

merged 6 commits into from
May 12, 2022

Conversation

dwahler
Copy link
Contributor

@dwahler dwahler commented May 10, 2022

This PR introduces a coder reset-password command that allows resetting a user's password on a production instance by directly updating the DB. This allows an administrator to recover access even if they can't authenticate normally through the API.

Fixes #1024

@dwahler dwahler requested review from kylecarbs and a team May 10, 2022 23:32
@codecov
Copy link

codecov bot commented May 10, 2022

Codecov Report

Merging #1380 (cb36912) into main (9d94f4f) will increase coverage by 0.19%.
The diff coverage is 57.62%.

@@            Coverage Diff             @@
##             main    #1380      +/-   ##
==========================================
+ Coverage   66.93%   67.13%   +0.19%     
==========================================
  Files         288      289       +1     
  Lines       18856    19220     +364     
  Branches      241      241              
==========================================
+ Hits        12622    12903     +281     
- Misses       4944     4983      +39     
- Partials     1290     1334      +44     
Flag Coverage Δ
unittest-go-macos-latest 54.18% <11.01%> (+0.10%) ⬆️
unittest-go-postgres- 65.70% <57.62%> (+0.28%) ⬆️
unittest-go-ubuntu-latest 56.58% <26.27%> (+0.19%) ⬆️
unittest-go-windows-2022 52.54% <11.01%> (+0.16%) ⬆️
unittest-js 74.19% <ø> (-0.05%) ⬇️
Impacted Files Coverage Δ
coderd/database/migrate.go 49.39% <54.00%> (+4.39%) ⬆️
cli/resetpassword.go 59.70% <59.70%> (ø)
cli/root.go 79.86% <100.00%> (+0.14%) ⬆️
peerbroker/dial.go 77.04% <0.00%> (-6.56%) ⬇️
coderd/turnconn/turnconn.go 81.91% <0.00%> (-3.20%) ⬇️
provisioner/echo/serve.go 54.40% <0.00%> (-2.40%) ⬇️
codersdk/workspaceagents.go 51.52% <0.00%> (-1.36%) ⬇️
agent/agent.go 65.90% <0.00%> (-1.24%) ⬇️
coderd/organizations.go 56.23% <0.00%> (-1.01%) ⬇️
coderd/provisionerdaemons.go 63.81% <0.00%> (-0.17%) ⬇️
... and 28 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 9d94f4f...cb36912. Read the comment docs.

Comment on lines +106 to +108
// Returns nil if currentVersion corresponds to the latest available migration,
// otherwise an error explaining why not.
func CheckLatestVersion(sourceDriver source.Driver, currentVersion uint) error {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the only thing I'm significantly unhappy about with this commit. I feel like this ought to be a private function, but that doesn't play nicely with the way we put tests in a separate package.

The alternative would be to test the EnsureClean function instead, but that seems difficult because its behavior implicitly depends on the global migrations data, for consistency with the rest of the package.

Copy link
Member

@mafredri mafredri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a few minor suggestions, looks good otherwise!

connectionURL, closeFunc, err := postgres.Open()
require.NoError(t, err)
defer closeFunc()
ctx, cancelFunc := context.WithCancel(context.Background())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor nit: Variables should be named after what they do or contain, not their types. As such, I think cancel is descriptive enough and adding Func is redundant (similarly with pg open above).

Dave Cheney has a pretty good writeup on this: https://dave.cheney.net/2019/01/29/you-shouldnt-name-your-variables-after-their-types-for-the-same-reason-you-wouldnt-name-your-pets-dog-or-cat

PS. I'm reviewing this with new-hire goggles, fully aware this may be based on existing code, but thought I'd call this out anyway 😄.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, fair point! This particular chunk of code is copy-and-pasted from server_test.go. I should probably go back and see if I can break it out into a cleaner "test helper" abstraction.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One problem is close overrides a builtin function, one of the linters gets mad :(

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a very good point @coadler, missed that. In that situation I think pgClose or closeConn could be a good fit (i.e. adds a bit more meaning whilst avoiding the naming conflict).

// EnsureClean checks whether all migrations for the current version have been
// applied, without making any changes to the database. If not, returns a
// non-nil error.
func EnsureClean(db *sql.DB) error {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if it helps, but if you want to test 99% of this function, you could write this as e.g. func EnsureClean(db *sql.DB) error { return ensureClean(db, migrateSetup); }.

This would let you swap out the migrateSetup function in your test, if that helps with the global state (considering your comment here #1380 (comment)). It would still require an internal test package but I think that's fine for this use-case.

})
if err != nil {
return xerrors.Errorf("password prompt: %w", err)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add a confirm step, similar to create first user?

coder/cli/login.go

Lines 121 to 133 in 9d94f4f

_, err = cliui.Prompt(cmd, cliui.PromptOptions{
Text: "Confirm " + cliui.Styles.Field.Render("password") + ":",
Secret: true,
Validate: func(s string) error {
if s != password {
return xerrors.Errorf("Passwords do not match")
}
return nil
},
})
if err != nil {
return xerrors.Errorf("confirm password prompt: %w", err)
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question.

Personally, I think the value of a password confirmation step is in making it harder to accidentally lock yourself out by changing your own password to something with a typo in it. I understand why it's helpful in the "create first user" flow, since you can only do that once, but if you make a typo when resetting a password it's easy to just re-run the command and fix it, since you're assumed to have direct access to the DB.

On the other hand, I can see the argument for just doing the confirmation anyway, for consistency with "create first user" and with UNIX passwd.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I agree that it's a bit redundant since you can just change it again.

My thought was both for uniformity and that the repetition increases the chance of typing the correct password, which could help when the user hops onto e.g. the webui and tries to login. If they're very confident they typed in the right password on the cli, but can't login on the web, that could lead to a frustrating experience.

I think either approach is fine though so I'll leave it up to you. 👍🏻

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that makes sense. I added a confirmation prompt, but I deviated from the behavior of coder login slightly in that if the passwords don't match, we just exit instead of repeating the second prompt. This matches the behavior of the passwd command, and I think it makes more sense because if you make a typo, you probably don't know whether it's the first or second input that's correct.

@dwahler dwahler merged commit 2091628 into main May 12, 2022
@dwahler dwahler deleted the david/reset-password-cli branch May 12, 2022 17:32
@misskniss misskniss added this to the V2 Beta milestone May 15, 2022
kylecarbs pushed a commit that referenced this pull request Jun 10, 2022
* allow non-destructively checking if database needs to be migrated

* feat: Add reset-password command

* fix linter errors

* clean up reset-password usage prompt

* Add confirmation to reset-password command

* Ping database before checking migration, to improve error message
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Bug: no ability to reset root account's password
6 participants