- Added configuration options for Stalwart hosted mail in .env.example. - Updated Docker Compose to include Stalwart service with health checks. - Introduced new API endpoints for managing mail domains and migration projects. - Enhanced Authentik blueprints for user enrollment and post-migration security. - Updated OAuth handling for Google and Microsoft migration processes. - Improved error handling and response structures in the mail API. - Added integration tests for email claiming and migration workflows.
110 lines
2.7 KiB
Go
110 lines
2.7 KiB
Go
//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()
|
|
}
|
|
}
|