From b29a85551fa998b7fee2d64adb437515502c088a Mon Sep 17 00:00:00 2001 From: Geno Date: Wed, 23 Jun 2021 11:36:09 +0200 Subject: [PATCH] web/auth: password set by code --- web/auth/api_password_code.go | 75 ++++++++++++++++++++++++++++++ web/auth/api_password_code_test.go | 58 +++++++++++++++++++++++ web/auth/models.go | 7 +-- 3 files changed, 137 insertions(+), 3 deletions(-) create mode 100644 web/auth/api_password_code.go create mode 100644 web/auth/api_password_code_test.go diff --git a/web/auth/api_password_code.go b/web/auth/api_password_code.go new file mode 100644 index 0000000..330fa27 --- /dev/null +++ b/web/auth/api_password_code.go @@ -0,0 +1,75 @@ +package auth + +import ( + "errors" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/google/uuid" + "gorm.io/gorm" + + "dev.sum7.eu/genofire/golang-lib/web" +) + +// PasswordWithForgetCode - JSON Request to set password without login +type PasswordWithForgetCode struct { + ForgetCode uuid.UUID `json:"forget_code"` + Password string `json:"password"` +} + +// @Summary Change Password with ForgetCode +// @Description Change Password of any user by generated forget code +// @Accept json +// @Produce json +// @Success 200 {object} string "username of changed password (e.g. `"admin"`)" +// @Failure 400 {object} web.HTTPError +// @Failure 401 {object} web.HTTPError +// @Failure 500 {object} web.HTTPError +// @Router /api/v1/auth/password/code [post] +// @Param body body PasswordWithForgetCode false "new password and forget code" +func init() { + web.ModuleRegister(func(r *gin.Engine, ws *web.Service) { + r.POST("/api/v1/auth/password/code", func(c *gin.Context) { + var req PasswordWithForgetCode + if err := c.BindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, web.HTTPError{ + Message: web.APIErrorInvalidRequestFormat, + Error: err.Error(), + }) + return + } + d := User{} + if err := ws.DB.Where("forget_code", req.ForgetCode).First(&d).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + c.JSON(http.StatusBadRequest, web.HTTPError{ + Message: APIErrorUserNotFound, + Error: err.Error(), + }) + return + } + c.JSON(http.StatusInternalServerError, web.HTTPError{ + Message: APIErrroCreatePassword, + Error: err.Error(), + }) + return + } + if err := d.SetPassword(req.Password); err != nil { + c.JSON(http.StatusInternalServerError, web.HTTPError{ + Message: APIErrroCreatePassword, + Error: err.Error(), + }) + return + } + d.ForgetCode = nil + + if err := ws.DB.Save(&d).Error; err != nil { + c.JSON(http.StatusInternalServerError, web.HTTPError{ + Message: web.APIErrorInternalDatabase, + Error: err.Error(), + }) + return + } + c.JSON(http.StatusOK, d.Username) + }) + }) +} diff --git a/web/auth/api_password_code_test.go b/web/auth/api_password_code_test.go new file mode 100644 index 0000000..a47032f --- /dev/null +++ b/web/auth/api_password_code_test.go @@ -0,0 +1,58 @@ +package auth + +import ( + "net/http" + "testing" + + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + + "dev.sum7.eu/genofire/golang-lib/web" + "dev.sum7.eu/genofire/golang-lib/web/webtest" +) + +func TestAPIPasswordCode(t *testing.T) { + assert := assert.New(t) + s := webtest.New(assert) + assert.NotNil(s) + SetupMigration(s.DB) + s.DB.MigrateTestdata() + + forgetCode := uuid.New() + passwordCurrent := "CHANGEME" + passwordNew := "test" + + s.DB.DB.Model(&User{ID: TestUser1ID}).Update("forget_code", forgetCode) + + hErr := web.HTTPError{} + // invalid + s.Request(http.MethodPost, "/api/v1/auth/password/code", &passwordNew, http.StatusBadRequest, &hErr) + assert.Equal(web.APIErrorInvalidRequestFormat, hErr.Message) + + res := "" + // set new password + s.Request(http.MethodPost, "/api/v1/auth/password/code", &PasswordWithForgetCode{ + ForgetCode: forgetCode, + Password: passwordNew, + }, http.StatusOK, &res) + assert.Equal("admin", res) + + hErr = web.HTTPError{} + // set password without code + s.Request(http.MethodPost, "/api/v1/auth/password/code", &PasswordWithForgetCode{ + ForgetCode: forgetCode, + Password: passwordCurrent, + }, http.StatusBadRequest, &hErr) + assert.Equal(APIErrorUserNotFound, hErr.Message) + + forgetCode = uuid.New() + s.DB.DB.Model(&User{ID: TestUser1ID}).Update("forget_code", forgetCode) + + res = "" + // set old password + s.Request(http.MethodPost, "/api/v1/auth/password/code", &PasswordWithForgetCode{ + ForgetCode: forgetCode, + Password: passwordCurrent, + }, http.StatusOK, &res) + assert.Equal("admin", res) +} diff --git a/web/auth/models.go b/web/auth/models.go index 827a731..8857fbe 100644 --- a/web/auth/models.go +++ b/web/auth/models.go @@ -7,9 +7,10 @@ import ( // User struct - default User model which could be extended type User struct { - ID uuid.UUID `json:"id" gorm:"type:uuid;default:gen_random_uuid()" example:"88078ec0-2135-445f-bf05-632701c77695"` - Username string `json:"username" gorm:"unique" example:"kukoon"` - Password string `json:"-" example:"super secret password"` + ID uuid.UUID `json:"id" gorm:"type:uuid;default:gen_random_uuid()" example:"88078ec0-2135-445f-bf05-632701c77695"` + Username string `json:"username" gorm:"unique" example:"kukoon"` + Password string `json:"-" example:"super secret password"` + ForgetCode *uuid.UUID `json:"-" gorm:"forget_code;type:uuid"` } // NewUser by username and password