//go:build integration package integrationtest import ( "context" "crypto/rand" "crypto/rsa" "encoding/json" "fmt" "net/http" "net/http/httptest" "strings" "time" "github.com/go-jose/go-jose/v4" "github.com/go-jose/go-jose/v4/jwt" "github.com/ultisuite/ulti-backend/internal/auth" ) const testOIDCClientID = "ulti-backend-test" // OIDCServer is a minimal OIDC issuer for integration tests. type OIDCServer struct { URL string Issuer string ClientID string server *httptest.Server key *rsa.PrivateKey jwk jose.JSONWebKey } func NewOIDCServer() (*OIDCServer, error) { key, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { return nil, fmt.Errorf("generate rsa key: %w", err) } jwk := jose.JSONWebKey{Key: key.Public(), KeyID: "test-key", Use: "sig", Algorithm: string(jose.RS256)} s := &OIDCServer{ ClientID: testOIDCClientID, key: key, jwk: jwk, } mux := http.NewServeMux() mux.HandleFunc("/.well-known/openid-configuration", func(w http.ResponseWriter, r *http.Request) { _ = json.NewEncoder(w).Encode(map[string]string{ "issuer": s.Issuer, "jwks_uri": s.Issuer + "/jwks/", }) }) mux.HandleFunc("/jwks/", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(map[string]any{"keys": []jose.JSONWebKey{s.jwk}}) }) s.server = httptest.NewServer(mux) s.URL = strings.TrimSuffix(s.server.URL, "/") s.Issuer = s.URL return s, nil } func (s *OIDCServer) Verifier(ctx context.Context) (*auth.Verifier, error) { return auth.NewVerifier(ctx, s.URL, s.ClientID, "localhost") } func (s *OIDCServer) Holder(ctx context.Context) (*auth.Holder, error) { v, err := s.Verifier(ctx) if err != nil { return nil, err } return auth.NewHolder(v), nil } func (s *OIDCServer) IssueToken(claims *auth.Claims) (string, error) { if claims == nil { return "", fmt.Errorf("claims is nil") } now := time.Now() opts := (&jose.SignerOptions{}).WithHeader(jose.HeaderKey("kid"), s.jwk.KeyID) sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.RS256, Key: s.key}, opts) if err != nil { return "", err } builder := jwt.Signed(sig).Claims(jwt.Claims{ Issuer: s.Issuer, Subject: claims.Sub, Audience: jwt.Audience{s.ClientID}, Expiry: jwt.NewNumericDate(now.Add(time.Hour)), IssuedAt: jwt.NewNumericDate(now), }).Claims(map[string]any{ "email": claims.Email, "preferred_username": claims.PreferredUsername, "upn": claims.UPN, "name": claims.Name, "groups": claims.Groups, }) return builder.Serialize() } func (s *OIDCServer) Close() { if s != nil && s.server != nil { s.server.Close() } }