bootrapping support
Some checks failed
Build and Publish Container Image / build-and-push (push) Has been cancelled

This commit is contained in:
2026-05-24 17:03:01 +02:00
parent 393abf3e0e
commit 7e22fcf3c7
9 changed files with 715 additions and 23 deletions

View File

@@ -43,7 +43,7 @@ func New(cfg Config, httpClient HTTPClient) *AutheliaAdapter {
// values — and requests the full fixed scope set. PKCE is omitted because
// the confidential client_secret authenticates the token exchange instead.
func (a *AutheliaAdapter) AuthorizeURL(_ context.Context, req domain.AuthRequest) (string, error) {
base := strings.TrimRight(a.cfg.BaseURL, "/") + "/api/oidc/authorization"
base := strings.TrimRight(a.authorizeBaseURL(), "/") + "/api/oidc/authorization"
q := url.Values{}
q.Set("client_id", a.cfg.ClientID)
@@ -136,7 +136,7 @@ type tokenResponse struct {
// exchangeCode sends a POST to Authelia's token endpoint and returns the
// parsed token response. On any HTTP or status error it returns a non-nil error.
func (a *AutheliaAdapter) exchangeCode(_ context.Context, code string) (*tokenResponse, error) {
tokenURL := strings.TrimRight(a.cfg.BaseURL, "/") + "/api/oidc/token"
tokenURL := strings.TrimRight(a.tokenBaseURL(), "/") + "/api/oidc/token"
body := url.Values{}
body.Set("grant_type", "authorization_code")
@@ -173,6 +173,20 @@ func (a *AutheliaAdapter) exchangeCode(_ context.Context, code string) (*tokenRe
return &tr, nil
}
func (a *AutheliaAdapter) authorizeBaseURL() string {
if a.cfg.BrowserBaseURL != "" {
return a.cfg.BrowserBaseURL
}
return a.cfg.BaseURL
}
func (a *AutheliaAdapter) tokenBaseURL() string {
if a.cfg.TokenBaseURL != "" {
return a.cfg.TokenBaseURL
}
return a.cfg.BaseURL
}
// parseIDTokenClaims extracts the JWT payload claims without verifying the
// signature. This is intentional — the token is received directly from the
// upstream OIDC provider over a server-to-server TLS connection.

View File

@@ -136,6 +136,33 @@ func TestAuthorizeURL_UsesBaseURL(t *testing.T) {
}
}
func TestAuthorizeURL_UsesBrowserBaseURLWhenConfigured(t *testing.T) {
cfg := testConfig()
cfg.BaseURL = "http://authelia.sso.svc.cluster.local:9091"
cfg.BrowserBaseURL = "https://auth.coulomb.social"
adapter := authelia.New(cfg, &mockHTTPClient{})
req := domain.AuthRequest{
ClientID: "app",
RedirectURI: "https://app.local/cb",
State: "s",
PKCEChallenge: "c",
PKCEChallengeMethod: "S256",
Scopes: []string{"openid"},
}
u, err := adapter.AuthorizeURL(context.Background(), req)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !strings.HasPrefix(u, "https://auth.coulomb.social") {
t.Errorf("expected URL to start with BrowserBaseURL, got: %s", u)
}
if strings.Contains(u, "authelia.sso.svc.cluster.local") {
t.Errorf("browser redirect must not use internal service URL, got: %s", u)
}
}
// ---------------------------------------------------------------------------
// HandleCallback — successful token exchange
// ---------------------------------------------------------------------------
@@ -172,6 +199,32 @@ func TestHandleCallback_Success_PreferredUsername(t *testing.T) {
}
}
func TestHandleCallback_UsesTokenBaseURLWhenConfigured(t *testing.T) {
tokenBody := buildTokenResponse(map[string]interface{}{
"sub": "user-uuid-123",
"preferred_username": "alice",
})
var tokenURL string
client := &mockHTTPClient{
doFn: func(req *http.Request) (*http.Response, error) {
tokenURL = req.URL.String()
return jsonResponse(tokenBody), nil
},
}
cfg := testConfig()
cfg.BaseURL = "https://auth.coulomb.social"
cfg.TokenBaseURL = "http://authelia.sso.svc.cluster.local:9091"
adapter := authelia.New(cfg, client)
if _, err := adapter.HandleCallback(context.Background(), domain.CallbackParams{Code: "code"}); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !strings.HasPrefix(tokenURL, "http://authelia.sso.svc.cluster.local:9091") {
t.Errorf("expected token exchange to use TokenBaseURL, got: %s", tokenURL)
}
}
func TestHandleCallback_Success_FallsBackToSub(t *testing.T) {
tokenBody := buildTokenResponse(map[string]interface{}{
"sub": "user-uuid-456",

View File

@@ -8,17 +8,25 @@ import "net/http"
// Config holds all connection parameters for the Authelia adapter.
type Config struct {
// BaseURL is the Authelia server base URL, e.g. "https://authelia.local".
BaseURL string
BaseURL string `yaml:"baseURL"`
// BrowserBaseURL is the public Authelia URL used for browser redirects.
// If empty, BaseURL is used.
BrowserBaseURL string `yaml:"browserBaseURL,omitempty"`
// TokenBaseURL is the server-side Authelia URL used for token exchange.
// If empty, BaseURL is used.
TokenBaseURL string `yaml:"tokenBaseURL,omitempty"`
// ClientID is the client ID registered in Authelia for KeyCape.
ClientID string
ClientID string `yaml:"clientId"`
// ClientSecret is the client secret for the KeyCape client registration.
ClientSecret string
ClientSecret string `yaml:"clientSecret"`
// RedirectURI is the callback URL registered in Authelia that points back
// to KeyCape's callback handler.
RedirectURI string
RedirectURI string `yaml:"redirectURI"`
}
// HTTPClient is a minimal interface over net/http.Client for test injection.