package server import ( "fmt" "net/http" "time" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" domain "github.com/haydenhargreaves/Potion/internal/domain/server" "github.com/haydenhargreaves/Potion/internal/infrastructure/logging" ) // JwtAuthMiddlewareV2 is responsible for protecting routes. Anything that may go wrong // will be returned via JSON with a 'message' field and a 401 error code. When this // middleware is successful, it will set the 'userId' and 'userEmail' fields and pass // to the next function in the chain. // // Functions that are called after this can assume that those values defined are always // set. func JwtAuthMiddlewareV2(jwtSecretKey []byte) gin.HandlerFunc { return func(ctx *gin.Context) { tokenString, err := ctx.Cookie("jwt_token") if err != nil { ctx.JSON(http.StatusUnauthorized, gin.H{ "status": http.StatusUnauthorized, "message": fmt.Sprintf("[UNAUTHORIZED] Failed to get token from cookie. %s", err.Error()), }) ctx.Abort() return } claims := &domain.JwtClaims{} token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) } return jwtSecretKey, nil }) // Error occurred when parsing if err != nil { ctx.JSON(http.StatusUnauthorized, gin.H{ "status": http.StatusUnauthorized, "message": fmt.Sprintf("[UNAUTHORIZED] Error parsing cooking. %s", err.Error()), }) ctx.Abort() return } // Token is invalid if !token.Valid { ctx.JSON(http.StatusUnauthorized, gin.H{ "status": http.StatusUnauthorized, "message": "[UNAUTHORIZED] Token is invalid.", }) ctx.Abort() return } // Found: Set the values ctx.Set("userId", claims.UserId) ctx.Set("userEmail", claims.Email) ctx.Next() } } // JwtOptionalAuthMiddlewareV2 is responsible for collecting user data for routes where // authentication is optional. Meaning: if the use is not logged in, this function does // not fail or return, it simply does nothing. But if the user is logged in, then the // 'userId' and 'userEmail' context values are set. // // e.g., `userIdAny, exists := ctx.Get("userId")` func JwtOptionalAuthMiddlewareV2(jwtSecretKey []byte) gin.HandlerFunc { return func(ctx *gin.Context) { tokenString, err := ctx.Cookie("jwt_token") if err != nil || tokenString == "" { // No cookie found: not authenticated, but allow access ctx.Next() return } claims := &domain.JwtClaims{} token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) } return jwtSecretKey, nil }) if err == nil && token.Valid { // Set user info in context if token is valid ctx.Set("userId", claims.UserId) ctx.Set("userEmail", claims.Email) } // Otherwise, just continue (user is unauthenticated) ctx.Next() } } func LoggingMiddleware(logs []logging.Logger) gin.HandlerFunc { // TODO: Need traces using IDs? return func(ctx *gin.Context) { start := time.Now() ctx.Next() var ( status int = ctx.Writer.Status() latency string = time.Since(start).String() client string = ctx.ClientIP() method string = ctx.Request.Method path string = ctx.Request.URL.Path ) // TODO: Add color to status format := "%d | %-14s | %15s | %-9s \"%s\"" logging.LogAll( logs, logging.LogLevelInformation, format, status, latency, client, method, path, ) } }