Started Authentication System

This commit is contained in:
2026-02-22 14:47:17 +01:00
parent c492228e22
commit b5840984b8
7 changed files with 126 additions and 38 deletions

45
auth/jwt.go Normal file
View File

@@ -0,0 +1,45 @@
package auth
import (
"os"
"time"
"github.com/golang-jwt/jwt/v5"
)
type Claims struct {
UserID string `json:"user_id"`
Role string `json:"role"`
jwt.RegisteredClaims
}
var secret = os.Getenv("SHAP_JWT_SECRET")
func GenerateJWT(userID, role string, secret []byte) (string, error) {
claims := Claims{
UserID: userID,
Role: role,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(15 * time.Minute)),
IssuedAt: jwt.NewNumericDate(time.Now()),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(secret)
}
func ValidateJWT(tokenStr string, secret []byte) (*Claims, error) {
token, err := jwt.ParseWithClaims(tokenStr, &Claims{}, func(token *jwt.Token) (interface{}, error) {
return secret, nil
})
if err != nil {
return nil, err
}
claims, ok := token.Claims.(*Claims)
if !ok || !token.Valid {
return nil, err
}
return claims, nil
}

48
auth/middleware.go Normal file
View File

@@ -0,0 +1,48 @@
package auth
import (
"context"
"net/http"
"strings"
)
type contextKey string
const UserContextKey contextKey = contextKey("user")
func AuthMiddleware(secret []byte) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
http.Error(w, "Missing token", http.StatusUnauthorized)
return
}
tokenStr := strings.TrimPrefix(authHeader, "Bearer ")
claims, err := ValidateJWT(tokenStr, secret)
if err != nil {
http.Error(w, "Invalid token", http.StatusUnauthorized)
return
}
ctx := context.WithValue(r.Context(), UserContextKey, claims)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
func RequireRole(role string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
claims := r.Context().Value(UserContextKey).(*Claims)
if claims.Role != role {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
next.ServeHTTP(w, r)
})
}
}

12
auth/password.go Normal file
View File

@@ -0,0 +1,12 @@
package auth
import "golang.org/x/crypto/bcrypt"
func HashPassword(password string) (string, error) {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14)
return string(bytes), err
}
func CheckPasswordHash(password, hash string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}

View File

@@ -3,6 +3,7 @@ package handlers
import (
"encoding/json"
"net/http"
"shap-planner-backend/auth"
"shap-planner-backend/models"
"shap-planner-backend/storage"
"shap-planner-backend/utils"
@@ -11,7 +12,7 @@ import (
func Register(w http.ResponseWriter, r *http.Request) {
var user models.User
_ = json.NewDecoder(r.Body).Decode(&user)
hashed, _ := utils.HashPassword(user.Password)
hashed, _ := auth.HashPassword(user.Password)
user.Password = hashed
user.ID = utils.GenerateUUID()
@@ -36,7 +37,7 @@ func Login(w http.ResponseWriter, r *http.Request) {
return
}
if !utils.CheckPasswordHash(creds.Password, user.Password) {
if !auth.CheckPasswordHash(creds.Password, user.Password) {
http.Error(w, "Wrong password", http.StatusUnauthorized)
return
}

7
handlers/expenses.go Normal file
View File

@@ -0,0 +1,7 @@
package handlers
import "net/http"
func GetExpenses(w http.ResponseWriter, r *http.Request) {}
func AdminPanel(w http.ResponseWriter, r *http.Request) {}

View File

@@ -4,6 +4,7 @@ import (
"log"
"net/http"
"os"
"shap-planner-backend/auth"
"shap-planner-backend/config"
"shap-planner-backend/handlers"
)
@@ -42,9 +43,16 @@ func InitServer() *Server {
}
func (server *Server) Run() {
http.HandleFunc("/register", handlers.Register)
http.HandleFunc("/login", handlers.Login)
mux := http.NewServeMux()
mux.HandleFunc("/login", handlers.Login)
protected := auth.AuthMiddleware(server.JWTSecret)(http.HandlerFunc(handlers.GetExpenses))
mux.Handle("/expenses", protected)
adminOnly := auth.AuthMiddleware(server.JWTSecret)(auth.RequireRole("admin")(http.HandlerFunc(handlers.AdminPanel)))
mux.Handle("/admin", adminOnly)
log.Printf("Listening on port %s", server.Port)
log.Fatal(http.ListenAndServe(":"+server.Port, nil))
log.Fatal(http.ListenAndServe(":"+server.Port, mux))
}

View File

@@ -1,45 +1,12 @@
package utils
import (
"os"
"time"
"crypto/rand"
"encoding/base64"
"github.com/golang-jwt/jwt/v5"
"github.com/google/uuid"
"golang.org/x/crypto/bcrypt"
)
var secret = []byte(os.Getenv("SHAP_JWT_SECRET"))
// Password Validation
func HashPassword(password string) (string, error) {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14)
return string(bytes), err
}
func CheckPasswordHash(password, hash string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}
// JWT Validation
func GenerateJWT(userID string) (string, error) {
claims := jwt.MapClaims{
"userId": userID,
"exp": time.Now().Add(time.Hour * 1).Unix(),
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(secret)
}
func ValidateJWT(tokenString string) (*jwt.Token, error) {
return jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return secret, nil
})
}
func GenerateUUID() string {
return uuid.New().String()
}