Commit 49c57d00 authored by ale's avatar ale

Extend functionality of the password recovery endpoint

Make it possible for the sso-server to fetch the recovery hint using
the same API.
parent 47b9f57e
Pipeline #1031 passed with stages
in 1 minute and 24 seconds
......@@ -344,6 +344,30 @@ Request parameters:
* `sso` - SSO ticket
* `user` - user to create, with resources
### `/api/recover_password`
Special endpoint for password recovery, used by the login FE service
(sso-server) to retrieve the password recovery hint, and to trigger
password recovery with the user-provided recovery password.
The recovery workflow should look like this: first, the sso-server
sends a PasswordRecoveryRequest with only the *username* field set,
retrieving the password recovery hint for the user. Then it presents
the hint to the user, and asks for both the recovery password and the
new password to be set if recovery is successful. These are finally
sent to the accountserver in another PasswordRecoveryRequest.
Request parameters:
* `username` - user name
* `recovery_password` - recovery password. Account recovery will only
be attempted if this field is not empty, otherwise we'll just return
the password hint.
* `password` - new password to be set for the account
Response parameters:
* `hint` - the password recovery hint to show the user
## Resource endpoints
......
......@@ -169,24 +169,39 @@ func (r *PasswordRecoveryRequest) Validate(ctx context.Context, s *AccountServic
return s.fieldValidators.password(ctx, r.Password)
}
// PasswordRecoveryResponse is the response type for AccountService.RecoverPassword().
type PasswordRecoveryResponse struct {
Hint string `json:"hint"`
}
// RecoverPassword lets users reset their password by providing
// secondary credentials, which we authenticate ourselves.
//
// TODO: call out to auth-server for secondary authentication?
func (s *AccountService) RecoverPassword(ctx context.Context, tx TX, req *PasswordRecoveryRequest) error {
func (s *AccountService) RecoverPassword(ctx context.Context, tx TX, req *PasswordRecoveryRequest) (*PasswordRecoveryResponse, error) {
user, err := getUserOrDie(ctx, tx, req.Username)
if err != nil {
return err
return nil, err
}
resp := PasswordRecoveryResponse{
Hint: user.PasswordRecoveryHint,
}
// Only attempt to authenticate if the recovery password is
// set in the request, otherwise just return the hint.
if req.RecoveryPassword == "" {
return &resp, nil
}
// Authenticate the secret recovery password.
if !pwhash.ComparePassword(tx.GetUserRecoveryEncryptedPassword(ctx, user), req.RecoveryPassword) {
return ErrUnauthorized
return nil, ErrUnauthorized
}
ctx = context.WithValue(ctx, authUserCtxKey, req.Username)
return s.withRequest(ctx, tx, req, user, func(ctx context.Context) error {
err = s.withRequest(ctx, tx, req, user, func(ctx context.Context) error {
s.audit.Log(ctx, ResourceID{}, "password reset via account recovery")
// Change the user password (the recovery password does not change).
......@@ -196,6 +211,7 @@ func (s *AccountService) RecoverPassword(ctx context.Context, tx TX, req *Passwo
// Disable 2FA.
return s.disable2FA(ctx, tx, user)
})
return &resp, err
}
// ResetPasswordRequest is the request type for AccountService.ResetPassword().
......
......@@ -633,7 +633,7 @@ func TestService_Recovery(t *testing.T) {
svc, tx := testService("")
// Bad recovery response.
err := svc.RecoverPassword(context.Background(), tx, &PasswordRecoveryRequest{
_, err := svc.RecoverPassword(context.Background(), tx, &PasswordRecoveryRequest{
Username: "testuser",
RecoveryPassword: "BADPASS",
Password: "new_password",
......@@ -643,7 +643,7 @@ func TestService_Recovery(t *testing.T) {
}
// Successful account recovery.
err = svc.RecoverPassword(context.Background(), tx, &PasswordRecoveryRequest{
_, err = svc.RecoverPassword(context.Background(), tx, &PasswordRecoveryRequest{
Username: "testuser",
RecoveryPassword: "recoverypassword",
Password: "new_password",
......
......@@ -68,7 +68,7 @@ func (s *AccountServer) handleSetPasswordRecoveryHint(tx as.TX, w http.ResponseW
func (s *AccountServer) handleRecoverPassword(tx as.TX, w http.ResponseWriter, r *http.Request) (interface{}, error) {
var req as.PasswordRecoveryRequest
return handleJSON(w, r, &req, func(ctx context.Context) (interface{}, error) {
return &emptyResponse, s.service.RecoverPassword(ctx, tx, &req)
return s.service.RecoverPassword(ctx, tx, &req)
})
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment