From 21c9067183fb81a7060df5c0062dc22c892cb671 Mon Sep 17 00:00:00 2001 From: James Tombleson Date: Tue, 26 Mar 2024 17:48:39 -0700 Subject: [PATCH 01/12] added jwt packages --- go.mod | 2 ++ go.sum | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/go.mod b/go.mod index 4ac5107..adbb909 100644 --- a/go.mod +++ b/go.mod @@ -21,10 +21,12 @@ require ( github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator v9.31.0+incompatible // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect + github.com/golang-jwt/jwt/v5 v5.2.1 // indirect github.com/google/uuid v1.5.0 // indirect github.com/huandu/go-sqlbuilder v1.25.0 // indirect github.com/huandu/xstrings v1.3.2 // indirect github.com/josharian/intern v1.0.0 // indirect + github.com/labstack/echo-jwt/v4 v4.2.0 // indirect github.com/labstack/echo/v4 v4.11.4 // indirect github.com/labstack/gommon v0.4.2 // indirect github.com/leodido/go-urn v1.4.0 // indirect diff --git a/go.sum b/go.sum index eba4381..a04d568 100644 --- a/go.sum +++ b/go.sum @@ -34,6 +34,8 @@ github.com/go-playground/validator v9.31.0+incompatible h1:UA72EPEogEnq76ehGdEDp github.com/go-playground/validator v9.31.0+incompatible/go.mod h1:yrEkQXlcI+PugkyDjY2bRrL/UBU4f3rvrgkN3V8JEig= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/huandu/go-assert v1.1.5/go.mod h1:yOLvuqZwmcHIC5rIzrBhT7D3Q9c3GFnd0JrPVhn/06U= @@ -50,6 +52,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg= github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s= +github.com/labstack/echo-jwt/v4 v4.2.0 h1:odSISV9JgcSCuhgQSV/6Io3i7nUmfM/QkBeR5GVJj5c= +github.com/labstack/echo-jwt/v4 v4.2.0/go.mod h1:MA2RqdXdEn4/uEglx0HcUOgQSyBaTh5JcaHIan3biwU= github.com/labstack/echo/v4 v4.11.4 h1:vDZmA+qNeh1pd/cCkEicDMrjtrnMGQ1QFI9gWN1zGq8= github.com/labstack/echo/v4 v4.11.4/go.mod h1:noh7EvLwqDsmh/X/HWKPUl1AjzJrhyptRyEbQJfxen8= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= From 13f5dba498ea0bb9e2527fb47fcfa99e3d03a971 Mon Sep 17 00:00:00 2001 From: James Tombleson Date: Tue, 26 Mar 2024 17:49:01 -0700 Subject: [PATCH 02/12] added Recover to help with panics --- main.go | 1 + 1 file changed, 1 insertion(+) diff --git a/main.go b/main.go index 1cf5f44..0896dc4 100644 --- a/main.go +++ b/main.go @@ -27,6 +27,7 @@ func main() { } e.Pre(middleware.RemoveTrailingSlash()) e.Pre(middleware.Logger()) + e.Pre(middleware.Recover()) v1Group := e.Group("/api/v1") v1Api := v1.NewHandler(db) From 063e677869d6c732022e27aac994a53a88e03ac1 Mon Sep 17 00:00:00 2001 From: James Tombleson Date: Tue, 26 Mar 2024 17:52:15 -0700 Subject: [PATCH 03/12] trying to remove the user repo and use a service --- .vscode/settings.json | 9 ++ api/services/userService.go | 105 ++++++++++++++++++ .../{users_test.go => userService_test.go} | 0 api/services/users.go | 43 ------- 4 files changed, 114 insertions(+), 43 deletions(-) create mode 100644 api/services/userService.go rename api/services/{users_test.go => userService_test.go} (100%) delete mode 100644 api/services/users.go diff --git a/.vscode/settings.json b/.vscode/settings.json index ae2f0c1..a160986 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,9 +1,18 @@ { "cSpell.words": [ + "echojwt", "glebarez", "gocook", "huandu", "labstack", "sqlbuilder" + ], + "sqltools.connections": [ + { + "previewLimit": 50, + "driver": "SQLite", + "database": "${workspaceFolder:go-cook}/gocook.db", + "name": "gocook" + } ] } \ No newline at end of file diff --git a/api/services/userService.go b/api/services/userService.go new file mode 100644 index 0000000..2a7e83f --- /dev/null +++ b/api/services/userService.go @@ -0,0 +1,105 @@ +package services + +import ( + "database/sql" + "errors" + "go-cook/api/models" + "go-cook/api/repositories" + "strings" + + "golang.org/x/crypto/bcrypt" +) + +const ( + ErrPasswordNotLongEnough = "password needs to be 8 character or longer" + ErrPasswordMissingSpecialCharacter = "password needs to contain one of the following: !, @, #" + ErrInvalidPassword = "invalid password" +) + +// This will handle operations that are user related, but one layer higher then the repository +type UserService struct { + repo repositories.IUserTable +} + +func NewUserService(conn *sql.DB) UserService { + return UserService{ + repo: repositories.NewUserRepository(conn), + } +} + +func (us UserService) DoesUserExist(username string) error { + _, err := us.repo.GetByName(username) + if err != nil { + return err + } + return nil +} + +func (us UserService) DoesPasswordMatchHash(username, password string) error { + passwordBytes := []byte(password) + hash, err := bcrypt.GenerateFromPassword(passwordBytes, bcrypt.DefaultCost) + if err != nil { + return err + } + + model, err := us.GetUser(username) + if err != nil { + return err + } + + if model.Hash != string(hash) { + return errors.New(ErrInvalidPassword) + } + + return nil +} + +func (us UserService) GetUser(username string) (models.UserModel, error) { + return us.repo.GetByName(username) +} + +func (us UserService) CreateNewUser(name, password string) (models.UserModel, error) { + err := us.CheckPasswordForRequirements(password) + if err != nil { + return models.UserModel{}, err + } + + us.repo.Create(name, password) + return models.UserModel{}, nil +} + +func (us UserService) CheckPasswordForRequirements(password string) error { + err := us.checkPasswordLength(password) + if err != nil { + return err + } + + err = us.checkPasswordForSpecialCharacters(password) + if err != nil { + return err + } + + return nil +} + +func (us UserService) checkPasswordLength(password string) error { + if len(password) <= 8 { + return errors.New(ErrPasswordNotLongEnough) + } + return nil +} + +func (us UserService) checkPasswordForSpecialCharacters(password string) error { + var chars []string + chars = append(chars, "!") + chars = append(chars, "@") + chars = append(chars, "#") + + for _, char := range chars { + if strings.Contains(password, char) { + return nil + } + } + + return errors.New(ErrPasswordMissingSpecialCharacter) +} diff --git a/api/services/users_test.go b/api/services/userService_test.go similarity index 100% rename from api/services/users_test.go rename to api/services/userService_test.go diff --git a/api/services/users.go b/api/services/users.go deleted file mode 100644 index bb2d355..0000000 --- a/api/services/users.go +++ /dev/null @@ -1,43 +0,0 @@ -package services - -import ( - "database/sql" - "errors" - "go-cook/api/models" - "go-cook/api/repositories" -) - -const ( - ErrPasswordNotLongEnough = "password needs to be 8 character or longer" -) - -// This will handle operations that are user related, but one layer higher then the repository -type UserService struct { - repo repositories.UserRepository -} - -func NewUserService(conn *sql.DB) UserService { - return UserService{ - repo: repositories.NewUserRepository(conn), - } -} - -func (us UserService) DoesUserExist(username string) error { - _, err := us.repo.GetByName(username) - if err != nil { - return err - } - return nil -} - -func (us UserService) CreateNewUser(name, password string) (models.UserModel, error) { - us.repo.NewUser(name, password) - return models.UserModel{}, nil -} - -func (us UserService) CheckPasswordForRequirements(password string) error { - if len(password) <= 8 { - return errors.New(ErrPasswordNotLongEnough) - } - return nil -} From e08dbea213f290bf7707d89b3e9e7a1b0992e571 Mon Sep 17 00:00:00 2001 From: James Tombleson Date: Tue, 26 Mar 2024 17:52:38 -0700 Subject: [PATCH 04/12] added a shell repo for recipes --- api/repositories/recipe.go | 45 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 api/repositories/recipe.go diff --git a/api/repositories/recipe.go b/api/repositories/recipe.go new file mode 100644 index 0000000..6ee15b3 --- /dev/null +++ b/api/repositories/recipe.go @@ -0,0 +1,45 @@ +package repositories + +import ( + "database/sql" + "errors" + "go-cook/api/models" +) + +type IRecipeTable interface { + Create(models.RecipeModel) error + List() ([]models.RecipeModel, error) + Get(id int) (models.RecipeModel, error) + Update(id int, entity models.RecipeModel) error + Delete(id int) error +} + +type RecipeRepository struct { + client *sql.DB +} + +func NewRecipeRepository(client *sql.DB) RecipeRepository { + return RecipeRepository{ + client: client, + } +} + +func (rr RecipeRepository) Create(models.RecipeModel) error { + return errors.New("not implemented") +} + +func (rr RecipeRepository) List() ([]models.RecipeModel, error) { + return []models.RecipeModel{}, errors.New("not implemented") +} + +func (rr RecipeRepository) Get(id int) (models.RecipeModel, error) { + return models.RecipeModel{}, errors.New("not implemented") +} + +func (rr RecipeRepository) Update(id int, entity models.RecipeModel) error { + return errors.New("not implemented") +} + +func (rr RecipeRepository) Delete(id int) error { + return errors.New("not implemented") +} \ No newline at end of file From f78d78d061c5486edc2d077e949aab228248a42d Mon Sep 17 00:00:00 2001 From: James Tombleson Date: Tue, 26 Mar 2024 17:53:22 -0700 Subject: [PATCH 05/12] added a interface and adding to CRUD --- api/repositories/users.go | 23 +++++++++++++++++++++-- api/repositories/users_test.go | 2 +- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/api/repositories/users.go b/api/repositories/users.go index e5ba982..1ee2e91 100644 --- a/api/repositories/users.go +++ b/api/repositories/users.go @@ -2,6 +2,7 @@ package repositories import ( "database/sql" + "errors" "fmt" "go-cook/api/models" "time" @@ -12,8 +13,17 @@ import ( const ( TableName string = "users" + ErrUserNotFound string = "requested user was not found" ) +type IUserTable interface { + GetByName(name string) (models.UserModel, error) + Create(name, password string) (int64, error) + Update(id int, entity models.UserModel) error + UpdatePassword(name, password string) error + CheckUserHash(name, password string) error +} + // Creates a new instance of UserRepository with the bound sql func NewUserRepository(conn *sql.DB) UserRepository { return UserRepository{ @@ -37,10 +47,15 @@ func (ur UserRepository) GetByName(name string) (models.UserModel, error) { return models.UserModel{}, err } - return ur.processRows(rows)[0], nil + data := ur.processRows(rows) + if (len(data) == 0) { + return models.UserModel{}, errors.New(ErrUserNotFound) + } + + return data[0], nil } -func (ur UserRepository) NewUser(name, password string) (int64, error) { +func (ur UserRepository) Create(name, password string) (int64, error) { passwordBytes := []byte(password) hash, err := bcrypt.GenerateFromPassword(passwordBytes, bcrypt.DefaultCost) if err != nil { @@ -62,6 +77,10 @@ func (ur UserRepository) NewUser(name, password string) (int64, error) { return 1, nil } +func (ur UserRepository) Update(id int, entity models.UserModel) error { + return errors.New("not implemented") +} + func (ur UserRepository) UpdatePassword(name, password string) error { _, err := ur.GetByName(name) if err != nil { diff --git a/api/repositories/users_test.go b/api/repositories/users_test.go index 283ca75..120b53b 100644 --- a/api/repositories/users_test.go +++ b/api/repositories/users_test.go @@ -20,7 +20,7 @@ func TestCanCreateNewUser(t *testing.T) { defer db.Close() repo := repositories.NewUserRepository(db) - updated, err := repo.NewUser("testing", "NotSecure") + updated, err := repo.Create("testing", "NotSecure") if err != nil { log.Println(err) t.FailNow() From 872db5b9e9bca912bd3c0c53a1c86056a99cb7b3 Mon Sep 17 00:00:00 2001 From: James Tombleson Date: Tue, 26 Mar 2024 17:53:51 -0700 Subject: [PATCH 06/12] added a ErrorResponse struct --- api/models/std.go | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 api/models/std.go diff --git a/api/models/std.go b/api/models/std.go new file mode 100644 index 0000000..efd0955 --- /dev/null +++ b/api/models/std.go @@ -0,0 +1,6 @@ +package models + +type ErrorResponse struct { + HttpCode int `json:"code"` + Message string `json:"message"` +} From d2d524ac4ed6b30259c50de8f900f0252323d82b Mon Sep 17 00:00:00 2001 From: James Tombleson Date: Tue, 26 Mar 2024 17:54:22 -0700 Subject: [PATCH 07/12] playing with the handler to get jwt working --- api/handlers/v1/auth.go | 99 ++++++++++++++++++++++++++++++++++++++ api/handlers/v1/demo.go | 5 ++ api/handlers/v1/handler.go | 36 ++++++++++++-- api/handlers/v1/users.go | 8 ++- 4 files changed, 141 insertions(+), 7 deletions(-) create mode 100644 api/handlers/v1/auth.go diff --git a/api/handlers/v1/auth.go b/api/handlers/v1/auth.go new file mode 100644 index 0000000..86f6b6b --- /dev/null +++ b/api/handlers/v1/auth.go @@ -0,0 +1,99 @@ +package v1 + +import ( + "go-cook/api/models" + "go-cook/api/repositories" + "net/http" + "time" + + "github.com/golang-jwt/jwt/v5" + "github.com/labstack/echo/v4" +) + +type JwtToken struct { + Exp time.Time `json:"exp"` + Authorized bool `json:"authorized"` + UserName string `json:"username"` + Token string `json:"token"` + jwt.RegisteredClaims +} + +func generateJwt() (string, error) { + //TODO use env here + secret := []byte("ThisIsABadSecretDontReallyUseThis") + + token := jwt.New(jwt.SigningMethodEdDSA) + claims := token.Claims.(jwt.MapClaims) + claims["exp"] = time.Now().Add(10 * time.Minute) + claims["authorized"] = true + claims["username"] = "someone" + + tokenString, err := token.SignedString(secret) + if err != nil { + return "", err + } + + return tokenString, nil +} + +func (h *Handler) AuthRegister(c echo.Context) error { + username := c.QueryParam("username") + _, err := h.userRepo.GetByName(username) + if err != nil { + // if we have an err, validate that if its not user not found. + // if the user is not found, we can use that name + if err.Error() != repositories.ErrUserNotFound { + return c.JSON(http.StatusInternalServerError, models.ErrorResponse{ + HttpCode: http.StatusInternalServerError, + Message: err.Error(), + }) + } + } + + password := c.QueryParam("password") + err = h.UserService.CheckPasswordForRequirements(password) + if err != nil { + return c.JSON(http.StatusInternalServerError, models.ErrorResponse{ + HttpCode: http.StatusInternalServerError, + Message: err.Error(), + }) + } + + _, err = h.userRepo.Create(username, password) + if err != nil { + return c.JSON(http.StatusInternalServerError, models.ErrorResponse{ + HttpCode: http.StatusInternalServerError, + Message: err.Error(), + }) + } + + return nil +} + +func (h *Handler) AuthLogin(c echo.Context) error { + username := c.QueryParam("username") + password := c.QueryParam("password") + + // check if the user exists + err := h.UserService.DoesUserExist(username) + if err != nil { + return c.JSON(http.StatusInternalServerError, err) + } + + // make sure the hash matches + err = h.UserService.DoesPasswordMatchHash(username, password) + if err != nil { + return c.JSON(http.StatusInternalServerError, err) + } + + token, err := generateJwt() + if err != nil { + return c.JSON(http.StatusInternalServerError, err) + } + + return c.JSON(http.StatusOK, token) +} + +func (h *Handler) RefreshJwtToken(c echo.Context) error { + return nil +} diff --git a/api/handlers/v1/demo.go b/api/handlers/v1/demo.go index bbab75e..2b5503d 100644 --- a/api/handlers/v1/demo.go +++ b/api/handlers/v1/demo.go @@ -47,3 +47,8 @@ func (h *Handler) HelloBody(c echo.Context) error { Message: fmt.Sprintf("Hello, %s", request.Name), }) } + + +func (h *Handler) ProtectedRoute(c echo.Context)error { + return nil +} \ No newline at end of file diff --git a/api/handlers/v1/handler.go b/api/handlers/v1/handler.go index 1788678..201d4f2 100644 --- a/api/handlers/v1/handler.go +++ b/api/handlers/v1/handler.go @@ -3,26 +3,52 @@ package v1 import ( "database/sql" "go-cook/api/repositories" + "go-cook/api/services" + "github.com/golang-jwt/jwt/v5" + echojwt "github.com/labstack/echo-jwt/v4" "github.com/labstack/echo/v4" ) type Handler struct { - userRepo repositories.UserRepository + UserService services.UserService + userRepo repositories.IUserTable + recipeRepo repositories.IRecipeTable } func NewHandler(conn *sql.DB) *Handler { return &Handler{ - userRepo: repositories.NewUserRepository(conn), + UserService: services.NewUserService(conn), + userRepo: repositories.NewUserRepository(conn), + recipeRepo: repositories.NewRecipeRepository(conn), } } func (h *Handler) Register(v1 *echo.Group) { + jwtConfig := echojwt.Config{ + NewClaimsFunc: func(c echo.Context) jwt.Claims { + return new(JwtToken) + }, + SigningKey: []byte("ThisIsABadSecretDontReallyUseThis"), + } + + v1.POST("/login", h.AuthLogin) + v1.POST("/register", h.AuthRegister) demo := v1.Group("/demo") - demo.GET("/hello", h.DemoHello) + + demo.GET("/hello", h.DemoHello) demo.GET("/hello/:who", h.HelloWho) + + demo.Use(echojwt.WithConfig(jwtConfig)) demo.GET("/hello/body", h.HelloBody) - users := v1.Group("/users") - users.POST("/new", h.NewUser) + protected := v1.Group("/demo/protected") + protected.GET("/", h.ProtectedRoute) + + //recipes := v1.Group("/recipe") + + //users := v1.Group("/users") + //users.POST("/register", h.RegisterUser) + //users.POST("/login", h.LoginUser) + //users.POST("/update/password", h.UpdatePassword) } diff --git a/api/handlers/v1/users.go b/api/handlers/v1/users.go index 3f3ed84..e2dcc37 100644 --- a/api/handlers/v1/users.go +++ b/api/handlers/v1/users.go @@ -6,6 +6,10 @@ import ( "github.com/labstack/echo/v4" ) -func (h *Handler) NewUser(c echo.Context) error { - return c.JSON(http.StatusOK, "not implemented yet") +type newUserResponse struct { + +} + +func (h *Handler) RegisterUser(c echo.Context) error { + return c.JSON(http.StatusOK, newUserResponse{}) } From bbdbb387111bfcfd155d41da59af703eb2dcc4fe Mon Sep 17 00:00:00 2001 From: James Tombleson Date: Tue, 26 Mar 2024 17:54:32 -0700 Subject: [PATCH 08/12] postman for vscode --- rest.http | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/rest.http b/rest.http index e80ad51..75892c2 100644 --- a/rest.http +++ b/rest.http @@ -1,3 +1,8 @@ +### +POST http://localhost:1323/api/v1/register?username=test&password=test +### +POST http://localhost:1323/api/v1/login?username=test&password=test +### GET http://localhost:1323/api/v1/demo/hello ### GET http://localhost:1323/api/v1/demo/hello/james @@ -5,5 +10,12 @@ GET http://localhost:1323/api/v1/demo/hello/james GET http://localhost:1323/api/v1/demo/hello/body Content-Type: application/json -name = "body" -### \ No newline at end of file +{ + "name": "body" +} +### + +POST http://localhost:1323/api/v1/login?username=test + +### +GET http://localhost:1323/api/v1/demo/protected \ No newline at end of file From dbe621ca0520b0e3029c79b28d6c181ce6b717ac Mon Sep 17 00:00:00 2001 From: James Tombleson Date: Tue, 26 Mar 2024 17:54:46 -0700 Subject: [PATCH 09/12] model structs that are not used yet --- api/models/recipe.go | 12 ++++++++++++ api/models/users.go | 4 ++++ 2 files changed, 16 insertions(+) create mode 100644 api/models/recipe.go diff --git a/api/models/recipe.go b/api/models/recipe.go new file mode 100644 index 0000000..9f98366 --- /dev/null +++ b/api/models/recipe.go @@ -0,0 +1,12 @@ +package models + +import "time" + +type RecipeModel struct { + Id int32 + Title string + Thumbnail string + Content string + CreatedAt time.Time + LastUpdated time.Time +} diff --git a/api/models/users.go b/api/models/users.go index e47dc2b..14560ca 100644 --- a/api/models/users.go +++ b/api/models/users.go @@ -9,3 +9,7 @@ type UserModel struct { CreatedAt time.Time LastUpdated time.Time } + +type UserDto struct { + +} From 2508dac595419575755cd22bb6f4004f2488a260 Mon Sep 17 00:00:00 2001 From: James Tombleson Date: Wed, 27 Mar 2024 17:24:23 -0700 Subject: [PATCH 10/12] woo! I can login and confirm my password with bcrypt! --- api/handlers/v1/auth.go | 2 +- api/services/userService.go | 15 ++++++++------- rest.http | 4 ++-- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/api/handlers/v1/auth.go b/api/handlers/v1/auth.go index 86f6b6b..5d58a90 100644 --- a/api/handlers/v1/auth.go +++ b/api/handlers/v1/auth.go @@ -22,7 +22,7 @@ func generateJwt() (string, error) { //TODO use env here secret := []byte("ThisIsABadSecretDontReallyUseThis") - token := jwt.New(jwt.SigningMethodEdDSA) + token := jwt.New(jwt.SigningMethodHS256) claims := token.Claims.(jwt.MapClaims) claims["exp"] = time.Now().Add(10 * time.Minute) claims["authorized"] = true diff --git a/api/services/userService.go b/api/services/userService.go index 2a7e83f..193c53d 100644 --- a/api/services/userService.go +++ b/api/services/userService.go @@ -36,18 +36,19 @@ func (us UserService) DoesUserExist(username string) error { } func (us UserService) DoesPasswordMatchHash(username, password string) error { - passwordBytes := []byte(password) - hash, err := bcrypt.GenerateFromPassword(passwordBytes, bcrypt.DefaultCost) - if err != nil { - return err - } + //passwordBytes := []byte(password) + //hash, err := bcrypt.GenerateFromPassword(passwordBytes, bcrypt.DefaultCost) + //if err != nil { + // return err + //} model, err := us.GetUser(username) if err != nil { return err } - if model.Hash != string(hash) { + err = bcrypt.CompareHashAndPassword([]byte(model.Hash), []byte(password)) + if err != nil { return errors.New(ErrInvalidPassword) } @@ -83,7 +84,7 @@ func (us UserService) CheckPasswordForRequirements(password string) error { } func (us UserService) checkPasswordLength(password string) error { - if len(password) <= 8 { + if len(password) < 8 { return errors.New(ErrPasswordNotLongEnough) } return nil diff --git a/rest.http b/rest.http index 75892c2..788121c 100644 --- a/rest.http +++ b/rest.http @@ -1,7 +1,7 @@ ### -POST http://localhost:1323/api/v1/register?username=test&password=test +POST http://localhost:1323/api/v1/register?username=test&password=test1234! ### -POST http://localhost:1323/api/v1/login?username=test&password=test +POST http://localhost:1323/api/v1/login?username=test&password=test1234! ### GET http://localhost:1323/api/v1/demo/hello ### From faf0bec0692006704e538545231a633a88ade55d Mon Sep 17 00:00:00 2001 From: James Tombleson Date: Wed, 27 Mar 2024 21:55:25 -0700 Subject: [PATCH 11/12] Heck yes! jwt is working and middleware is CHECKING IT !!!!!!! --- api/handlers/v1/auth.go | 6 +++--- api/handlers/v1/demo.go | 11 +++++------ api/handlers/v1/handler.go | 3 ++- rest.http | 3 ++- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/api/handlers/v1/auth.go b/api/handlers/v1/auth.go index 5d58a90..a51f8c2 100644 --- a/api/handlers/v1/auth.go +++ b/api/handlers/v1/auth.go @@ -18,7 +18,7 @@ type JwtToken struct { jwt.RegisteredClaims } -func generateJwt() (string, error) { +func generateJwt(username string) (string, error) { //TODO use env here secret := []byte("ThisIsABadSecretDontReallyUseThis") @@ -26,7 +26,7 @@ func generateJwt() (string, error) { claims := token.Claims.(jwt.MapClaims) claims["exp"] = time.Now().Add(10 * time.Minute) claims["authorized"] = true - claims["username"] = "someone" + claims["username"] = username tokenString, err := token.SignedString(secret) if err != nil { @@ -86,7 +86,7 @@ func (h *Handler) AuthLogin(c echo.Context) error { return c.JSON(http.StatusInternalServerError, err) } - token, err := generateJwt() + token, err := generateJwt(username) if err != nil { return c.JSON(http.StatusInternalServerError, err) } diff --git a/api/handlers/v1/demo.go b/api/handlers/v1/demo.go index 2b5503d..e1895a0 100644 --- a/api/handlers/v1/demo.go +++ b/api/handlers/v1/demo.go @@ -38,17 +38,16 @@ func (h *Handler) HelloBody(c echo.Context) error { if err != nil { return c.JSON(http.StatusBadRequest, HelloWhoResponse{ Success: false, - Error: err.Error(), + Error: err.Error(), }) } - + return c.JSON(http.StatusOK, HelloWhoResponse{ Success: true, Message: fmt.Sprintf("Hello, %s", request.Name), }) } - -func (h *Handler) ProtectedRoute(c echo.Context)error { - return nil -} \ No newline at end of file +func (h *Handler) ProtectedRoute(c echo.Context) error { + return c.JSON(http.StatusOK, "You have a good bearer token!") +} diff --git a/api/handlers/v1/handler.go b/api/handlers/v1/handler.go index 201d4f2..9de5b13 100644 --- a/api/handlers/v1/handler.go +++ b/api/handlers/v1/handler.go @@ -43,7 +43,8 @@ func (h *Handler) Register(v1 *echo.Group) { demo.GET("/hello/body", h.HelloBody) protected := v1.Group("/demo/protected") - protected.GET("/", h.ProtectedRoute) + protected.Use(echojwt.WithConfig(jwtConfig)) + protected.GET("", h.ProtectedRoute) //recipes := v1.Group("/recipe") diff --git a/rest.http b/rest.http index 788121c..a5a5dc3 100644 --- a/rest.http +++ b/rest.http @@ -18,4 +18,5 @@ Content-Type: application/json POST http://localhost:1323/api/v1/login?username=test ### -GET http://localhost:1323/api/v1/demo/protected \ No newline at end of file +GET http://localhost:1323/api/v1/demo/protected +Authorization: Bearer \ No newline at end of file From 565e6112a8412088002565ded187ed8d1c567be1 Mon Sep 17 00:00:00 2001 From: James Tombleson Date: Fri, 29 Mar 2024 14:49:57 -0700 Subject: [PATCH 12/12] The jwt token is now checked to see if it expires and will return an error --- api/handlers/v1/auth.go | 39 ++++++++++++++++++++++++++++++++++----- api/handlers/v1/demo.go | 11 ++++++++++- 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/api/handlers/v1/auth.go b/api/handlers/v1/auth.go index a51f8c2..86a7828 100644 --- a/api/handlers/v1/auth.go +++ b/api/handlers/v1/auth.go @@ -1,6 +1,7 @@ package v1 import ( + "errors" "go-cook/api/models" "go-cook/api/repositories" "net/http" @@ -10,6 +11,12 @@ import ( "github.com/labstack/echo/v4" ) +const ( + ErrJwtMissing = "auth token is missing" + ErrJwtClaimsMissing = "claims missing on token" + ErrJwtExpired = "auth token has expired" +) + type JwtToken struct { Exp time.Time `json:"exp"` Authorized bool `json:"authorized"` @@ -45,8 +52,8 @@ func (h *Handler) AuthRegister(c echo.Context) error { if err.Error() != repositories.ErrUserNotFound { return c.JSON(http.StatusInternalServerError, models.ErrorResponse{ HttpCode: http.StatusInternalServerError, - Message: err.Error(), - }) + Message: err.Error(), + }) } } @@ -55,7 +62,7 @@ func (h *Handler) AuthRegister(c echo.Context) error { if err != nil { return c.JSON(http.StatusInternalServerError, models.ErrorResponse{ HttpCode: http.StatusInternalServerError, - Message: err.Error(), + Message: err.Error(), }) } @@ -63,7 +70,7 @@ func (h *Handler) AuthRegister(c echo.Context) error { if err != nil { return c.JSON(http.StatusInternalServerError, models.ErrorResponse{ HttpCode: http.StatusInternalServerError, - Message: err.Error(), + Message: err.Error(), }) } @@ -73,7 +80,7 @@ func (h *Handler) AuthRegister(c echo.Context) error { func (h *Handler) AuthLogin(c echo.Context) error { username := c.QueryParam("username") password := c.QueryParam("password") - + // check if the user exists err := h.UserService.DoesUserExist(username) if err != nil { @@ -97,3 +104,25 @@ func (h *Handler) AuthLogin(c echo.Context) error { func (h *Handler) RefreshJwtToken(c echo.Context) error { return nil } + +func (h *Handler) getJwtToken(c echo.Context) (JwtToken, error) { + // Make sure that the request came with a jwtToken + token, ok := c.Get("user").(*jwt.Token) + if !ok { + return JwtToken{}, errors.New(ErrJwtMissing) + } + + // Generate the claims from the token + claims, ok := token.Claims.(*JwtToken) + if !ok { + return JwtToken{}, errors.New(ErrJwtClaimsMissing) + } + + // Check to see if the token has expired + hasExpired := claims.Exp.Compare(time.Now()) + if hasExpired == -1 { + return JwtToken{}, errors.New(ErrJwtExpired) + } + + return *claims, nil +} diff --git a/api/handlers/v1/demo.go b/api/handlers/v1/demo.go index e1895a0..597cb6f 100644 --- a/api/handlers/v1/demo.go +++ b/api/handlers/v1/demo.go @@ -2,6 +2,7 @@ package v1 import ( "fmt" + "go-cook/api/models" "net/http" "github.com/labstack/echo/v4" @@ -49,5 +50,13 @@ func (h *Handler) HelloBody(c echo.Context) error { } func (h *Handler) ProtectedRoute(c echo.Context) error { - return c.JSON(http.StatusOK, "You have a good bearer token!") + token, err := h.getJwtToken(c) + if err != nil { + return c.JSON(http.StatusForbidden, models.ErrorResponse{ + HttpCode: http.StatusForbidden, + Message: err.Error(), + }) + } + + return c.JSON(http.StatusOK, token) }