BOC v1.0: RS256 auth, Ledger integration, Prometheus metrics, CI/CD, backup
This commit is contained in:
@@ -7,3 +7,4 @@ RprzZGLlOpwCslfvNFrz6vB9HnUxYHIPexB54YwTtUZjpoz+Um/A5y6nAn94P/E5
|
||||
RqTqVp80vHPpTXL/KSOwU6E8NQYHWPhp1eziiq0hfTOZeDzZIeDKn+tHNwBiU71q
|
||||
KQIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
|
||||
|
||||
+71
-2
@@ -3,13 +3,18 @@ package auth
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// RS256Service validates RS256 JWT tokens using a public key
|
||||
@@ -20,6 +25,66 @@ type RS256Service struct {
|
||||
audience string
|
||||
}
|
||||
|
||||
// JWKS represents a JSON Web Key Set
|
||||
type JWKS struct {
|
||||
Keys []JWK `json:"keys"`
|
||||
}
|
||||
|
||||
// JWK represents a JSON Web Key
|
||||
type JWK struct {
|
||||
Kty string `json:"kty"`
|
||||
N string `json:"n"`
|
||||
E string `json:"e"`
|
||||
Use string `json:"use"`
|
||||
Alg string `json:"alg"`
|
||||
Kid string `json:"kid"`
|
||||
}
|
||||
|
||||
// NewRS256ServiceFromURL fetches JWKS from URL and creates RS256Service
|
||||
func NewRS256ServiceFromURL(jwksURL string) (*RS256Service, error) {
|
||||
client := &http.Client{Timeout: 10 * time.Second}
|
||||
resp, err := client.Get(jwksURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch JWKS: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("JWKS endpoint returned %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var jwks JWKS
|
||||
if err := json.NewDecoder(resp.Body).Decode(&jwks); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode JWKS: %w", err)
|
||||
}
|
||||
|
||||
if len(jwks.Keys) == 0 {
|
||||
return nil, fmt.Errorf("no keys in JWKS")
|
||||
}
|
||||
|
||||
// Use first signing key
|
||||
key := jwks.Keys[0]
|
||||
nBytes, err := base64.RawURLEncoding.DecodeString(key.N)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode N: %w", err)
|
||||
}
|
||||
eBytes, err := base64.RawURLEncoding.DecodeString(key.E)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode E: %w", err)
|
||||
}
|
||||
|
||||
pub := &rsa.PublicKey{
|
||||
N: new(big.Int).SetBytes(nBytes),
|
||||
E: int(new(big.Int).SetBytes(eBytes).Int64()),
|
||||
}
|
||||
|
||||
return &RS256Service{
|
||||
publicKey: pub,
|
||||
issuer: "prexo-identity",
|
||||
audience: "prexo",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewRS256Service loads the public key from a PEM file
|
||||
func NewRS256Service(publicKeyPath string) (*RS256Service, error) {
|
||||
pemData, err := os.ReadFile(publicKeyPath)
|
||||
@@ -71,6 +136,7 @@ func (s *RS256Service) Middleware() func(http.Handler) http.Handler {
|
||||
tokenString := strings.TrimPrefix(authHeader, "Bearer ")
|
||||
claims, err := s.ValidateToken(tokenString)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("token validation failed")
|
||||
http.Error(w, `{"error":"invalid token"}`, http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
@@ -89,8 +155,11 @@ func (s *RS256Service) ValidateToken(tokenString string) (*Claims, error) {
|
||||
}
|
||||
return s.publicKey, nil
|
||||
})
|
||||
if err != nil || !token.Valid {
|
||||
return nil, fmt.Errorf("invalid token: %w", err)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("token parse error: %w", err)
|
||||
}
|
||||
if !token.Valid {
|
||||
return nil, fmt.Errorf("token invalid")
|
||||
}
|
||||
|
||||
mapClaims, ok := token.Claims.(jwt.MapClaims)
|
||||
|
||||
Reference in New Issue
Block a user