Added jwt to cookie + database change
This commit is contained in:
parent
b772443b72
commit
20e23ef9c1
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
33
api/service/jwt/create.go
Normal 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
24
api/service/jwt/verify.go
Normal 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
|
||||||
|
}
|
||||||
@ -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
|
||||||
@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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
1
go.mod
@ -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
2
go.sum
@ -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=
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user