diff --git a/CHANGELOG.md b/CHANGELOG.md index e618db2..973f814 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,28 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +### [0.0.22](https://github.com/hawks-atlanta/authentication-go/compare/v0.0.5...v0.0.22) (2023-09-07) + + +### Features + +* authorization prefix ([f951146](https://github.com/hawks-atlanta/authentication-go/commit/f951146b15219fb42f78235a2badcc64059a083f)) +* authorize controller operation ([22845c6](https://github.com/hawks-atlanta/authentication-go/commit/22845c65026f853061f8e1302dd48d670e0af537)) +* challenge endpoint ([f4f23c8](https://github.com/hawks-atlanta/authentication-go/commit/f4f23c85a63354860221ff38f42394b71508e362)) +* update password ([5b2d77f](https://github.com/hawks-atlanta/authentication-go/commit/5b2d77fb0041731c8819948ddccef0bd33d6ede2)) +* user uuid by username ([c81b42f](https://github.com/hawks-atlanta/authentication-go/commit/c81b42f27428b8ab0f25dbcbd88bd6ebb3a3ac86)) +* v0.0.10 ([#12](https://github.com/hawks-atlanta/authentication-go/issues/12)) ([96549ef](https://github.com/hawks-atlanta/authentication-go/commit/96549eff4bb24932dec1fca949692bf5efe1395d)) +* v0.0.21 ([#35](https://github.com/hawks-atlanta/authentication-go/issues/35)) ([e2302d5](https://github.com/hawks-atlanta/authentication-go/commit/e2302d52080936b1ca6113c87c655c527f79b4b0)), closes [#33](https://github.com/hawks-atlanta/authentication-go/issues/33) [#34](https://github.com/hawks-atlanta/authentication-go/issues/34) +* v0.0.7 ([#8](https://github.com/hawks-atlanta/authentication-go/issues/8)) ([e9a8acd](https://github.com/hawks-atlanta/authentication-go/commit/e9a8acdf6d14be40ed3c05f62b71de28bab8ad83)), closes [#5](https://github.com/hawks-atlanta/authentication-go/issues/5) [#6](https://github.com/hawks-atlanta/authentication-go/issues/6) [#7](https://github.com/hawks-atlanta/authentication-go/issues/7) + + +### Bug Fixes + +* added missing env setup in dockerfile related to JWT secret ([e035eed](https://github.com/hawks-atlanta/authentication-go/commit/e035eed7fc422a842c4547491e7b261f72b3f391)) +* fmt ([1653f19](https://github.com/hawks-atlanta/authentication-go/commit/1653f198fcbed059d14a657afe3b553983e5f531)) +* removed line ([#15](https://github.com/hawks-atlanta/authentication-go/issues/15)) ([6b3899f](https://github.com/hawks-atlanta/authentication-go/commit/6b3899f5c8a3da1f15c0522b452f5dc5a326a601)) +* triggering pipeline ([f1e7475](https://github.com/hawks-atlanta/authentication-go/commit/f1e7475f6d5595c5bcc656136ef6093ff647dcfc)) + ### 0.0.21 (2023-09-06) diff --git a/CLI.md b/CLI.md index d327d0a..63d0823 100644 --- a/CLI.md +++ b/CLI.md @@ -6,6 +6,7 @@ This document describes how to use the service as a CLI tool. | Variable | Description | Example | | ----------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | +| `JWT_SECRET` | Secret used by the JWT engine | `CAPY_FILE` | | `DATABASE_ENGINE` | Database engine to use. Available are `postgres` and `sqlite` | `postgres` | | `DATABASE_DSN` | Database connection DSN to use | `host=127.0.0.1 user=sulcud password=sulcud dbname=sulcud port=5432 sslmode=disable` | diff --git a/Dockerfile b/Dockerfile index 3d6bb11..137afa0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,6 +18,7 @@ USER application COPY --from=builder /build/ /opt/application WORKDIR /opt/application/db WORKDIR / +ENV JWT_SECRET "CAPY_FILE" ENV DATABASE_ENGINE "sqlite" ENV DATABASE_DSN "/opt/application/db/database.db?cache=shared&mode=rwc" ENTRYPOINT ["/opt/application/authentication-go", ":8080"] diff --git a/controller/auth.go b/controller/auth.go new file mode 100644 index 0000000..96a2adf --- /dev/null +++ b/controller/auth.go @@ -0,0 +1,31 @@ +package controller + +import ( + "errors" + + "github.com/golang-jwt/jwt/v5" + "github.com/hawks-atlanta/authentication-go/models" + "gorm.io/gorm" +) + +func (c *Controller) Authorize(jwtS string) (user models.User, err error) { + tok, err := jwt.Parse(jwtS, c.JWT.KeyFunc) + if err != nil { + err = ErrUnauthorized + return user, err + } + var check models.User + err = check.FromClaims(tok.Claims.(jwt.MapClaims)) + if err != nil { + err = ErrUnauthorized + return user, err + } + err = c.DB. + Where("uuid = ?", check.UUID). + First(&user). + Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = ErrUnauthorized + } + return user, err +} diff --git a/controller/auth_test.go b/controller/auth_test.go new file mode 100644 index 0000000..af92da0 --- /dev/null +++ b/controller/auth_test.go @@ -0,0 +1,60 @@ +package controller + +import ( + "testing" + + "github.com/hawks-atlanta/authentication-go/database" + "github.com/hawks-atlanta/authentication-go/internal/utils/jwt" + "github.com/hawks-atlanta/authentication-go/internal/utils/random" + "github.com/hawks-atlanta/authentication-go/models" + "github.com/stretchr/testify/assert" + "gorm.io/gorm" +) + +func TestController_Authorize(t *testing.T) { + t.Run("Succeed", database.Test(func(t *testing.T, db *gorm.DB) { + assertions := assert.New(t) + + c, err := New(WithDB(db), WithSecret(random.Bytes(16))) + assertions.Nil(err) + + u := models.RandomUser() + err = c.Register(u) + assertions.Nil(err) + + token := c.JWT.New(u.Claims()) + + user, err := c.Authorize(token) + assertions.Nil(err) + + assertions.Equal(u.UUID, user.UUID) + assertions.Equal(u.Username, user.Username) + })) + t.Run("Invalid Token", database.Test(func(t *testing.T, db *gorm.DB) { + assertions := assert.New(t) + + c, err := New(WithDB(db), WithSecret(random.Bytes(16))) + assertions.Nil(err) + + _, err = c.Authorize("INVALID") + assertions.NotNil(err) + })) + t.Run("Invalid SECRET", database.Test(func(t *testing.T, db *gorm.DB) { + assertions := assert.New(t) + + c, err := New(WithDB(db), WithSecret(random.Bytes(16))) + assertions.Nil(err) + + _, err = c.Authorize(jwt.New(random.Bytes(16)).New(models.RandomUser().Claims())) + assertions.NotNil(err) + })) + t.Run("Invalid User", database.Test(func(t *testing.T, db *gorm.DB) { + assertions := assert.New(t) + + c, err := New(WithDB(db), WithSecret(random.Bytes(16))) + assertions.Nil(err) + + _, err = c.Authorize(c.JWT.New(models.RandomUser().Claims())) + assertions.NotNil(err) + })) +} diff --git a/controller/query.go b/controller/query.go new file mode 100644 index 0000000..84ed3b8 --- /dev/null +++ b/controller/query.go @@ -0,0 +1,15 @@ +package controller + +import "github.com/hawks-atlanta/authentication-go/models" + +type UserRequest struct { + Username string `json:"username"` +} + +func (c *Controller) UserByUsername(req *UserRequest) (user models.User, err error) { + err = c.DB. + Where("username = ?", req.Username). + First(&user). + Error + return user, err +} diff --git a/controller/query_test.go b/controller/query_test.go new file mode 100644 index 0000000..7d3002d --- /dev/null +++ b/controller/query_test.go @@ -0,0 +1,31 @@ +package controller + +import ( + "testing" + + "github.com/hawks-atlanta/authentication-go/database" + "github.com/hawks-atlanta/authentication-go/internal/utils/random" + "github.com/hawks-atlanta/authentication-go/models" + "github.com/stretchr/testify/assert" + "gorm.io/gorm" +) + +func TestUserByUsername(t *testing.T) { + t.Run("Succeed", database.Test(func(t *testing.T, db *gorm.DB) { + assertions := assert.New(t) + + c, err := New(WithDB(db), WithSecret(random.String(16))) + assertions.Nil(err) + + u := models.RandomUser() + err = c.Register(u) + assertions.Nil(err) + + req := UserRequest{ + Username: *u.Username, + } + ou, err := c.UserByUsername(&req) + + assertions.Equal(u.UUID, ou.UUID) + })) +} diff --git a/controller/update_password.go b/controller/update_password.go new file mode 100644 index 0000000..44e68b4 --- /dev/null +++ b/controller/update_password.go @@ -0,0 +1,25 @@ +package controller + +import ( + "github.com/hawks-atlanta/authentication-go/models" +) + +type UpdatePasswordRequest struct { + OldPassword string `json:"oldPassword"` + NewPassword string `json:"newPassword"` +} + +func (c *Controller) UpdatePassword(session *models.User, req *UpdatePasswordRequest) (err error) { + if !session.Authenticate(req.OldPassword) { + return ErrUnauthorized + } + update := models.User{ + Model: session.Model, + Password: req.NewPassword, + } + err = c.DB. + Where("uuid = ? AND password_hash = ?", session.UUID, session.PasswordHash). + Updates(&update). + Error + return err +} diff --git a/controller/update_password_test.go b/controller/update_password_test.go new file mode 100644 index 0000000..74e31d5 --- /dev/null +++ b/controller/update_password_test.go @@ -0,0 +1,55 @@ +package controller + +import ( + "testing" + + "github.com/hawks-atlanta/authentication-go/database" + "github.com/hawks-atlanta/authentication-go/internal/utils/random" + "github.com/hawks-atlanta/authentication-go/models" + "github.com/stretchr/testify/assert" + "gorm.io/gorm" +) + +func TestController_UpdatePassword(t *testing.T) { + t.Run("Succeed", database.Test(func(t *testing.T, db *gorm.DB) { + assertions := assert.New(t) + + c, err := New(WithDB(db), WithSecret(random.Bytes(16))) + assertions.Nil(err) + + u := models.RandomUser() + err = c.Register(u) + assertions.Nil(err) + + req := UpdatePasswordRequest{ + OldPassword: u.Password, + NewPassword: random.String(16), + } + err = c.UpdatePassword(u, &req) + assertions.Nil(err) + + creds := models.User{ + Username: u.Username, + Password: req.NewPassword, + } + _, err = c.Login(&creds) + assertions.Nil(err) + })) + t.Run("Invalid Password", database.Test(func(t *testing.T, db *gorm.DB) { + assertions := assert.New(t) + + c, err := New(WithDB(db), WithSecret(random.Bytes(16))) + assertions.Nil(err) + + u := models.RandomUser() + err = c.Register(u) + assertions.Nil(err) + + req := UpdatePasswordRequest{ + OldPassword: random.String(16), + NewPassword: random.String(16), + } + err = c.UpdatePassword(u, &req) + assertions.NotNil(err) + })) +} diff --git a/docs/spec.openapi.yaml b/docs/spec.openapi.yaml index e21e770..2a72533 100644 --- a/docs/spec.openapi.yaml +++ b/docs/spec.openapi.yaml @@ -75,21 +75,18 @@ paths: $ref: '#/components/schemas/statusResponse' /challenge: post: + security: + - bearerAuth: [] tags: - Authorization description: Verifies the received token is still valid - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/authorization' responses: '200': - description: Account token is still valid + description: Fresh JWT content: application/json: schema: - $ref: '#/components/schemas/account' + $ref: '#/components/schemas/authorization' '401': description: Unauthorized content: @@ -105,6 +102,8 @@ paths: /account/password: patch: + security: + - bearerAuth: [] tags: - Account description: Updates the username password @@ -112,14 +111,12 @@ paths: content: application/json: schema: - allOf: - - $ref: '#/components/schemas/authorization' - - type: object - properties: - currentPassword: - type: string - newPassword: - type: string + type: object + properties: + currentPassword: + type: string + newPassword: + type: string responses: '200': description: Password updated successfully @@ -141,6 +138,8 @@ paths: $ref: '#/components/schemas/statusResponse' /user/uuid/{username}: get: + security: + - bearerAuth: [] tags: - Account description: Obtain the uuid of an user by its username @@ -171,6 +170,11 @@ paths: $ref: '#/components/schemas/statusResponse' components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT schemas: account: type: object diff --git a/models/model.go b/models/model.go index bb414d0..dfceca9 100644 --- a/models/model.go +++ b/models/model.go @@ -8,7 +8,7 @@ import ( ) type Model struct { - UUID uuid.UUID `json:"uuid" gorm:"primaryKey;"` + UUID uuid.UUID `json:"uuid,omitempty" gorm:"primaryKey;"` CreatedAt time.Time `json:"-"` UpdatedAt time.Time `json:"-"` DeletedAt *time.Time `json:"-" sql:"index"` diff --git a/models/user.go b/models/user.go index 6db2aef..4dd5d6a 100644 --- a/models/user.go +++ b/models/user.go @@ -15,8 +15,8 @@ import ( type User struct { Model - Username *string `json:"username" gorm:"unique;not null;"` - Password string `json:"password" gorm:"-"` + Username *string `json:"username,omitempty" gorm:"unique;not null;"` + Password string `json:"password,omitempty" gorm:"-"` PasswordHash []byte `json:"-" gorm:"not null;"` } diff --git a/package-lock.json b/package-lock.json index 8ced59a..e194249 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { "name": "authentication-go", - "version": "0.0.21", + "version": "0.0.22", "lockfileVersion": 3, "requires": true, "packages": { "": { - "version": "0.0.21", + "version": "0.0.22", "devDependencies": { "git-semver-tags": "^4.1.1", "standard-version": "^9.5.0" diff --git a/package.json b/package.json index 0f77be4..f31efea 100644 --- a/package.json +++ b/package.json @@ -3,5 +3,5 @@ "git-semver-tags": "^4.1.1", "standard-version": "^9.5.0" }, - "version": "0.0.21" + "version": "0.0.22" } diff --git a/router/auth.go b/router/auth.go new file mode 100644 index 0000000..b5db465 --- /dev/null +++ b/router/auth.go @@ -0,0 +1,28 @@ +package router + +import ( + "errors" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/hawks-atlanta/authentication-go/controller" +) + +func (r *Router) Authorize(ctx *gin.Context) { + header := ctx.GetHeader(AuthorizationHeader) + if len(header) < 7 { + ctx.AbortWithStatusJSON(http.StatusUnauthorized, UnauthorizedResult) + return + } + user, err := r.C.Authorize(header[7:]) + if err != nil { + if errors.Is(err, controller.ErrUnauthorized) { + ctx.AbortWithStatusJSON(http.StatusUnauthorized, UnauthorizedResult) + return + } + ctx.AbortWithStatusJSON(http.StatusInternalServerError, InternalServerError(err)) + return + } + ctx.Set(SessionVariale, &user) + ctx.Next() +} diff --git a/router/auth_test.go b/router/auth_test.go new file mode 100644 index 0000000..79dbeaf --- /dev/null +++ b/router/auth_test.go @@ -0,0 +1,57 @@ +package router + +import ( + "net/http" + "testing" + + "github.com/hawks-atlanta/authentication-go/database" + "github.com/hawks-atlanta/authentication-go/internal/utils/jwt" + "github.com/hawks-atlanta/authentication-go/models" + "gorm.io/gorm" +) + +func TestRouter_Authorization(t *testing.T) { + t.Run("Succeed", database.Test(func(t *testing.T, db *gorm.DB) { + expect, closeFunc := NewDefault(t, db) + defer closeFunc() + + u := models.RandomUser() + tok := expect. + POST(RegisterRoute). + WithJSON(u). + Expect(). + Status(http.StatusCreated). + JSON(). + Object(). + Value("jwt"). + String(). + Raw() + + expect. + PATCH(ChallengeRoute). + WithHeader(AuthorizationHeader, Bearer(tok)). + Expect(). + Status(http.StatusOK) + })) + t.Run("No Token", database.Test(func(t *testing.T, db *gorm.DB) { + expect, closeFunc := NewDefault(t, db) + defer closeFunc() + + expect. + PATCH(ChallengeRoute). + WithHeader(AuthorizationHeader, ""). + Expect(). + Status(http.StatusUnauthorized) + })) + t.Run("Invalid Session", database.Test(func(t *testing.T, db *gorm.DB) { + expect, closeFunc := NewDefault(t, db) + defer closeFunc() + + u := models.RandomUser() + expect. + PATCH(ChallengeRoute). + WithHeader(AuthorizationHeader, jwt.New([]byte("BYTE")).New(u.Claims())). + Expect(). + Status(http.StatusUnauthorized) + })) +} diff --git a/router/challenge.go b/router/challenge.go new file mode 100644 index 0000000..e2aa823 --- /dev/null +++ b/router/challenge.go @@ -0,0 +1,13 @@ +package router + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/hawks-atlanta/authentication-go/models" +) + +func (r *Router) Challenge(ctx *gin.Context) { + ctx.JSON(http.StatusOK, Token{JWT: r.C.JWT.New(ctx.MustGet(SessionVariale).(*models.User).Claims())}) + return +} diff --git a/router/echo.go b/router/echo.go deleted file mode 100644 index b2062a7..0000000 --- a/router/echo.go +++ /dev/null @@ -1,15 +0,0 @@ -package router - -import ( - "net/http" - "os" - - "github.com/gin-gonic/gin" -) - -func (r *Router) AnyEcho(ctx *gin.Context) { - ctx.JSON(http.StatusOK, struct { - Remote string `json:"remote"` - Environment []string `json:"environment"` - }{ctx.Request.RemoteAddr, os.Environ()}) -} diff --git a/router/echo_test.go b/router/echo_test.go deleted file mode 100644 index a34322c..0000000 --- a/router/echo_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package router - -import ( - "net/http" - "testing" - - "github.com/hawks-atlanta/authentication-go/database" - "gorm.io/gorm" -) - -func TestRouter_AnyEcho(t *testing.T) { - t.Run("GET", database.Test(func(t *testing.T, db *gorm.DB) { - expect, closeFunc := NewDefault(t, db) - defer closeFunc() - - expect. - GET(EchoRoute). - Expect(). - Status(http.StatusOK) - })) - t.Run("POST", database.Test(func(t *testing.T, db *gorm.DB) { - expect, closeFunc := NewDefault(t, db) - defer closeFunc() - - expect. - POST(EchoRoute). - Expect(). - Status(http.StatusOK) - })) -} diff --git a/router/query.go b/router/query.go new file mode 100644 index 0000000..dc373f9 --- /dev/null +++ b/router/query.go @@ -0,0 +1,22 @@ +package router + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/hawks-atlanta/authentication-go/controller" + "github.com/hawks-atlanta/authentication-go/models" +) + +func (r *Router) UserByUsername(ctx *gin.Context) { + var req = controller.UserRequest{ + Username: ctx.Param(UsernameParam), + } + user, err := r.C.UserByUsername(&req) + if err != nil { + ctx.JSON(http.StatusInternalServerError, InternalServerError(err)) + return + } + res := models.User{Username: user.Username} + ctx.JSON(http.StatusOK, res) +} diff --git a/router/query_test.go b/router/query_test.go new file mode 100644 index 0000000..b907206 --- /dev/null +++ b/router/query_test.go @@ -0,0 +1,57 @@ +package router + +import ( + "net/http" + "testing" + + "github.com/hawks-atlanta/authentication-go/database" + "github.com/hawks-atlanta/authentication-go/models" + "gorm.io/gorm" +) + +func TestRouter_UserByUsername(t *testing.T) { + t.Run("Succeed", database.Test(func(t *testing.T, db *gorm.DB) { + expect, closeFunc := NewDefault(t, db) + defer closeFunc() + + u := models.RandomUser() + tok := expect. + POST(RegisterRoute). + WithJSON(u). + Expect(). + Status(http.StatusCreated). + JSON(). + Object(). + Value("jwt"). + String(). + Raw() + + expect. + GET(UserUUIDRoute+"/"+*u.Username). + WithHeader(AuthorizationHeader, Bearer(tok)). + Expect(). + Status(http.StatusOK) + })) + t.Run("Invalid USERNAME", database.Test(func(t *testing.T, db *gorm.DB) { + expect, closeFunc := NewDefault(t, db) + defer closeFunc() + + u := models.RandomUser() + tok := expect. + POST(RegisterRoute). + WithJSON(u). + Expect(). + Status(http.StatusCreated). + JSON(). + Object(). + Value("jwt"). + String(). + Raw() + + expect. + GET(UserUUIDRoute+"/INVALID"). + WithHeader(AuthorizationHeader, Bearer(tok)). + Expect(). + Status(http.StatusInternalServerError) + })) +} diff --git a/router/router.go b/router/router.go index 965befd..e4e0fb2 100644 --- a/router/router.go +++ b/router/router.go @@ -16,9 +16,13 @@ func New(opts ...Option) *gin.Engine { opt(&r) } - r.Any(EchoRoute, r.AnyEcho) r.POST(LoginRoute, r.Login) r.POST(RegisterRoute, r.Register) + // Authentication required + authReq := r.Group(RootRoute, r.Authorize) + authReq.PATCH(AccountPasswordRoute, r.UpdatePassword) + authReq.GET(UserUUIDWithParamsRoute, r.UserByUsername) + authReq.Any(ChallengeRoute, r.Challenge) return r.Engine } diff --git a/router/update_password.go b/router/update_password.go new file mode 100644 index 0000000..016bce4 --- /dev/null +++ b/router/update_password.go @@ -0,0 +1,29 @@ +package router + +import ( + "errors" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/hawks-atlanta/authentication-go/controller" + "github.com/hawks-atlanta/authentication-go/models" +) + +func (r *Router) UpdatePassword(ctx *gin.Context) { + var req controller.UpdatePasswordRequest + err := ctx.Bind(&req) + if err != nil { + return + } + session := ctx.MustGet(SessionVariale).(*models.User) + err = r.C.UpdatePassword(session, &req) + if err != nil { + if errors.Is(err, controller.ErrUnauthorized) { + ctx.AbortWithStatusJSON(http.StatusUnauthorized, UnauthorizedResult) + return + } + ctx.AbortWithStatusJSON(http.StatusInternalServerError, InternalServerError(err)) + return + } + ctx.AbortWithStatusJSON(http.StatusOK, SucceedResult("Password updated successfully")) +} diff --git a/router/update_password_test.go b/router/update_password_test.go new file mode 100644 index 0000000..11080f6 --- /dev/null +++ b/router/update_password_test.go @@ -0,0 +1,93 @@ +package router + +import ( + "net/http" + "testing" + + "github.com/hawks-atlanta/authentication-go/controller" + "github.com/hawks-atlanta/authentication-go/database" + "github.com/hawks-atlanta/authentication-go/internal/utils/random" + "github.com/hawks-atlanta/authentication-go/models" + "gorm.io/gorm" +) + +func TestController_UpdatePassword(t *testing.T) { + t.Run("Succeed", database.Test(func(t *testing.T, db *gorm.DB) { + expect, closeFunc := NewDefault(t, db) + defer closeFunc() + + u := models.RandomUser() + tok := expect. + POST(RegisterRoute). + WithJSON(u). + Expect(). + Status(http.StatusCreated). + JSON(). + Object(). + Value("jwt"). + String(). + Raw() + + req := controller.UpdatePasswordRequest{ + OldPassword: u.Password, + NewPassword: random.String(16), + } + expect. + PATCH(AccountPasswordRoute). + WithHeader(AuthorizationHeader, Bearer(tok)). + WithJSON(req). + Expect(). + Status(http.StatusOK) + })) + t.Run("Invalid Old Password", database.Test(func(t *testing.T, db *gorm.DB) { + expect, closeFunc := NewDefault(t, db) + defer closeFunc() + + u := models.RandomUser() + tok := expect. + POST(RegisterRoute). + WithJSON(u). + Expect(). + Status(http.StatusCreated). + JSON(). + Object(). + Value("jwt"). + String(). + Raw() + + req := controller.UpdatePasswordRequest{ + OldPassword: "INVALID", + NewPassword: random.String(16), + } + expect. + PATCH(AccountPasswordRoute). + WithHeader(AuthorizationHeader, Bearer(tok)). + WithJSON(req). + Expect(). + Status(http.StatusUnauthorized) + })) + t.Run("Invalid JWT", database.Test(func(t *testing.T, db *gorm.DB) { + expect, closeFunc := NewDefault(t, db) + defer closeFunc() + + u := models.RandomUser() + tok := expect. + POST(RegisterRoute). + WithJSON(u). + Expect(). + Status(http.StatusCreated). + JSON(). + Object(). + Value("jwt"). + String(). + Raw() + + expect. + PATCH(AccountPasswordRoute). + WithHeader(AuthorizationHeader, Bearer(tok)). + WithHeader("Content-Type", "application/json"). + WithText("}"). + Expect(). + Status(http.StatusBadRequest) + })) +} diff --git a/router/utils.go b/router/utils.go index 3b186ef..3badaaa 100644 --- a/router/utils.go +++ b/router/utils.go @@ -1,9 +1,28 @@ package router +import "fmt" + +const ( + UsernameParam = "username" +) + +const ( + RootRoute = "/" + EchoRoute = "/echo" + LoginRoute = "/login" + RegisterRoute = "/register" + ChallengeRoute = "/challenge" + AccountPasswordRoute = "/account/password" + UserUUIDRoute = "/user/uuid" + UserUUIDWithParamsRoute = UserUUIDRoute + "/:" + UsernameParam +) + const ( - EchoRoute = "/echo" - LoginRoute = "/login" - RegisterRoute = "/register" + AuthorizationHeader = "Authorization" +) + +const ( + SessionVariale = "SESSION" ) type Result struct { @@ -16,3 +35,11 @@ var UnauthorizedResult = Result{Succeed: false, Message: "unauthorized"} func InternalServerError(err error) Result { return Result{Succeed: false, Message: err.Error()} } + +func SucceedResult(msg string) Result { + return Result{Succeed: true, Message: msg} +} + +func Bearer(tok string) string { + return fmt.Sprintf("Bearer %s", tok) +}