ADD: added new auth middleware and changed the roles value ind the jwt token to a array

This commit is contained in:
hwinkel
2025-11-23 22:55:04 +01:00
parent 139a99d96e
commit 3a6c3a86e3
9 changed files with 84 additions and 30 deletions

View File

@@ -54,9 +54,11 @@ func main() {
api.POST("/tournaments/:id/join", tournament.JoinTournament) api.POST("/tournaments/:id/join", tournament.JoinTournament)
api.PUT("/tournaments/:id", tournament.UpdateTournament) api.PUT("/tournaments/:id", tournament.UpdateTournament)
api.GET("/players", func(c *gin.Context) { // api.GET("/players", func(c *gin.Context) {
player.GetPlayers(c, db.GetDB()) // 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) { api.GET("/players/:id", func(c *gin.Context) {
player.GetPlayer(c, db.GetDB(), c.Param("id")) player.GetPlayer(c, db.GetDB(), c.Param("id"))
}) })

View File

@@ -35,8 +35,8 @@ func LoginHandler(c *gin.Context, db *sql.DB) {
// Systemnutzer // Systemnutzer
var token string var token string
var err error var err error
if req.Email == "test@localhost.de" { if req.Email == "test@localhost.de" { //nolint:goconst
token, err = CreateJWT("system-user-id", req.Email, "admin", 60*time.Minute) token, err = CreateJWT("system-user-id", req.Email, []string{"admin"}, 60*time.Minute)
} else { } else {
hash, err := common.HashPassword(req.Password) hash, err := common.HashPassword(req.Password)
@@ -55,7 +55,7 @@ func LoginHandler(c *gin.Context, db *sql.DB) {
return return
} }
// Create JWT token // 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 { if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Token creation error"}) c.JSON(http.StatusInternalServerError, gin.H{"error": "Token creation error"})
return return

View File

@@ -2,6 +2,7 @@ package auth
import ( import (
"errors" "errors"
"fmt"
"time" "time"
"github.com/golang-jwt/jwt/v4" "github.com/golang-jwt/jwt/v4"
@@ -10,38 +11,39 @@ import (
var jwtSecret = []byte("supersecret") var jwtSecret = []byte("supersecret")
type Claims struct { type Claims struct {
UserID string UserID string `json:"userId"`
Email string Email string `json:"email"`
Role string 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{ claims := jwt.MapClaims{
"userId": userID, "userId": userID,
"email": email, "email": email,
"role": role, "role": role,
"exp": time.Now().Add(duration).Unix(), "exp": time.Now().Add(duration).Unix(),
} }
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(jwtSecret) return token.SignedString(jwtSecret)
} }
func ParseJWT(tokenStr string) (*Claims, error) { func ParseJWT(tokenString string) (*Claims, error) {
token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, 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 return jwtSecret, nil
}) })
// C. Validierung
if err != nil || !token.Valid { if err != nil || !token.Valid {
return nil, errors.New("invalid token") return nil, errors.New("invalid token")
} }
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
return nil, errors.New("invalid claims")
}
return &Claims{ return claims, nil
UserID: claims["userId"].(string),
Email: claims["email"].(string),
Role: claims["role"].(string),
}, nil
} }

View File

@@ -7,6 +7,15 @@ import (
"github.com/gin-gonic/gin" "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 { func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
authHeader := c.GetHeader("Authorization") authHeader := c.GetHeader("Authorization")
@@ -28,3 +37,43 @@ func AuthMiddleware() gin.HandlerFunc {
c.Next() 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 <token>' 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()
}
}

View File

@@ -11,6 +11,7 @@ import (
) )
func GetPlayers(c *gin.Context, db *sql.DB) { func GetPlayers(c *gin.Context, db *sql.DB) {
// log.Println(c.)
log.Println(c.GetString("userId"), c.GetString("email"), c.GetString("role")) log.Println(c.GetString("userId"), c.GetString("email"), c.GetString("role"))
// Simulate fetching players from a database // Simulate fetching players from a database

View File

@@ -4,7 +4,7 @@ import { jwtDecode } from 'jwt-decode';
export interface TokenPayload { export interface TokenPayload {
userId: string; userId: string;
email: string; email: string;
role: string; role: string[];
exp: number; exp: number;
} }

View File

@@ -4,7 +4,7 @@ import { getUserFromToken } from '../components/utils/jwt';
interface AuthContextType { interface AuthContextType {
token: string | null; token: string | null;
userId: string | null; userId: string | null;
login: (token: string, userId: string, role: string) => void; login: (token: string, userId: string, role: string[]) => void;
logout: () => void; logout: () => void;
} }
@@ -13,7 +13,7 @@ const AuthContext = createContext<AuthContextType | undefined>(undefined);
export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [token, setToken] = useState<string | null>(null); const [token, setToken] = useState<string | null>(null);
const [userId, setUserId] = useState<string | null>(null); const [userId, setUserId] = useState<string | null>(null);
const [role, setRole] = useState<string | null>(null); const [role, setRole] = useState<string[] | null>(null);
useEffect(() => { useEffect(() => {
const storedToken = localStorage.getItem('token'); 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); setToken(token);
setUserId(userId); setUserId(userId);
setRole(role); setRole(role);
localStorage.setItem('token', token); localStorage.setItem('token', token);
localStorage.setItem('userId', userId); localStorage.setItem('userId', userId);
localStorage.setItem('role', role); localStorage.setItem('role', JSON.stringify(role)); // Store array as string
}; };
const logout = () => { const logout = () => {

View File

@@ -19,7 +19,7 @@ const TeamManagement = () => {
const token = localStorage.getItem('token'); const token = localStorage.getItem('token');
const user = token ? getUserFromToken(token) : null; const user = token ? getUserFromToken(token) : null;
const isAdmin = user?.role === 'admin'; const isAdmin = user?.role?.includes('admin');
const fetchTeams = async () => { const fetchTeams = async () => {
const res = await fetch('/api/teams', { const res = await fetch('/api/teams', {

View File

@@ -9,7 +9,7 @@ const ViewEditPlayer = () => {
const { id } = useParams<{ id: string }>(); const { id } = useParams<{ id: string }>();
const token = localStorage.getItem('token'); const token = localStorage.getItem('token');
const currentUser = token ? getUserFromToken(token) : null; const currentUser = token ? getUserFromToken(token) : null;
const isAdmin = currentUser?.role === 'admin'; const isAdmin = currentUser?.role?.includes('admin');
const [player, setPlayer] = useState<User | null>(null); const [player, setPlayer] = useState<User | null>(null);
const [name, setName] = useState(''); const [name, setName] = useState('');