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
multi: add ability to persist password on UI login #1119
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First pass. Looks good conceptually, though I've suggested some architectural changes.
@@ -396,7 +403,9 @@ func TestAPIWithdraw(t *testing.T) { | |||
return resp.OK | |||
} | |||
|
|||
body = &withdrawForm{} | |||
body = &withdrawForm{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is needed because resolvePass
will return an error if this is an empty string.
@buck54321 This is ready for another review. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Partial review. Lemme know what you think about these notes. I promise to review faster now.
@buck54321 It's ready for another look. In the registration workflow (where the page is not reloaded between forms) |
@martonp Please rebase and resolve when you get a chance. |
@martonp Please try to avoid merging from master to get the branch up-to-date. Instead, rebase on master. It is tricky to get rid of the merge commit when you synchronized with master @ 16da72f. I'm squashing this branch down to a single commit with 16da72f as it's base like:
Then it can be rebased on master a bit more easily since it's just one commit now ( Although I see that the conflicts are gonna be tricky to resolve, particularly in forms.js, so thank you. |
0ccc8f3
to
78b22ff
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking really good. I want to give it one more look, but it's testing well. Just a couple of little things for now.
client/webserver/api.go
Outdated
passwordIsCached := s.isPasswordCached(r) | ||
s.deauth() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe switch the order of these two lines so that the comment is right above the line it applies to.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I do that then I won't know if the password was cached before I called deauth. I can put the comment between the two lines.
<div{{if not .UserInfo.Authed}} class="d-hide"{{end}}> | ||
<div {{if not .UserInfo.Authed}}class="d-hide"{{end}}> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now, it will be <div >
instead of <div>
. What's the purpose? (one other place too)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a mistake.. it was red in my IDE the previous way, and I thought they were equivalent.
this.fields.unlockErr.textContent = msg | ||
Doc.show(this.fields.unlockErr) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this.setError(msg)
<label for="rememberPass" class="pl-1 mb-1">Remember my password</label> | ||
<input type="checkbox" id="rememberPass"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks way better now.
*/ | ||
async openWallet (assetID) { | ||
if (!State.passwordIsCached()) { | ||
this.showOpen.bind(this)(assetID) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't need to .bind(this)
here.
if (app.checkResponse(res)) { | ||
this.openWalletSuccess.bind(this)() | ||
} else { | ||
this.showOpen.bind(this)(assetID, `Error opening wallet: ${res.msg}`) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't need to .bind(this)
here.
client/webserver/webserver.go
Outdated
cookie, err := r.Cookie(pwKeyCK) | ||
switch { | ||
case err == nil: | ||
sessionKey, err := hex.DecodeString(cookie.Value) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return sessionKey, nil | ||
default: | ||
return nil, err | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We actually want to see theses errors. How about this?
switch {
case err != nil:
...
case errors.Is(err, http.ErrNoCookie):
return nil, nil
default:
return nil, err
}
and then in getCachedPasswordUsingRequest
, you return nil, errNoCachedPW
on the condition pwKeyBlob == nil
, but not err != nil
client/webserver/webserver.go
Outdated
// getCachedPassword takes authToken passed to and the key returned from | ||
// cacheAppPassword to retrieve and decrypt the app password. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// getCachedPassword retrieves the cached password for the user identified by authToken and
// presenting the specified key in their cookies.
client/webserver/webserver.go
Outdated
|
||
crypter, err := encrypt.Deserialize(key, cachedPassword.SerializedCrypter) | ||
if err != nil { | ||
return nil, fmt.Errorf("error deserializing crypter: %v", err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might as well %w
.
client/webserver/webserver.go
Outdated
|
||
pw, err := crypter.Decrypt(cachedPassword.EncryptedPass) | ||
if err != nil { | ||
return nil, fmt.Errorf("error decrypting password: %v", err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might as well %w
.
client/webserver/webserver.go
Outdated
srv *http.Server | ||
html *templates | ||
indent bool | ||
mtx sync.RWMutex |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should really rename this to authMtx
or something, shouldn't we?
client/webserver/api.go
Outdated
pass, err := s.resolvePass(form.Pass, r) | ||
if err != nil { | ||
s.writeAPIError(w, fmt.Errorf("password error: %v", err)) | ||
return | ||
} | ||
coin, err := s.core.Withdraw(pass, form.AssetID, form.Value, form.Address) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should always require the password to be entered manually for withdraws.
<div class="fs16 px-4 text-center {{if $passwordIsCached}}d-hide{{end}}">Authorize the withdraw with your app password.</div> | ||
<div class="d-flex px-4 mt-3 {{if $passwordIsCached}}justify-content-end{{end}}"> | ||
<div class="col-12 p-0 {{if $passwordIsCached}}d-hide{{end}}"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should always require password for withdraws.
@buck54321 I agree with all the comments, and I've updated them all. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please increment the cache busters in bodybuilder.js, and this is good with me.
srv *http.Server | ||
html *templates | ||
indent bool | ||
authMtx sync.RWMutex |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would prefer a newline to segregate these guarded fields with their mutex. Shoulda been that way previously, so might as well change it now.
client/webserver/webserver.go
Outdated
if err != nil { | ||
return nil, err | ||
} | ||
if pwKeyBlob == nil && err == nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
err
is guaranteed to be nil
here, so this half of the condition is redundant.
client/webserver/api.go
Outdated
@@ -398,13 +438,8 @@ func (s *WebServer) apiLogout(w http.ResponseWriter, r *http.Request) { | |||
// sessions to login again. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pls update comments at deauth
call sites to reflect it's expanded role w.r.t. cached passwords as well as the auth tokens.
client/webserver/webserver.go
Outdated
authTokens map[string]bool | ||
cachedPasswords map[string]*cachedPassword |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The keys for these maps are the same, right (both auth tokens)? Don't worry about refactoring at this stage, but it suggests a single map would be adequate if the logic around access to the map were updated to reflect new semantics (found
would indicate known auth token regardless of value stored including nil, while non-nil value would indicate a cached password). Rather than change anything, I just request a simple trailing comment on this delc line to doco the map key
cachedPasswords map[string]*cachedPassword // cached passwords keyed by auth token
There was a problem hiding this comment.
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'll do another PR for this.
This PR adds a checkbox to the login page that gives the user the option to persist their password for the session. When selected, all the password fields, other than disable account, will no longer be displayed.
When the checkbox is selected, the password is encrypted with a random key, and the encrypted password is stored in a cache in core. The key to decrypt the password is put into a cookie. Whenever a request that requires a password is made with an empty password field, the webserver uses the key from the cookie to retrieve the password from core.
Closes #946.