refactored auth.go & tests
This commit is contained in:
@@ -2,9 +2,7 @@ package middlewares
|
||||
|
||||
import (
|
||||
"GoMembership/internal/config"
|
||||
"GoMembership/internal/models"
|
||||
"GoMembership/internal/utils"
|
||||
customerrors "GoMembership/pkg/errors"
|
||||
"GoMembership/pkg/logger"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -34,26 +32,43 @@ func verifyAndRenewToken(tokenString string) (string, uint, error) {
|
||||
return "", 0, fmt.Errorf("Authorization token is required")
|
||||
}
|
||||
token, claims, err := ExtractContentFrom(tokenString)
|
||||
if err != nil {
|
||||
|
||||
if err != nil && !errors.Is(err, jwt.ErrTokenExpired) {
|
||||
logger.Error.Printf("Couldn't parse JWT token String: %v", err)
|
||||
return "", 0, err
|
||||
}
|
||||
sessionID := (*claims)["session_id"].(string)
|
||||
userID := uint((*claims)["user_id"].(float64))
|
||||
roleID := int8((*claims)["role_id"].(float64))
|
||||
|
||||
if token.Valid {
|
||||
// token is valid, so we can return the old tokenString
|
||||
return tokenString, uint((*claims)["user_id"].(float64)), nil
|
||||
}
|
||||
|
||||
// Token is expired but valid
|
||||
sessionID, ok := (*claims)["session_id"].(string)
|
||||
if !ok || sessionID == "" {
|
||||
return "", 0, fmt.Errorf("invalid session ID")
|
||||
}
|
||||
id, ok := (*claims)["user_id"]
|
||||
if !ok {
|
||||
return "", 0, fmt.Errorf("missing user_id claim")
|
||||
}
|
||||
userID := uint(id.(float64))
|
||||
|
||||
id, ok = (*claims)["role_id"]
|
||||
if !ok {
|
||||
return "", 0, fmt.Errorf("missing role_id claim")
|
||||
}
|
||||
roleID := int8(id.(float64))
|
||||
|
||||
session, ok := sessions[sessionID]
|
||||
if !ok {
|
||||
logger.Error.Printf("session not found")
|
||||
return "", 0, fmt.Errorf("session not found")
|
||||
}
|
||||
|
||||
if userID != session.UserID {
|
||||
return "", 0, fmt.Errorf("Cookie has been altered, aborting..")
|
||||
}
|
||||
if token.Valid {
|
||||
// token is valid, so we can return the old tokenString
|
||||
return tokenString, session.UserID, customerrors.ErrValidToken
|
||||
}
|
||||
|
||||
if time.Now().After(sessions[sessionID].ExpiresAt) {
|
||||
delete(sessions, sessionID)
|
||||
@@ -64,8 +79,8 @@ func verifyAndRenewToken(tokenString string) (string, uint, error) {
|
||||
|
||||
logger.Error.Printf("Session still valid generating new token")
|
||||
// Session is still valid, generate a new token
|
||||
user := models.User{ID: userID, RoleID: roleID}
|
||||
newTokenString, err := GenerateToken(config.Auth.JWTSecret, &user, sessionID)
|
||||
user := map[string]interface{}{"user_id": userID, "role_id": roleID}
|
||||
newTokenString, err := GenerateToken(&config.Auth.JWTSecret, user, sessionID)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
@@ -89,11 +104,6 @@ func AuthMiddleware() gin.HandlerFunc {
|
||||
|
||||
newToken, userID, err := verifyAndRenewToken(tokenString)
|
||||
if err != nil {
|
||||
if err == customerrors.ErrValidToken {
|
||||
c.Set("user_id", uint(userID))
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
logger.Error.Printf("Token(%v) is invalid: %v\n", tokenString, err)
|
||||
c.JSON(http.StatusUnauthorized,
|
||||
gin.H{"errors": []gin.H{{
|
||||
@@ -104,24 +114,30 @@ func AuthMiddleware() gin.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
if newToken != tokenString {
|
||||
utils.SetCookie(c, newToken)
|
||||
}
|
||||
c.Set("user_id", uint(userID))
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func GenerateToken(jwtKey string, user *models.User, sessionID string) (string, error) {
|
||||
// GenerateToken generates a new JWT token with the given claims and session ID.
|
||||
// "user_id": user.ID, "role_id": user.RoleID
|
||||
func GenerateToken(jwtKey *string, claims map[string]interface{}, sessionID string) (string, error) {
|
||||
if sessionID == "" {
|
||||
sessionID = uuid.New().String()
|
||||
}
|
||||
token := jwt.NewWithClaims(jwtSigningMethod, jwt.MapClaims{
|
||||
"user_id": user.ID,
|
||||
"role_id": user.RoleID,
|
||||
"session_id": sessionID,
|
||||
"exp": time.Now().Add(time.Minute * 1).Unix(), // Token expires in 10 Minutes
|
||||
})
|
||||
UpdateSession(sessionID, user.ID)
|
||||
return token.SignedString([]byte(jwtKey))
|
||||
claims["session_id"] = sessionID
|
||||
claims["exp"] = time.Now().Add(time.Minute * 1).Unix() // Token expires in 10 Minutes
|
||||
token := jwt.NewWithClaims(jwtSigningMethod, jwt.MapClaims(claims))
|
||||
|
||||
userID, ok := claims["user_id"].(uint)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("invalid user_id in claims")
|
||||
}
|
||||
UpdateSession(sessionID, userID)
|
||||
return token.SignedString([]byte(*jwtKey))
|
||||
}
|
||||
|
||||
func ExtractContentFrom(tokenString string) (*jwt.Token, *jwt.MapClaims, error) {
|
||||
@@ -130,23 +146,33 @@ func ExtractContentFrom(tokenString string) (*jwt.Token, *jwt.MapClaims, error)
|
||||
return []byte(config.Auth.JWTSecret), nil
|
||||
})
|
||||
|
||||
if !errors.Is(err, jwt.ErrTokenExpired) && err != nil {
|
||||
logger.Error.Printf("Error during token(%v) parsing: %#v", tokenString, err)
|
||||
// Handle parsing errors (excluding expiration error)
|
||||
if err != nil && !errors.Is(err, jwt.ErrTokenExpired) {
|
||||
logger.Error.Printf("Error parsing token: %v", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Token is expired, check if session is still valid
|
||||
claims, ok := token.Claims.(jwt.MapClaims)
|
||||
if !ok {
|
||||
logger.Error.Printf("Invalid Token Claims")
|
||||
return nil, nil, fmt.Errorf("invalid token claims")
|
||||
// Ensure token is not nil (e.g., malformed tokens)
|
||||
if token == nil {
|
||||
logger.Error.Print("Token is nil after parsing")
|
||||
return nil, nil, fmt.Errorf("invalid token")
|
||||
}
|
||||
|
||||
// Extract and validate claims
|
||||
claims, ok := token.Claims.(jwt.MapClaims)
|
||||
if !ok {
|
||||
logger.Error.Printf("invalid session_id in token")
|
||||
return nil, nil, fmt.Errorf("invalid session_id in token")
|
||||
logger.Error.Print("Invalid token claims structure")
|
||||
return nil, nil, fmt.Errorf("invalid token claims format")
|
||||
}
|
||||
return token, &claims, nil
|
||||
|
||||
// Validate required session_id claim
|
||||
if _, exists := claims["session_id"]; !exists {
|
||||
logger.Error.Print("Missing session_id in token claims")
|
||||
return nil, nil, fmt.Errorf("missing session_id claim")
|
||||
}
|
||||
|
||||
// Return token, claims, and original error (might be expiration)
|
||||
return token, &claims, err
|
||||
}
|
||||
|
||||
func UpdateSession(sessionID string, userID uint) {
|
||||
|
||||
@@ -3,7 +3,6 @@ package middlewares
|
||||
import (
|
||||
"GoMembership/internal/config"
|
||||
"GoMembership/internal/constants"
|
||||
"GoMembership/internal/models"
|
||||
"GoMembership/pkg/logger"
|
||||
"encoding/json"
|
||||
"log"
|
||||
@@ -56,8 +55,11 @@ func TestAuthMiddleware(t *testing.T) {
|
||||
{
|
||||
name: "Valid Token",
|
||||
setupAuth: func(r *http.Request) {
|
||||
user := models.User{ID: 123, RoleID: constants.Roles.Member}
|
||||
token, _ := GenerateToken(config.Auth.JWTSecret, &user, "")
|
||||
claims := map[string]interface{}{"user_id": uint(123), "role_id": constants.Roles.Member}
|
||||
token, err := GenerateToken(&config.Auth.JWTSecret, claims, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
r.AddCookie(&http.Cookie{Name: "jwt", Value: token})
|
||||
},
|
||||
expectedStatus: http.StatusOK,
|
||||
@@ -82,7 +84,7 @@ func TestAuthMiddleware(t *testing.T) {
|
||||
setupAuth: func(r *http.Request) {
|
||||
sessionID := "test-session"
|
||||
token := jwt.NewWithClaims(jwtSigningMethod, jwt.MapClaims{
|
||||
"user_id": 123,
|
||||
"user_id": uint(123),
|
||||
"role_id": constants.Roles.Member,
|
||||
"session_id": sessionID,
|
||||
"exp": time.Now().Add(-time.Hour).Unix(), // Expired 1 hour ago
|
||||
@@ -100,7 +102,7 @@ func TestAuthMiddleware(t *testing.T) {
|
||||
setupAuth: func(r *http.Request) {
|
||||
sessionID := "expired-session"
|
||||
token := jwt.NewWithClaims(jwtSigningMethod, jwt.MapClaims{
|
||||
"user_id": 123,
|
||||
"user_id": uint(123),
|
||||
"role_id": constants.Roles.Member,
|
||||
"session_id": sessionID,
|
||||
"exp": time.Now().Add(-time.Hour).Unix(), // Expired 1 hour ago
|
||||
@@ -116,7 +118,7 @@ func TestAuthMiddleware(t *testing.T) {
|
||||
name: "Invalid Signature",
|
||||
setupAuth: func(r *http.Request) {
|
||||
token := jwt.NewWithClaims(jwtSigningMethod, jwt.MapClaims{
|
||||
"user_id": 123,
|
||||
"user_id": uint(123),
|
||||
"session_id": "some-session",
|
||||
"exp": time.Now().Add(time.Hour).Unix(),
|
||||
})
|
||||
@@ -130,7 +132,7 @@ func TestAuthMiddleware(t *testing.T) {
|
||||
name: "Invalid Signing Method",
|
||||
setupAuth: func(r *http.Request) {
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodES256, jwt.MapClaims{
|
||||
"user_id": 123,
|
||||
"user_id": uint(123),
|
||||
"session_id": "some-session",
|
||||
"role_id": constants.Roles.Member,
|
||||
"exp": time.Now().Add(time.Hour).Unix(),
|
||||
|
||||
Reference in New Issue
Block a user