add/rm tokens, register with token
This commit is contained in:
@@ -1,10 +1,12 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
|
||||||
"git.dynamicdiscord.de/kalipso/zineshop/models"
|
"git.dynamicdiscord.de/kalipso/zineshop/models"
|
||||||
"git.dynamicdiscord.de/kalipso/zineshop/repositories"
|
"git.dynamicdiscord.de/kalipso/zineshop/repositories"
|
||||||
@@ -188,13 +190,14 @@ func (rc *UserController) RegisterHandler(c *gin.Context) {
|
|||||||
|
|
||||||
tokenExists, err := repositories.Tokens.Exists(token)
|
tokenExists, err := repositories.Tokens.Exists(token)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
data := gin.H{
|
data := gin.H{
|
||||||
"error": err,
|
"error": err,
|
||||||
"success": "",
|
"success": "",
|
||||||
}
|
}
|
||||||
|
|
||||||
c.HTML(http.StatusOK, "register.html", data)
|
c.HTML(http.StatusOK, "register.html", data)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !tokenExists {
|
if !tokenExists {
|
||||||
@@ -203,6 +206,7 @@ func (rc *UserController) RegisterHandler(c *gin.Context) {
|
|||||||
"success": "",
|
"success": "",
|
||||||
}
|
}
|
||||||
c.HTML(http.StatusOK, "register.html", data)
|
c.HTML(http.StatusOK, "register.html", data)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = services.Users.Register(name, email, password, false)
|
_, err = services.Users.Register(name, email, password, false)
|
||||||
@@ -233,9 +237,10 @@ func (rc *UserController) RegisterView(c *gin.Context) {
|
|||||||
data := gin.H{
|
data := gin.H{
|
||||||
"error": "",
|
"error": "",
|
||||||
"success": "",
|
"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) {
|
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)
|
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) {
|
func (rc *UserController) MainView(c *gin.Context) {
|
||||||
shopItems, _ := repositories.ShopItems.GetAll()
|
shopItems, _ := repositories.ShopItems.GetAll()
|
||||||
fmt.Println(len(shopItems))
|
fmt.Println(len(shopItems))
|
||||||
|
|||||||
4
main.go
4
main.go
@@ -89,7 +89,9 @@ func main() {
|
|||||||
viewRoutes.GET("/logout", userController.Logout)
|
viewRoutes.GET("/logout", userController.Logout)
|
||||||
viewRoutes.GET("/register", userController.InitAdmin)
|
viewRoutes.GET("/register", userController.InitAdmin)
|
||||||
viewRoutes.GET("/register/:token", userController.RegisterView)
|
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("/additem", authValidator.RequireAuth, shopItemController.AddItemView)
|
||||||
viewRoutes.GET("/batchupload", authValidator.RequireAuth, shopItemController.AddItemsView)
|
viewRoutes.GET("/batchupload", authValidator.RequireAuth, shopItemController.AddItemsView)
|
||||||
viewRoutes.POST("/login", userController.LoginHandler)
|
viewRoutes.POST("/login", userController.LoginHandler)
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
package middlewares
|
package middlewares
|
||||||
|
|
||||||
import(
|
import (
|
||||||
"os"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"time"
|
"time"
|
||||||
//"strconv"
|
//"strconv"
|
||||||
"net/http"
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/golang-jwt/jwt/v5"
|
"github.com/golang-jwt/jwt/v5"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
//"git.dynamicdiscord.de/kalipso/zineshop/models"
|
//"git.dynamicdiscord.de/kalipso/zineshop/models"
|
||||||
"git.dynamicdiscord.de/kalipso/zineshop/repositories"
|
"git.dynamicdiscord.de/kalipso/zineshop/repositories"
|
||||||
@@ -98,6 +98,63 @@ func (av *AuthValidator) RequireAuth(c *gin.Context) {
|
|||||||
c.AbortWithStatus(http.StatusUnauthorized)
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
func (av *AuthValidator) OptionalAuth(c *gin.Context) {
|
func (av *AuthValidator) OptionalAuth(c *gin.Context) {
|
||||||
defer c.Next()
|
defer c.Next()
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package repositories
|
package repositories
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
@@ -59,9 +60,13 @@ func (t *GORMRegisterTokenRepository) GetAll() ([]models.RegisterToken, error) {
|
|||||||
|
|
||||||
func (t *GORMRegisterTokenRepository) Exists(tokenString string) (bool, error) {
|
func (t *GORMRegisterTokenRepository) Exists(tokenString string) (bool, error) {
|
||||||
var token models.RegisterToken
|
var token models.RegisterToken
|
||||||
result := t.DB.First(&token, tokenString)
|
result := t.DB.First(&token, "token = ?", tokenString)
|
||||||
|
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
|
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
return false, result.Error
|
return false, result.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
19
utils/utils.go
Normal file
19
utils/utils.go
Normal file
@@ -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)
|
||||||
|
}
|
||||||
28
views/invites.html
Normal file
28
views/invites.html
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{{ template "header.html" . }}
|
||||||
|
|
||||||
|
|
||||||
|
<div class="flex min-h-full flex-col justify-center px-6 py-12 lg:px-8">
|
||||||
|
<div class="sm:mx-auto sm:w-full sm:max-w-sm">
|
||||||
|
<img class="mx-auto h-10 w-auto" src="/static/img/logo-black.png" alt="Your Company">
|
||||||
|
<h2 class="mt-10 text-center text-2xl/9 font-bold tracking-tight text-gray-900">Create/Delete Invites</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
|
||||||
|
{{ range .tokens }}
|
||||||
|
<form action="/invites" method="POST">
|
||||||
|
<div class="max-w-md mx-auto mt-4">
|
||||||
|
<div class="flex">
|
||||||
|
<input type="text" id="token" name="token" value="{{ .Token }}" readonly="readonly" class="flex-grow border border-gray-300 rounded-md p-2 focus:outline-none focus:ring focus:ring-blue-500">
|
||||||
|
<button type="submit" name="action" value="delete" class="bg-red-800 text-white rounded px-4 hover:bg-red-900">Delete</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{{ end }}
|
||||||
|
<form action="/invites" method="POST">
|
||||||
|
<div class="max-w-md mx-auto mt-4">
|
||||||
|
<div class="flex">
|
||||||
|
<button type="submit" name="action" value="create" class="bg-green-600 text-white ml-4 mr-4 rounded px-4 hover:bg-green-700">Create</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{{ template "footer.html" . }}
|
||||||
59
views/registertoken.html
Normal file
59
views/registertoken.html
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
{{ template "header.html" . }}
|
||||||
|
|
||||||
|
<div class="flex min-h-full flex-col justify-center px-6 py-12 lg:px-8">
|
||||||
|
<div class="sm:mx-auto sm:w-full sm:max-w-sm">
|
||||||
|
<img class="mx-auto h-10 w-auto" src="/static/img/logo-black.png" alt="Logo">
|
||||||
|
<h2 class="mt-10 text-center text-2xl/9 font-bold tracking-tight text-gray-900">Register your account</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
|
||||||
|
<form class="space-y-6" action="/register" method="POST">
|
||||||
|
<div>
|
||||||
|
<label for="token" class="block text-sm/6 font-medium text-gray-900">Token</label>
|
||||||
|
<div class="mt-2">
|
||||||
|
<input type="text" name="token" id="token" value="{{ .token }}" required class="block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="name" class="block text-sm/6 font-medium text-gray-900">Username</label>
|
||||||
|
<div class="mt-2">
|
||||||
|
<input type="text" name="name" id="name" required class="block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="email" class="block text-sm/6 font-medium text-gray-900">Email address</label>
|
||||||
|
<div class="mt-2">
|
||||||
|
<input type="email" name="email" id="email" autocomplete="email" required class="block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<label for="password" class="block text-sm/6 font-medium text-gray-900">Password</label>
|
||||||
|
</div>
|
||||||
|
<div class="mt-2">
|
||||||
|
<input type="password" name="password" id="password" autocomplete="current-password" required class="block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="mt-10 text-center text-sm/6 text-red-500">
|
||||||
|
{{ .error }}
|
||||||
|
</p>
|
||||||
|
<p class="mt-10 text-center text-sm/6 text-green-500">
|
||||||
|
{{ .success }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button type="submit" class="flex w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm/6 font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">Register</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{{ template "footer.html" . }}
|
||||||
Reference in New Issue
Block a user