Added jwt to cookie + database change

This commit is contained in:
darius 2024-05-19 17:49:20 +02:00
parent b772443b72
commit 20e23ef9c1
15 changed files with 130 additions and 35 deletions

View File

@ -12,9 +12,9 @@ func ApiRoutes() *http.ServeMux {
// Register the routes and webHandler // Register the routes and webHandler
mux.HandleFunc("/", handlers.CatchAllHandler) mux.HandleFunc("/", handlers.CatchAllHandler)
mux.HandleFunc("POST /api/user", handlers.CreateUser) mux.HandleFunc("POST /register", handlers.CreateUser)
mux.HandleFunc("GET /api/user/{id}", handlers.GetUser) mux.HandleFunc("GET /user/{id}", handlers.GetUser)
mux.HandleFunc("POST /api/login", handlers.Login) mux.HandleFunc("POST /login", handlers.Login)
return mux return mux
} }

View File

@ -3,16 +3,14 @@ package handlers
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"fmt"
"net/http" "net/http"
"portfolio/api/service/bcrypt"
"portfolio/api/service/jwt"
"portfolio/database/ent" "portfolio/database/ent"
"portfolio/database/ent/user"
"portfolio/database/query" "portfolio/database/query"
"portfolio/service/bcrypt"
) )
func Login(w http.ResponseWriter, r *http.Request) { func Login(w http.ResponseWriter, r *http.Request) {
fmt.Println("test")
var u *ent.User var u *ent.User
isHtmx := r.Header.Get("HX-Request") isHtmx := r.Header.Get("HX-Request")
@ -21,31 +19,40 @@ func Login(w http.ResponseWriter, r *http.Request) {
u = &ent.User{ u = &ent.User{
Name: r.PostFormValue("name"), Name: r.PostFormValue("name"),
Password: r.PostFormValue("password"), Password: r.PostFormValue("password"),
Role: user.Role(r.PostFormValue("role")),
} }
} else { } else {
err := json.NewDecoder(r.Body).Decode(&u) err := json.NewDecoder(r.Body).Decode(&u)
if err != nil { if err != nil {
InternalServerErrorHandler(w) InternalServerErrorHandler(w, err)
} }
} }
User, err := query.GetLogin(context.Background(), u.Name) User, err := query.GetLogin(context.Background(), u)
if err != nil { if err != nil {
UnprocessableEntityHandler(w, err)
return return
} }
if bcrypt.CheckPasswordHash(u.Password, User.Password) { if bcrypt.CheckPasswordHash(u.Password, User.Password) {
w.Header().Set("HX-Location", "/")
return jwtToken := jwt.CreateUserJWT(u.Name, u.ID, string(u.Role))
if jwtToken != "" {
w.Header().Set("HX-Location", "/")
cookie := &http.Cookie{Name: "jwt", Value: jwtToken, HttpOnly: true, Secure: true, SameSite: http.SameSiteStrictMode}
http.SetCookie(w, cookie)
w.WriteHeader(http.StatusOK)
_, err = w.Write([]byte("login success"))
return
} else {
InternalServerErrorHandler(w, err)
return
}
} else { } else {
UnauthorizedHandler(w)
}
w.Header().Set("Content-Type", "application/json")
err = json.NewEncoder(w).Encode(User)
if err != nil {
return return
} }
} }

View File

@ -2,12 +2,13 @@ package handlers
import "net/http" import "net/http"
func InternalServerErrorHandler(w http.ResponseWriter) { func InternalServerErrorHandler(w http.ResponseWriter, err error) {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError) //set http status
_, err := w.Write([]byte("500 Internal Server Error")) _, err = w.Write([]byte(err.Error())) //set response message
if err != nil { if err != nil {
return return
} }
return
} }
func NotFoundHandler(w http.ResponseWriter) { func NotFoundHandler(w http.ResponseWriter) {
@ -25,3 +26,21 @@ func BadRequestHandler(w http.ResponseWriter) {
return return
} }
} }
func UnprocessableEntityHandler(w http.ResponseWriter, err error) {
w.WriteHeader(http.StatusUnprocessableEntity) //set http status
_, err = w.Write([]byte(err.Error())) //set response message
if err != nil {
return
}
return
}
func UnauthorizedHandler(w http.ResponseWriter) {
w.WriteHeader(http.StatusUnauthorized) //set http status
_, err := w.Write([]byte("Unauthorized")) //set response message
if err != nil {
return
}
return
}

View File

@ -6,6 +6,6 @@ func CatchAllHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusGone) w.WriteHeader(http.StatusGone)
_, err := w.Write([]byte("Bad endpoint")) _, err := w.Write([]byte("Bad endpoint"))
if err != nil { if err != nil {
InternalServerErrorHandler(w) InternalServerErrorHandler(w, err)
} }
} }

View File

