Change password reset flow, add token exchange step
This commit is contained in:
+18
-2
@@ -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": [
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user