package infra import ( "crypto/hmac" "crypto/sha256" "encoding/base64" "encoding/json" "errors" "strings" "time" ) // JWTValidate verifica la firma HMAC-SHA256 de un JWT y decodifica sus claims. // Rechaza tokens mal formados, con firma invalida o expirados (exp < time.Now()). // Retorna las claims si todo es valido. func JWTValidate(token string, secret string) (JWTClaims, error) { var zero JWTClaims if secret == "" { return zero, errors.New("jwt_validate: secret vacio") } parts := strings.Split(token, ".") if len(parts) != 3 { return zero, errors.New("jwt_validate: token malformado (se esperaban 3 segmentos)") } enc := base64.RawURLEncoding signingInput := parts[0] + "." + parts[1] // Verificar firma mac := hmac.New(sha256.New, []byte(secret)) mac.Write([]byte(signingInput)) expectedSig := mac.Sum(nil) gotSig, err := enc.DecodeString(parts[2]) if err != nil { return zero, errors.New("jwt_validate: firma mal codificada") } if !hmac.Equal(expectedSig, gotSig) { return zero, errors.New("jwt_validate: firma invalida") } // Decodificar header y confirmar alg headerBytes, err := enc.DecodeString(parts[0]) if err != nil { return zero, errors.New("jwt_validate: header mal codificado") } var header map[string]string if err := json.Unmarshal(headerBytes, &header); err != nil { return zero, errors.New("jwt_validate: header no es JSON valido") } if alg, _ := header["alg"]; alg != "HS256" { return zero, errors.New("jwt_validate: algoritmo no soportado (solo HS256)") } // Decodificar claims payloadBytes, err := enc.DecodeString(parts[1]) if err != nil { return zero, errors.New("jwt_validate: payload mal codificado") } var claims JWTClaims if err := json.Unmarshal(payloadBytes, &claims); err != nil { return zero, errors.New("jwt_validate: payload no es JSON valido") } // Validar expiracion si esta presente if claims.ExpiresAt > 0 && time.Now().Unix() >= claims.ExpiresAt { return zero, errors.New("jwt_validate: token expirado") } return claims, nil }