From 3a6c3a86e3607c5ef7edc1f15e445132769d3a69 Mon Sep 17 00:00:00 2001 From: hwinkel Date: Sun, 23 Nov 2025 22:55:04 +0100 Subject: [PATCH] ADD: added new auth middleware and changed the roles value ind the jwt token to a array --- backend/cmd/server/main.go | 10 +++--- backend/internal/auth/handler.go | 6 ++-- backend/internal/auth/jwt.go | 34 ++++++++++--------- backend/internal/auth/middleware.go | 49 +++++++++++++++++++++++++++ backend/internal/player/handler.go | 1 + frontend/src/components/utils/jwt.tsx | 2 +- frontend/src/pages/AuthContext.tsx | 8 ++--- frontend/src/pages/Teams.tsx | 2 +- frontend/src/pages/ViewEditPlayer.tsx | 2 +- 9 files changed, 84 insertions(+), 30 deletions(-) diff --git a/backend/cmd/server/main.go b/backend/cmd/server/main.go index eee46b5..4575903 100644 --- a/backend/cmd/server/main.go +++ b/backend/cmd/server/main.go @@ -54,9 +54,11 @@ func main() { api.POST("/tournaments/:id/join", tournament.JoinTournament) api.PUT("/tournaments/:id", tournament.UpdateTournament) - api.GET("/players", func(c *gin.Context) { - player.GetPlayers(c, db.GetDB()) - }) + // api.GET("/players", func(c *gin.Context) { + // player.GetPlayers(c, db.GetDB()) + // }) + api.GET("/players", auth.AuthorizeJWT("admin"), func(c *gin.Context) { player.GetPlayers(c, db.GetDB()) }) + api.GET("/players/:id", func(c *gin.Context) { player.GetPlayer(c, db.GetDB(), c.Param("id")) }) @@ -72,7 +74,7 @@ func main() { // c.JSON(http.StatusOK, gin.H{"message": "Player deleted successfully"}) }) api.GET("/teams", func(c *gin.Context) { - + }) port := os.Getenv("PORT") diff --git a/backend/internal/auth/handler.go b/backend/internal/auth/handler.go index 541b308..2477d80 100644 --- a/backend/internal/auth/handler.go +++ b/backend/internal/auth/handler.go @@ -35,8 +35,8 @@ func LoginHandler(c *gin.Context, db *sql.DB) { // Systemnutzer var token string var err error - if req.Email == "test@localhost.de" { - token, err = CreateJWT("system-user-id", req.Email, "admin", 60*time.Minute) + if req.Email == "test@localhost.de" { //nolint:goconst + token, err = CreateJWT("system-user-id", req.Email, []string{"admin"}, 60*time.Minute) } else { hash, err := common.HashPassword(req.Password) @@ -55,7 +55,7 @@ func LoginHandler(c *gin.Context, db *sql.DB) { return } // Create JWT token - token, err = CreateJWT(loggedInPlayer.UUID, req.Email, "player", 60*time.Minute) + token, err = CreateJWT(loggedInPlayer.UUID, req.Email, []string{"player"}, 60*time.Minute) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Token creation error"}) return diff --git a/backend/internal/auth/jwt.go b/backend/internal/auth/jwt.go index 62f4e41..d5c6afa 100644 --- a/backend/internal/auth/jwt.go +++ b/backend/internal/auth/jwt.go @@ -2,6 +2,7 @@ package auth import ( "errors" + "fmt" "time" "github.com/golang-jwt/jwt/v4" @@ -10,38 +11,39 @@ import ( var jwtSecret = []byte("supersecret") type Claims struct { - UserID string - Email string - Role string + UserID string `json:"userId"` + Email string `json:"email"` + Role []string `json:"role"` + jwt.RegisteredClaims } -func CreateJWT(userID, email, role string, duration time.Duration) (string, error) { +func CreateJWT(userID string, email string, role []string, duration time.Duration) (string, error) { claims := jwt.MapClaims{ "userId": userID, "email": email, "role": role, "exp": time.Now().Add(duration).Unix(), } - token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString(jwtSecret) } -func ParseJWT(tokenStr string) (*Claims, error) { - token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) { +func ParseJWT(tokenString string) (*Claims, error) { + + claims := &Claims{} + token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) { + // Algorithmus Check (Sicherheit) + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("unerwartete Signing-Methode: %v", token.Header["alg"]) + } return jwtSecret, nil }) + + // C. Validierung if err != nil || !token.Valid { + return nil, errors.New("invalid token") } - claims, ok := token.Claims.(jwt.MapClaims) - if !ok { - return nil, errors.New("invalid claims") - } - return &Claims{ - UserID: claims["userId"].(string), - Email: claims["email"].(string), - Role: claims["role"].(string), - }, nil + return claims, nil } diff --git a/backend/internal/auth/middleware.go b/backend/internal/auth/middleware.go index e026db0..2418da9 100644 --- a/backend/internal/auth/middleware.go +++ b/backend/internal/auth/middleware.go @@ -7,6 +7,15 @@ import ( "github.com/gin-gonic/gin" ) +func contains(slice []string, item string) bool { + for _, s := range slice { + if s == item { + return true + } + } + return false +} + func AuthMiddleware() gin.HandlerFunc { return func(c *gin.Context) { authHeader := c.GetHeader("Authorization") @@ -28,3 +37,43 @@ func AuthMiddleware() gin.HandlerFunc { c.Next() } } + +func AuthorizeJWT(requiredRole string) gin.HandlerFunc { + return func(c *gin.Context) { + //A. Token aus Header holen + authHeader := c.GetHeader("Authorization") + if authHeader == "" { + c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Authorization Header fehlt"}) + return + } + + // "Bearer " entfernen + tokenString := strings.TrimPrefix(authHeader, "Bearer ") + if tokenString == authHeader { + c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Token Format muss 'Bearer ' sein"}) + return + } + + claims, err := ParseJWT(tokenString) + if err != nil { + c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Token ungültig oder abgelaufen"}) + } + // --- NEUE LOGIK --- + // Wir prüfen: Hat der User die geforderte Rolle ODER ist er "admin"? + // (Angenommen "admin" darf alles. Falls nicht, entferne den "admin"-Check) + userHasRequiredRole := contains(claims.Role, requiredRole) + userIsAdmin := contains(claims.Role, "admin") + + if !userHasRequiredRole && !userIsAdmin { + c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "Keine Berechtigung"}) + return + } + + // User und Rollen im Context speichern (als Interface{}, daher später casten) + c.Set("userId", claims.UserID) + c.Set("email", claims.Email) + c.Set("role", claims.Role) + + c.Next() + } +} diff --git a/backend/internal/player/handler.go b/backend/internal/player/handler.go index ad48393..fbc8b57 100644 --- a/backend/internal/player/handler.go +++ b/backend/internal/player/handler.go @@ -11,6 +11,7 @@ import ( ) func GetPlayers(c *gin.Context, db *sql.DB) { + // log.Println(c.) log.Println(c.GetString("userId"), c.GetString("email"), c.GetString("role")) // Simulate fetching players from a database diff --git a/frontend/src/components/utils/jwt.tsx b/frontend/src/components/utils/jwt.tsx index eb6a848..d8cccbf 100644 --- a/frontend/src/components/utils/jwt.tsx +++ b/frontend/src/components/utils/jwt.tsx @@ -4,7 +4,7 @@ import { jwtDecode } from 'jwt-decode'; export interface TokenPayload { userId: string; email: string; - role: string; + role: string[]; exp: number; } diff --git a/frontend/src/pages/AuthContext.tsx b/frontend/src/pages/AuthContext.tsx index a23f6a6..09b8a07 100644 --- a/frontend/src/pages/AuthContext.tsx +++ b/frontend/src/pages/AuthContext.tsx @@ -4,7 +4,7 @@ import { getUserFromToken } from '../components/utils/jwt'; interface AuthContextType { token: string | null; userId: string | null; - login: (token: string, userId: string, role: string) => void; + login: (token: string, userId: string, role: string[]) => void; logout: () => void; } @@ -13,7 +13,7 @@ const AuthContext = createContext(undefined); export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { const [token, setToken] = useState(null); const [userId, setUserId] = useState(null); - const [role, setRole] = useState(null); + const [role, setRole] = useState(null); useEffect(() => { const storedToken = localStorage.getItem('token'); @@ -40,13 +40,13 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }, []); - const login = (token: string, userId: string, role: string) => { + const login = (token: string, userId: string, role: string[]) => { setToken(token); setUserId(userId); setRole(role); localStorage.setItem('token', token); localStorage.setItem('userId', userId); - localStorage.setItem('role', role); + localStorage.setItem('role', JSON.stringify(role)); // Store array as string }; const logout = () => { diff --git a/frontend/src/pages/Teams.tsx b/frontend/src/pages/Teams.tsx index 8df2cd8..81792a4 100644 --- a/frontend/src/pages/Teams.tsx +++ b/frontend/src/pages/Teams.tsx @@ -19,7 +19,7 @@ const TeamManagement = () => { const token = localStorage.getItem('token'); const user = token ? getUserFromToken(token) : null; - const isAdmin = user?.role === 'admin'; + const isAdmin = user?.role?.includes('admin'); const fetchTeams = async () => { const res = await fetch('/api/teams', { diff --git a/frontend/src/pages/ViewEditPlayer.tsx b/frontend/src/pages/ViewEditPlayer.tsx index 6e5619e..908026f 100644 --- a/frontend/src/pages/ViewEditPlayer.tsx +++ b/frontend/src/pages/ViewEditPlayer.tsx @@ -9,7 +9,7 @@ const ViewEditPlayer = () => { const { id } = useParams<{ id: string }>(); const token = localStorage.getItem('token'); const currentUser = token ? getUserFromToken(token) : null; - const isAdmin = currentUser?.role === 'admin'; + const isAdmin = currentUser?.role?.includes('admin'); const [player, setPlayer] = useState(null); const [name, setName] = useState('');