LINUS ROUND 5: RS256 default auth, middleware, full integration
- main.go: RS256Service with AAMOS public key, fallback to HS256 - auth/rs256.go: Middleware() for RS256 Bearer validation - auth/rs256_test.go: 6 RS256 tests (middleware + validation) - 27/27 auth tests passing, 82.6% coverage - Build passes, all services operational
This commit is contained in:
@@ -5,7 +5,9 @@ import (
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
@@ -51,6 +53,34 @@ func NewRS256Service(publicKeyPath string) (*RS256Service, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Middleware returns HTTP middleware that validates Bearer tokens using RS256
|
||||
func (s *RS256Service) Middleware() func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
authHeader := r.Header.Get("Authorization")
|
||||
if authHeader == "" {
|
||||
http.Error(w, `{"error":"missing authorization header"}`, http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(authHeader, "Bearer ") {
|
||||
http.Error(w, `{"error":"invalid authorization format"}`, http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
tokenString := strings.TrimPrefix(authHeader, "Bearer ")
|
||||
claims, err := s.ValidateToken(tokenString)
|
||||
if err != nil {
|
||||
http.Error(w, `{"error":"invalid token"}`, http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := WithClaims(r.Context(), claims)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateToken verifies an RS256 JWT token
|
||||
func (s *RS256Service) ValidateToken(tokenString string) (*Claims, error) {
|
||||
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -152,6 +154,77 @@ func TestRS256Service_ValidateToken_Expired(t *testing.T) {
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestRS256Service_Middleware_ValidToken(t *testing.T) {
|
||||
privateKey, pubPEM := generateTestKeyPair(t)
|
||||
|
||||
tmpFile, err := os.CreateTemp("", "test-pub-*.pem")
|
||||
require.NoError(t, err)
|
||||
defer os.Remove(tmpFile.Name())
|
||||
|
||||
_, err = tmpFile.Write(pubPEM)
|
||||
require.NoError(t, err)
|
||||
tmpFile.Close()
|
||||
|
||||
svc, err := NewRS256Service(tmpFile.Name())
|
||||
require.NoError(t, err)
|
||||
|
||||
// Issue a valid token
|
||||
now := time.Now().Unix()
|
||||
claims := jwt.MapClaims{
|
||||
"sub": "user-123",
|
||||
"email": "test@example.com",
|
||||
"iss": "prexo-identity",
|
||||
"aud": "prexo",
|
||||
"iat": now,
|
||||
"exp": now + 3600,
|
||||
}
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
|
||||
tokenString, err := token.SignedString(privateKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Test middleware
|
||||
handler := svc.Middleware()(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
validatedClaims, ok := FromContext(r.Context())
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, "user-123", validatedClaims.Sub)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/test", nil)
|
||||
req.Header.Set("Authorization", "Bearer "+tokenString)
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(rr, req)
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
}
|
||||
|
||||
func TestRS256Service_Middleware_InvalidToken(t *testing.T) {
|
||||
_, pubPEM := generateTestKeyPair(t)
|
||||
|
||||
tmpFile, err := os.CreateTemp("", "test-pub-*.pem")
|
||||
require.NoError(t, err)
|
||||
defer os.Remove(tmpFile.Name())
|
||||
|
||||
_, err = tmpFile.Write(pubPEM)
|
||||
require.NoError(t, err)
|
||||
tmpFile.Close()
|
||||
|
||||
svc, err := NewRS256Service(tmpFile.Name())
|
||||
require.NoError(t, err)
|
||||
|
||||
handler := svc.Middleware()(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Fatal("should not reach handler")
|
||||
}))
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/test", nil)
|
||||
req.Header.Set("Authorization", "Bearer invalid-token")
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(rr, req)
|
||||
assert.Equal(t, http.StatusUnauthorized, rr.Code)
|
||||
}
|
||||
|
||||
func TestRS256Service_ValidateToken_HS256(t *testing.T) {
|
||||
_, pubPEM := generateTestKeyPair(t)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user