@ -4,10 +4,11 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"net/http" "net/http"
"portfolio/api/service/bcrypt"
"portfolio/api/service/validate"
"portfolio/database/ent" "portfolio/database/ent"
"portfolio/database/ent/user" "portfolio/database/ent/user"
"portfolio/database/query" "portfolio/database/query"
"portfolio/service/validate"
"strconv" "strconv"
) )
@ -25,7 +26,7 @@ func CreateUser(w http.ResponseWriter, r *http.Request) {
} else { } else {
err := json.NewDecoder(r.Body).Decode(&u) err := json.NewDecoder(r.Body).Decode(&u)
if err != nil { if err != nil {
InternalServerErrorHandler(w) InternalServerErrorHandler(w, err)
} }
} }
@ -34,8 +35,12 @@ func CreateUser(w http.ResponseWriter, r *http.Request) {
return return
} }
//hash password
u.Password, _ = bcrypt.HashPassword(u.Password)
err := query.CreateUser(context.Background(), *u) err := query.CreateUser(context.Background(), *u)
if err != nil { if err != nil {
UnprocessableEntityHandler(w, err)
return return
} }
w.WriteHeader(http.StatusCreated) w.WriteHeader(http.StatusCreated)

33
api/service/jwt/create.go Normal file
View File

@ -0,0 +1,33 @@
package jwt
import (
"github.com/golang-jwt/jwt/v5"
"os"
"strconv"
"time"
)
func CreateUserJWT(name string, uid int, role string) string {
//create claims for jwt
claims := jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now()),
NotBefore: jwt.NewNumericDate(time.Now()),
Issuer: "portfolio.dariusklein.nl",
Subject: name,
ID: strconv.Itoa(uid),
Audience: []string{role},
}
return SignJWT(claims)
}
func SignJWT(claims jwt.Claims) string {
//Build jwt with claims
t := jwt.NewWithClaims(jwt.SigningMethodHS512, claims)
//get jwt secret from environment
secret := os.Getenv("JWT_SECRET")
//sign jwt token with secret
token, _ := t.SignedString([]byte(secret))
return token
}

24
api/service/jwt/verify.go Normal file
View File

@ -0,0 +1,24 @@
package jwt
import (
_ "context"
"github.com/golang-jwt/jwt/v5"
"os"
"strconv"
)
// VerifyJWT verify JWT token and returns user object
func VerifyJWT(authToken string) (int, string, error) {
//get jwt secret from environment
secret := os.Getenv("JWT_SECRET")
//parse jwt token
token, err := jwt.ParseWithClaims(authToken, &jwt.RegisteredClaims{}, func(token *jwt.Token) (interface{}, error) {
return []byte(secret), nil
})
if err != nil {
return 0, "", err
}
uid, err := strconv.Atoi(token.Claims.(*jwt.RegisteredClaims).ID)
audience := token.Claims.(*jwt.RegisteredClaims).Audience[0]
return uid, audience, err
}

View File

@ -6,7 +6,8 @@ import (
func UserIsValid(u *ent.User) bool { func UserIsValid(u *ent.User) bool {
if len(u.Name) > 0 && if len(u.Name) > 0 &&
len(u.Role) > 0 { len(u.Email) > 0 &&
len(u.Password) > 0 {
return true return true
} }
return false return false

View File

@ -14,7 +14,8 @@ type Team struct {
// Fields of the Team. // Fields of the Team.
func (Team) Fields() []ent.Field { func (Team) Fields() []ent.Field {
return []ent.Field{ return []ent.Field{
field.String("name"), field.String("name").
Unique(),
} }
} }

View File

@ -16,10 +16,11 @@ func (User) Fields() []ent.Field {
return []ent.Field{ return []ent.Field{
field.String("name"). field.String("name").
Unique(), Unique(),
field.String("email"), field.String("email").
Unique(),
field.String("password"), field.String("password"),
field.Enum("role"). field.Enum("role").
Values("admin", "user", "visitor"), Values("owner", "admin", "user", "visitor"),
} }
} }

View File

@ -9,11 +9,10 @@ import (
"portfolio/database/ent/user" "portfolio/database/ent/user"
) )
func GetLogin(ctx context.Context, name string) (*ent.User, error) { func GetLogin(ctx context.Context, U *ent.User) (*ent.User, error) {
u, err := database.Client.User. u, err := database.Client.User.
Query(). Query().
Where(user.Name(name)). Where(user.Name(U.Name)).
Only(ctx) Only(ctx)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed querying user: %w", err) return nil, fmt.Errorf("failed querying user: %w", err)

View File

@ -22,12 +22,14 @@ func GetUser(ctx context.Context, id int) (*ent.User, error) {
return u, nil return u, nil
} }
func CreateUser(ctx context.Context, User ent.User) error { func CreateUser(ctx context.Context, user ent.User) error {
_, err := database.Client.User. _, err := database.Client.User.
Create(). Create().
SetName(User.Name). SetName(user.Name).
SetRole(User.Role). SetEmail(user.Email).
SetPassword(user.Password).
SetRole("visitor").
Save(ctx) Save(ctx)
if err != nil { if err != nil {
return fmt.Errorf("failed to create user: %w", err) return fmt.Errorf("failed to create user: %w", err)

1
go.mod
View File

@ -5,6 +5,7 @@ go 1.22.2
require ( require (
entgo.io/ent v0.13.1 entgo.io/ent v0.13.1
github.com/delaneyj/gomponents-iconify v0.0.20231025 github.com/delaneyj/gomponents-iconify v0.0.20231025
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/joho/godotenv v1.5.1 github.com/joho/godotenv v1.5.1
github.com/lib/pq v1.10.9 github.com/lib/pq v1.10.9
github.com/maragudk/gomponents v0.20.2 github.com/maragudk/gomponents v0.20.2

2
go.sum
View File

@ -16,6 +16,8 @@ github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNP
github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4= github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4=
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
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/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=