From 3955d8626aef43efb0c86e723c44835dbb9660f8 Mon Sep 17 00:00:00 2001 From: kalipso Date: Tue, 15 Apr 2025 00:12:34 +0200 Subject: [PATCH] add/rm tokens, register with token --- controllers/userController.go | 41 ++++++++++++- main.go | 4 +- middlewares/requireAuth.go | 79 +++++++++++++++++++++---- repositories/registerTokenRepository.go | 7 ++- utils/utils.go | 19 ++++++ views/invites.html | 28 +++++++++ views/registertoken.html | 59 ++++++++++++++++++ 7 files changed, 222 insertions(+), 15 deletions(-) create mode 100644 utils/utils.go create mode 100644 views/invites.html create mode 100644 views/registertoken.html diff --git a/controllers/userController.go b/controllers/userController.go index ba31b35..de5ff66 100644 --- a/controllers/userController.go +++ b/controllers/userController.go @@ -1,10 +1,12 @@ package controllers import ( + "errors" "fmt" "net/http" "github.com/gin-gonic/gin" + "gorm.io/gorm" "git.dynamicdiscord.de/kalipso/zineshop/models" "git.dynamicdiscord.de/kalipso/zineshop/repositories" @@ -188,13 +190,14 @@ func (rc *UserController) RegisterHandler(c *gin.Context) { tokenExists, err := repositories.Tokens.Exists(token) - if err != nil { + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { data := gin.H{ "error": err, "success": "", } c.HTML(http.StatusOK, "register.html", data) + return } if !tokenExists { @@ -203,6 +206,7 @@ func (rc *UserController) RegisterHandler(c *gin.Context) { "success": "", } c.HTML(http.StatusOK, "register.html", data) + return } _, err = services.Users.Register(name, email, password, false) @@ -233,9 +237,10 @@ func (rc *UserController) RegisterView(c *gin.Context) { data := gin.H{ "error": "", "success": "", + "token": c.Param("token"), } - c.HTML(http.StatusOK, "register.html", data) + c.HTML(http.StatusOK, "registertoken.html", data) } func (rc *UserController) InitAdmin(c *gin.Context) { @@ -292,6 +297,38 @@ func (rc *UserController) ResetHandler(c *gin.Context) { c.HTML(http.StatusOK, "passwordreset.html", data) } +func (rc *UserController) InviteView(c *gin.Context) { + tokens, _ := repositories.Tokens.GetAll() + fmt.Println(tokens) + + data := gin.H{ + "tokens": tokens, + } + + c.HTML(http.StatusOK, "invites.html", data) +} + +func (rc *UserController) InviteHandler(c *gin.Context) { + action := c.PostForm("action") + + if action == "create" { + _, err := repositories.Tokens.Create() + + if err != nil { + fmt.Println(err) + c.HTML(http.StatusBadRequest, "error.html", gin.H{"error": err}) + return + } + } + + if action == "delete" { + token := c.PostForm("token") + repositories.Tokens.Delete(token) + } + + rc.InviteView(c) +} + func (rc *UserController) MainView(c *gin.Context) { shopItems, _ := repositories.ShopItems.GetAll() fmt.Println(len(shopItems)) diff --git a/main.go b/main.go index b3f9f3b..3f1d114 100644 --- a/main.go +++ b/main.go @@ -89,7 +89,9 @@ func main() { viewRoutes.GET("/logout", userController.Logout) viewRoutes.GET("/register", userController.InitAdmin) viewRoutes.GET("/register/:token", userController.RegisterView) - viewRoutes.GET("/passwordreset", userController.ResetView) + viewRoutes.GET("/invites", userController.InviteView) + viewRoutes.POST("/invites", userController.InviteHandler) + viewRoutes.GET("/passwordreset", authValidator.RequireAuth, userController.ResetView) viewRoutes.GET("/additem", authValidator.RequireAuth, shopItemController.AddItemView) viewRoutes.GET("/batchupload", authValidator.RequireAuth, shopItemController.AddItemsView) viewRoutes.POST("/login", userController.LoginHandler) diff --git a/middlewares/requireAuth.go b/middlewares/requireAuth.go index 43e9bcc..7c5cbc1 100644 --- a/middlewares/requireAuth.go +++ b/middlewares/requireAuth.go @@ -1,13 +1,13 @@ package middlewares -import( - "os" +import ( "fmt" + "os" "time" //"strconv" - "net/http" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" + "net/http" //"git.dynamicdiscord.de/kalipso/zineshop/models" "git.dynamicdiscord.de/kalipso/zineshop/repositories" @@ -70,7 +70,7 @@ func (av *AuthValidator) RequireAuth(c *gin.Context) { c.AbortWithStatus(http.StatusUnauthorized) return } - + if claims, ok := token.Claims.(jwt.MapClaims); ok { //Check Expiration if float64(time.Now().Unix()) > claims["exp"].(float64) { @@ -78,7 +78,7 @@ func (av *AuthValidator) RequireAuth(c *gin.Context) { c.AbortWithStatus(http.StatusUnauthorized) return } - + //Find user user, err := repositories.Users.GetById(claims["sub"]) @@ -86,15 +86,72 @@ func (av *AuthValidator) RequireAuth(c *gin.Context) { c.AbortWithStatus(http.StatusUnauthorized) return } - + //Attach to req c.Set("user", user) - + // Coninue c.Next() return } - + + c.AbortWithStatus(http.StatusUnauthorized) +} + +func (av *AuthValidator) RequireAdmin(c *gin.Context) { + // Get Cookie + tokenString, err := c.Cookie("Authorization") + + if err != nil { + c.AbortWithStatus(http.StatusUnauthorized) + return + } + + //Validate + token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + // Don't forget to validate the alg is what you expect: + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) + } + + // hmacSampleSecret is a []byte containing your secret, e.g. []byte("my_secret_key") + return []byte(os.Getenv("SECRET")), nil + }) + + if err != nil { + c.AbortWithStatus(http.StatusUnauthorized) + return + } + + if claims, ok := token.Claims.(jwt.MapClaims); ok { + //Check Expiration + if float64(time.Now().Unix()) > claims["exp"].(float64) { + //expired + c.AbortWithStatus(http.StatusUnauthorized) + return + } + + //Find user + user, err := repositories.Users.GetById(claims["sub"]) + + if err != nil { + c.AbortWithStatus(http.StatusUnauthorized) + return + } + + if !user.IsAdmin { + c.AbortWithStatus(http.StatusUnauthorized) + return + } + + //Attach to req + c.Set("user", user) + + // Coninue + c.Next() + return + } + c.AbortWithStatus(http.StatusUnauthorized) } @@ -119,19 +176,19 @@ func (av *AuthValidator) OptionalAuth(c *gin.Context) { if err != nil { return } - + if claims, ok := token.Claims.(jwt.MapClaims); ok { if float64(time.Now().Unix()) > claims["exp"].(float64) { return } - + //Find user user, err := repositories.Users.GetById(claims["sub"]) if err != nil { return } - + //Attach to req c.Set("user", user) } diff --git a/repositories/registerTokenRepository.go b/repositories/registerTokenRepository.go index 8d8d1d4..99391a2 100644 --- a/repositories/registerTokenRepository.go +++ b/repositories/registerTokenRepository.go @@ -1,6 +1,7 @@ package repositories import ( + "errors" "fmt" "gorm.io/gorm" @@ -59,9 +60,13 @@ func (t *GORMRegisterTokenRepository) GetAll() ([]models.RegisterToken, error) { func (t *GORMRegisterTokenRepository) Exists(tokenString string) (bool, error) { var token models.RegisterToken - result := t.DB.First(&token, tokenString) + result := t.DB.First(&token, "token = ?", tokenString) if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return false, nil + } + return false, result.Error } diff --git a/utils/utils.go b/utils/utils.go new file mode 100644 index 0000000..66c4d20 --- /dev/null +++ b/utils/utils.go @@ -0,0 +1,19 @@ +package utils + +import ( + "crypto/rand" + "encoding/hex" +) + +func GenerateSessionId(length int) string { + bytes := make([]byte, length) // 16 bytes = 128 bits + _, err := rand.Read(bytes) + if err != nil { + panic("failed to generate session ID") + } + return hex.EncodeToString(bytes) +} + +func GenerateToken() string { + return GenerateSessionId(16) +} diff --git a/views/invites.html b/views/invites.html new file mode 100644 index 0000000..0d2e303 --- /dev/null +++ b/views/invites.html @@ -0,0 +1,28 @@ +{{ template "header.html" . }} + + +
+
+ Your Company +

Create/Delete Invites

+
+ +
+ {{ range .tokens }} +
+
+
+ + +
+ + {{ end }} +
+
+
+ +
+
+
+
+{{ template "footer.html" . }} diff --git a/views/registertoken.html b/views/registertoken.html new file mode 100644 index 0000000..38bbf14 --- /dev/null +++ b/views/registertoken.html @@ -0,0 +1,59 @@ +{{ template "header.html" . }} + +
+
+ Logo +

Register your account

+
+ +
+
+
+ +
+ +
+
+ + +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+
+ +
+
+ +
+
+ +

+ {{ .error }} +

+

+ {{ .success }} +

+ + + +
+ +
+
+
+
+ + +{{ template "footer.html" . }}