1
0
forked from baron/baron-sso

fix(headless-login): honor public base url for audience checks

- resolve headless audience against BACKEND_PUBLIC_URL first
- keep forwarded header support for https absolute audiences
- add regression tests for https success and http mismatch rejection
- write BACKEND_PUBLIC_URL into staging workflow env generation
This commit is contained in:
Lectom C Han
2026-04-01 21:05:41 +09:00
parent 3186fab596
commit 71a006cd7b
8 changed files with 372 additions and 19 deletions

View File

@@ -248,6 +248,28 @@ func runHeadlessPasswordLoginE2E(
jwks map[string]any,
clientAssertion string,
) (*http.Response, string) {
return runHeadlessPasswordLoginE2ERequest(
t,
logger,
appEnv,
jwks,
clientAssertion,
"http://example.com/api/v1/auth/headless/password/login",
nil,
)
}
func runHeadlessPasswordLoginE2ERequest(
t *testing.T,
logger *slog.Logger,
appEnv string,
jwks map[string]any,
clientAssertion string,
requestURL string,
headers map[string]string,
) (*http.Response, string) {
t.Helper()
t.Helper()
logBuffer := &bytes.Buffer{}
@@ -324,8 +346,11 @@ func runHeadlessPasswordLoginE2E(
"password": "password",
"login_challenge": "challenge-123",
})
req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/headless/password/login", bytes.NewReader(body))
req := httptest.NewRequest(http.MethodPost, requestURL, bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
for key, value := range headers {
req.Header.Set(key, value)
}
resp, err := app.Test(req)
if err != nil {
@@ -417,3 +442,91 @@ func TestHeadlessPasswordLogin_E2E_DebugLogsIncludeDiagnostics(t *testing.T) {
t.Fatalf("expected debug logs to include login challenge prefix, got=%s", output)
}
}
func TestHeadlessPasswordLogin_E2E_AcceptsForwardedHTTPSAudience(t *testing.T) {
privateKey, jwks := mustE2EHeadlessRSAJWK(t)
const receivedAudience = "https://sso.hmac.kr/api/v1/auth/headless/password/login"
clientAssertion := mustE2EHeadlessClientAssertion(
t,
privateKey,
"headless-login-client",
receivedAudience,
)
logBuffer := &bytes.Buffer{}
logger := slog.New(slog.NewJSONHandler(logBuffer, &slog.HandlerOptions{Level: slog.LevelDebug}))
resp, output := runHeadlessPasswordLoginE2ERequest(
t,
logger,
"production",
jwks,
clientAssertion,
"http://sso.hmac.kr/api/v1/auth/headless/password/login",
map[string]string{
"X-Forwarded-Proto": "https",
"X-Forwarded-Host": "sso.hmac.kr",
},
)
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
bodyBytes, _ := io.ReadAll(resp.Body)
t.Fatalf("expected 200 for forwarded https audience, got %d, body=%s", resp.StatusCode, string(bodyBytes))
}
var got map[string]any
if err := json.NewDecoder(resp.Body).Decode(&got); err != nil {
t.Fatalf("failed to decode response body: %v", err)
}
if got["redirectTo"] != "http://rp/cb" {
t.Fatalf("expected redirectTo, got=%v", got["redirectTo"])
}
if strings.Contains(output, "\"reason_code\":\"invalid_client_assertion_audience\"") {
t.Fatalf("did not expect audience mismatch log, got=%s", output)
}
}
func TestHeadlessPasswordLogin_E2E_AcceptsConfiguredPublicHTTPSAudience(t *testing.T) {
t.Setenv("BACKEND_PUBLIC_URL", "https://sso.hmac.kr")
privateKey, jwks := mustE2EHeadlessRSAJWK(t)
const receivedAudience = "https://sso.hmac.kr/api/v1/auth/headless/password/login"
clientAssertion := mustE2EHeadlessClientAssertion(
t,
privateKey,
"headless-login-client",
receivedAudience,
)
logBuffer := &bytes.Buffer{}
logger := slog.New(slog.NewJSONHandler(logBuffer, &slog.HandlerOptions{Level: slog.LevelDebug}))
resp, output := runHeadlessPasswordLoginE2ERequest(
t,
logger,
"production",
jwks,
clientAssertion,
"http://sso.hmac.kr/api/v1/auth/headless/password/login",
nil,
)
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
bodyBytes, _ := io.ReadAll(resp.Body)
t.Fatalf("expected 200 for configured public https audience, got %d, body=%s", resp.StatusCode, string(bodyBytes))
}
var got map[string]any
if err := json.NewDecoder(resp.Body).Decode(&got); err != nil {
t.Fatalf("failed to decode response body: %v", err)
}
if got["redirectTo"] != "http://rp/cb" {
t.Fatalf("expected redirectTo, got=%v", got["redirectTo"])
}
if strings.Contains(output, "\"reason_code\":\"invalid_client_assertion_audience\"") {
t.Fatalf("did not expect audience mismatch log, got=%s", output)
}
}