3
0

Change password reset flow, add token exchange step

This commit is contained in:
Denis Arh
2019-04-09 22:35:39 +02:00
parent 4c4445d2a2
commit 3f83a33622
7 changed files with 168 additions and 18 deletions
+18 -2
View File
@@ -93,7 +93,7 @@
{
"name": "requestPasswordReset",
"method": "POST",
"title": "Request password reset token",
"title": "Request password reset token (via email)",
"path": "/request-password-reset",
"parameters": {
"post": [
@@ -106,10 +106,26 @@
]
}
},
{
"name": "exchangePasswordResetToken",
"method": "POST",
"title": "Exchange password reset token for new token and user info",
"path": "/exchange-password-reset-token",
"parameters": {
"post": [
{
"name": "token",
"type": "string",
"required": true,
"title": "Token"
}
]
}
},
{
"name": "resetPassword",
"method": "POST",
"title": "Reset password with token",
"title": "Reset password with exchanged password reset token",
"path": "/reset-password",
"parameters": {
"post": [
+18 -2
View File
@@ -72,7 +72,7 @@
{
"Name": "requestPasswordReset",
"Method": "POST",
"Title": "Request password reset token",
"Title": "Request password reset token (via email)",
"Path": "/request-password-reset",
"Parameters": {
"post": [
@@ -85,10 +85,26 @@
]
}
},
{
"Name": "exchangePasswordResetToken",
"Method": "POST",
"Title": "Exchange password reset token for new token and user info",
"Path": "/exchange-password-reset-token",
"Parameters": {
"post": [
{
"name": "token",
"required": true,
"title": "Token",
"type": "string"
}
]
}
},
{
"Name": "resetPassword",
"Method": "POST",
"Title": "Reset password with token",
"Title": "Reset password with exchanged password reset token",
"Path": "/reset-password",
"Parameters": {
"post": [
+19 -4
View File
@@ -133,8 +133,9 @@
| ------ | -------- | ------- |
| `POST` | `/auth/internal/login` | Login user |
| `POST` | `/auth/internal/signup` | User signup/registration |
| `POST` | `/auth/internal/request-password-reset` | Request password reset token |
| `POST` | `/auth/internal/reset-password` | Reset password with token |
| `POST` | `/auth/internal/request-password-reset` | Request password reset token (via email) |
| `POST` | `/auth/internal/exchange-password-reset-token` | Exchange password reset token for new token and user info |
| `POST` | `/auth/internal/reset-password` | Reset password with exchanged password reset token |
| `POST` | `/auth/internal/confirm-email` | Confirm email with token |
| `POST` | `/auth/internal/change-password` | Changes password for current user, requires current password |
@@ -171,7 +172,7 @@
| handle | string | POST | User handle | N/A | NO |
| name | string | POST | Display name | N/A | NO |
## Request password reset token
## Request password reset token (via email)
#### Method
@@ -185,7 +186,21 @@
| --------- | ---- | ------ | ----------- | ------- | --------- |
| email | string | POST | Email | N/A | YES |
## Reset password with token
## Exchange password reset token for new token and user info
#### Method
| URI | Protocol | Method | Authentication |
| --- | -------- | ------ | -------------- |
| `/auth/internal/exchange-password-reset-token` | HTTP/S | POST | |
#### Request parameters
| Parameter | Type | Method | Description | Default | Required? |
| --------- | ---- | ------ | ----------- | ------- | --------- |
| token | string | POST | Token | N/A | YES |
## Reset password with exchanged password reset token
#### Method
+34 -4
View File
@@ -42,6 +42,7 @@ type (
ChangePassword(userID uint64, oldPassword, newPassword string) error
ValidateEmailConfirmationToken(token string) (user *types.User, err error)
ExchangePasswordResetToken(token string) (user *types.User, exchangedToken string, err error)
ValidatePasswordResetToken(token string) (user *types.User, err error)
SendEmailAddressConfirmationToken(email string) (err error)
SendPasswordResetToken(email string) (err error)
@@ -49,9 +50,10 @@ type (
)
const (
credentialsTypePassword = "password"
credentialsTypeEmailAuthToken = "email-authentication-token"
credentialsTypeResetPasswordToken = "password-reset-token"
credentialsTypePassword = "password"
credentialsTypeEmailAuthToken = "email-authentication-token"
credentialsTypeResetPasswordToken = "password-reset-token"
credentialsTypeResetPasswordTokenExchanged = "password-reset-token-exchanged"
credentialsTokenLength = 32
)
@@ -560,7 +562,35 @@ func (svc auth) ValidatePasswordResetToken(token string) (user *types.User, err
return nil, errors.New("password reset disabled")
}
return svc.loadUserFromToken(token, credentialsTypeResetPasswordToken)
return svc.loadUserFromToken(token, credentialsTypeResetPasswordTokenExchanged)
}
// ExchangePasswordResetToken exchanges reset password token for a new one and returns it with user info
func (svc auth) ExchangePasswordResetToken(token string) (user *types.User, exchangedToken string, err error) {
if !svc.settings.internalEnabled {
err = errors.New("internal authentication disabled")
return
}
if !svc.settings.internalPasswordResetEnabled {
err = errors.New("password reset disabled")
return
}
user, err = svc.loadUserFromToken(token, credentialsTypeResetPasswordToken)
if err != nil {
user = nil
return
}
exchangedToken, err = svc.createUserToken(user, credentialsTypeResetPasswordTokenExchanged)
if err != nil {
user = nil
exchangedToken = ""
return
}
return
}
func (svc auth) SendEmailAddressConfirmationToken(email string) error {
+17
View File
@@ -19,6 +19,11 @@ type (
User *types.User `json:"user"`
}
authPasswordResetTokenExchangeResponse struct {
Token string `json:"token"`
User *types.User `json:"user"`
}
AuthInternal struct {
tokenEncoder auth.TokenEncoder
authSvc service.AuthService
@@ -71,6 +76,18 @@ func (ctrl *AuthInternal) RequestPasswordReset(ctx context.Context, r *request.A
return true, ctrl.authSvc.SendPasswordResetToken(r.Email)
}
func (ctrl *AuthInternal) ExchangePasswordResetToken(ctx context.Context, r *request.AuthInternalExchangePasswordResetToken) (interface{}, error) {
u, token, err := ctrl.authSvc.ExchangePasswordResetToken(r.Token)
if err != nil {
return nil, err
}
return authPasswordResetTokenExchangeResponse{
Token: token,
User: u,
}, nil
}
func (ctrl *AuthInternal) ResetPassword(ctx context.Context, r *request.AuthInternalResetPassword) (interface{}, error) {
var u, err = ctrl.authSvc.ValidatePasswordResetToken(r.Token)
if err != nil {
+16 -6
View File
@@ -31,6 +31,7 @@ type AuthInternalAPI interface {
Login(context.Context, *request.AuthInternalLogin) (interface{}, error)
Signup(context.Context, *request.AuthInternalSignup) (interface{}, error)
RequestPasswordReset(context.Context, *request.AuthInternalRequestPasswordReset) (interface{}, error)
ExchangePasswordResetToken(context.Context, *request.AuthInternalExchangePasswordResetToken) (interface{}, error)
ResetPassword(context.Context, *request.AuthInternalResetPassword) (interface{}, error)
ConfirmEmail(context.Context, *request.AuthInternalConfirmEmail) (interface{}, error)
ChangePassword(context.Context, *request.AuthInternalChangePassword) (interface{}, error)
@@ -38,12 +39,13 @@ type AuthInternalAPI interface {
// HTTP API interface
type AuthInternal struct {
Login func(http.ResponseWriter, *http.Request)
Signup func(http.ResponseWriter, *http.Request)
RequestPasswordReset func(http.ResponseWriter, *http.Request)
ResetPassword func(http.ResponseWriter, *http.Request)
ConfirmEmail func(http.ResponseWriter, *http.Request)
ChangePassword func(http.ResponseWriter, *http.Request)
Login func(http.ResponseWriter, *http.Request)
Signup func(http.ResponseWriter, *http.Request)
RequestPasswordReset func(http.ResponseWriter, *http.Request)
ExchangePasswordResetToken func(http.ResponseWriter, *http.Request)
ResetPassword func(http.ResponseWriter, *http.Request)
ConfirmEmail func(http.ResponseWriter, *http.Request)
ChangePassword func(http.ResponseWriter, *http.Request)
}
func NewAuthInternal(ah AuthInternalAPI) *AuthInternal {
@@ -69,6 +71,13 @@ func NewAuthInternal(ah AuthInternalAPI) *AuthInternal {
return ah.RequestPasswordReset(r.Context(), params)
})
},
ExchangePasswordResetToken: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewAuthInternalExchangePasswordResetToken()
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
return ah.ExchangePasswordResetToken(r.Context(), params)
})
},
ResetPassword: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewAuthInternalResetPassword()
@@ -99,6 +108,7 @@ func (ah *AuthInternal) MountRoutes(r chi.Router, middlewares ...func(http.Handl
r.Post("/auth/internal/login", ah.Login)
r.Post("/auth/internal/signup", ah.Signup)
r.Post("/auth/internal/request-password-reset", ah.RequestPasswordReset)
r.Post("/auth/internal/exchange-password-reset-token", ah.ExchangePasswordResetToken)
r.Post("/auth/internal/reset-password", ah.ResetPassword)
r.Post("/auth/internal/confirm-email", ah.ConfirmEmail)
r.Post("/auth/internal/change-password", ah.ChangePassword)
+46
View File
@@ -193,6 +193,52 @@ func (auReq *AuthInternalRequestPasswordReset) Fill(r *http.Request) (err error)
var _ RequestFiller = NewAuthInternalRequestPasswordReset()
// AuthInternal exchangePasswordResetToken request parameters
type AuthInternalExchangePasswordResetToken struct {
Token string
}
func NewAuthInternalExchangePasswordResetToken() *AuthInternalExchangePasswordResetToken {
return &AuthInternalExchangePasswordResetToken{}
}
func (auReq *AuthInternalExchangePasswordResetToken) Fill(r *http.Request) (err error) {
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(r.Body).Decode(auReq)
switch {
case err == io.EOF:
err = nil
case err != nil:
return errors.Wrap(err, "error parsing http request body")
}
}
if err = r.ParseForm(); err != nil {
return err
}
get := map[string]string{}
post := map[string]string{}
urlQuery := r.URL.Query()
for name, param := range urlQuery {
get[name] = string(param[0])
}
postVars := r.Form
for name, param := range postVars {
post[name] = string(param[0])
}
if val, ok := post["token"]; ok {
auReq.Token = val
}
return err
}
var _ RequestFiller = NewAuthInternalExchangePasswordResetToken()
// AuthInternal resetPassword request parameters
type AuthInternalResetPassword struct {
Token string