diff --git a/src/auth/auth.go b/src/auth/auth.go new file mode 100644 index 0000000..1ff0133 --- /dev/null +++ b/src/auth/auth.go @@ -0,0 +1,79 @@ +package auth + +import ( + "fmt" + "time" + + "github.com/gin-gonic/gin" + "github.com/golang-jwt/jwt/v5" +) + +var jwtSecret = []byte("SUPER_SECRET_CHANGE_THIS") + +type Claims struct { + Username string `json:"username"` + jwt.RegisteredClaims +} + +func GenerateToken(username string) (string, error) { + expirationTime := time.Now().Add(24 * time.Hour) + + claims := &Claims{ + Username: username, + RegisteredClaims: jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(expirationTime), + IssuedAt: jwt.NewNumericDate(time.Now()), + }, + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + return token.SignedString(jwtSecret) +} + +func AuthMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + + var tokenStr string + + // 1️⃣ Check Authorization header (Bearer token) + authHeader := c.GetHeader("Authorization") + if len(authHeader) > 7 && authHeader[:7] == "Bearer " { + tokenStr = authHeader[7:] + } + + // 2️⃣ Fallback to cookie if no Bearer token + if tokenStr == "" { + cookie, err := c.Cookie("auth") + if err == nil { + tokenStr = cookie + } + } + + if tokenStr == "" { + c.Redirect(302, "/login") + return + } + + claims := &Claims{} + + token, err := jwt.ParseWithClaims(tokenStr, claims, func(token *jwt.Token) (interface{}, error) { + + // Validate signing method + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("unexpected signing method") + } + + return jwtSecret, nil + }) + + if err != nil || !token.Valid { + c.AbortWithStatusJSON(401, gin.H{"error": "invalid or expired token"}) + return + } + + // Store user in context + c.Set("username", claims.Username) + + c.Next() + } +} diff --git a/src/handlers.go b/src/handlers.go index 4245545..f72437c 100644 --- a/src/handlers.go +++ b/src/handlers.go @@ -1,6 +1,7 @@ package main import ( + "Backend/src/auth" "fmt" "io" "os" @@ -13,6 +14,11 @@ import ( "golang.org/x/crypto/bcrypt" ) +type LoginRequest struct { + Username string `json:"username" binding:"required"` + Password string `json:"password" binding:"required"` +} + func uploadHandler(c *gin.Context) { err := c.Request.ParseMultipartForm(0) // unlimited if err != nil { @@ -143,7 +149,7 @@ func loginHandler(c *gin.Context) { return } - token, _ := generateToken(username) + token, _ := auth.GenerateToken(username) c.SetCookie( "auth", @@ -158,6 +164,41 @@ func loginHandler(c *gin.Context) { c.Redirect(302, "/admin") } +func apiLoginHandler(c *gin.Context) { + var req LoginRequest + + // Bind JSON body + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(400, gin.H{"error": "Invalid request body"}) + return + } + + var user User + if err := db.Where("username = ?", req.Username).First(&user).Error; err != nil { + c.JSON(401, gin.H{"error": "Invalid credentials"}) + return + } + + if bcrypt.CompareHashAndPassword( + []byte(user.Password), + []byte(req.Password), + ) != nil { + c.JSON(401, gin.H{"error": "Invalid credentials"}) + return + } + + // Generate JWT + token, err := auth.GenerateToken(user.Username) + if err != nil { + c.JSON(500, gin.H{"error": "Could not generate token"}) + return + } + + c.JSON(200, gin.H{ + "token": token, + }) +} + func adminIndexHandler(c *gin.Context) { var files []FileRecord diff --git a/src/main.go b/src/main.go index 9d2e0d0..ac420c5 100644 --- a/src/main.go +++ b/src/main.go @@ -1,6 +1,7 @@ package main import ( + "Backend/src/auth" "fmt" "html/template" "log" @@ -52,9 +53,10 @@ func main() { router.GET("/api/file/delete/:del_id", deleteHandler) router.POST("/api/upload", uploadHandler) + router.POST("/api/login", apiLoginHandler) admin := router.Group("/admin") - admin.Use(authMiddleware()) + admin.Use(auth.AuthMiddleware()) admin.GET("/", adminIndexHandler) admin.GET("/delete/fr/:id", func(c *gin.Context) { diff --git a/src/utils.go b/src/utils.go index cebc322..f61650d 100644 --- a/src/utils.go +++ b/src/utils.go @@ -5,9 +5,6 @@ import ( "os" "path/filepath" "time" - - "github.com/gin-gonic/gin" - "github.com/golang-jwt/jwt/v5" ) func performDeletion(r *FileRecord) { @@ -39,40 +36,6 @@ func cleanupWorker() { } } -var jwtSecret = []byte("SUPER_SECRET_CHANGE_THIS") - -func generateToken(username string) (string, error) { - token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ - "username": username, - }) - - return token.SignedString(jwtSecret) -} - -func authMiddleware() gin.HandlerFunc { - return func(c *gin.Context) { - - tokenStr, err := c.Cookie("auth") - if err != nil { - c.Redirect(302, "/login") - c.Abort() - return - } - - token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) { - return jwtSecret, nil - }) - - if err != nil || !token.Valid { - c.Redirect(302, "/login") - c.Abort() - return - } - - c.Next() - } -} - func humanSize(size int64) string { const unit = 1024 if size < unit {