forked from baron/baron-sso
adminfront/devfront code-check 오류 수정
This commit is contained in:
@@ -320,6 +320,27 @@ test.describe("User Management", () => {
|
|||||||
}) => {
|
}) => {
|
||||||
let updatePayload: Record<string, unknown> | undefined;
|
let updatePayload: Record<string, unknown> | undefined;
|
||||||
|
|
||||||
|
await page.route(/\/admin\/global-custom-claims$/, async (route) => {
|
||||||
|
if (route.request().method() !== "GET") {
|
||||||
|
return route.fallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
return route.fulfill({
|
||||||
|
json: {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
key: "contract_date",
|
||||||
|
label: "계약일",
|
||||||
|
valueType: "date",
|
||||||
|
readPermission: "admin_only",
|
||||||
|
writePermission: "admin_only",
|
||||||
|
description: "",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
await page.route(/\/admin\/users\/u-1$/, async (route) => {
|
await page.route(/\/admin\/users\/u-1$/, async (route) => {
|
||||||
const method = route.request().method();
|
const method = route.request().method();
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package handler
|
|||||||
import (
|
import (
|
||||||
"baron-sso-backend/internal/domain"
|
"baron-sso-backend/internal/domain"
|
||||||
"baron-sso-backend/internal/service"
|
"baron-sso-backend/internal/service"
|
||||||
"baron-sso-backend/internal/testsupport"
|
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
@@ -50,35 +49,37 @@ func newHeadlessLinkTestApp(h *AuthHandler) *fiber.App {
|
|||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
func newKratosWhoamiTestServer(t *testing.T, identityID string) *httptest.Server {
|
func newKratosWhoamiTestServer(t *testing.T, identityID string) string {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.URL.Path != "/sessions/whoami" {
|
|
||||||
http.NotFound(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if r.Header.Get("Cookie") == "" && r.Header.Get("X-Session-Token") == "" {
|
|
||||||
http.Error(w, "missing session", http.StatusUnauthorized)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
|
||||||
"id": "session-123",
|
|
||||||
"authenticated_at": "2026-05-21T00:00:00Z",
|
|
||||||
"identity": map[string]any{
|
|
||||||
"id": identityID,
|
|
||||||
"traits": map[string]any{
|
|
||||||
"email": "user@example.com",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}))
|
|
||||||
origDefaultClient := http.DefaultClient
|
origDefaultClient := http.DefaultClient
|
||||||
http.DefaultClient = server.Client()
|
http.DefaultClient = &http.Client{
|
||||||
|
Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) {
|
||||||
|
if r.URL.Path != "/sessions/whoami" {
|
||||||
|
return httpResponse(r, http.StatusNotFound, "not found"), nil
|
||||||
|
}
|
||||||
|
if r.Header.Get("Cookie") == "" && r.Header.Get("X-Session-Token") == "" {
|
||||||
|
return httpResponse(r, http.StatusUnauthorized, "missing session"), nil
|
||||||
|
}
|
||||||
|
body, err := json.Marshal(map[string]any{
|
||||||
|
"id": "session-123",
|
||||||
|
"authenticated_at": "2026-05-21T00:00:00Z",
|
||||||
|
"identity": map[string]any{
|
||||||
|
"id": identityID,
|
||||||
|
"traits": map[string]any{
|
||||||
|
"email": "user@example.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return httpResponse(r, http.StatusOK, string(body)), nil
|
||||||
|
}),
|
||||||
|
}
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
http.DefaultClient = origDefaultClient
|
http.DefaultClient = origDefaultClient
|
||||||
})
|
})
|
||||||
t.Cleanup(server.Close)
|
return "http://kratos.test"
|
||||||
return server
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEnchantedLinkFlow_Email_Success(t *testing.T) {
|
func TestEnchantedLinkFlow_Email_Success(t *testing.T) {
|
||||||
@@ -215,8 +216,7 @@ func TestVerifyMagicLink_VerifyOnlySharedBrowserSameSubjectApprovesOnly(t *testi
|
|||||||
redis := &mockRedisRepo{data: map[string]string{
|
redis := &mockRedisRepo{data: map[string]string{
|
||||||
prefixToken + "token-123": `{"pendingRef":"pending-123","loginId":"user@example.com"}`,
|
prefixToken + "token-123": `{"pendingRef":"pending-123","loginId":"user@example.com"}`,
|
||||||
}}
|
}}
|
||||||
kratosPublic := newKratosWhoamiTestServer(t, "kratos-user-1")
|
t.Setenv("KRATOS_PUBLIC_URL", newKratosWhoamiTestServer(t, "kratos-user-1"))
|
||||||
t.Setenv("KRATOS_PUBLIC_URL", kratosPublic.URL)
|
|
||||||
|
|
||||||
h := &AuthHandler{
|
h := &AuthHandler{
|
||||||
RedisService: redis,
|
RedisService: redis,
|
||||||
@@ -248,8 +248,7 @@ func TestVerifyMagicLink_VerifyOnlySharedBrowserDifferentSubjectApprovesOnly(t *
|
|||||||
redis := &mockRedisRepo{data: map[string]string{
|
redis := &mockRedisRepo{data: map[string]string{
|
||||||
prefixToken + "token-123": `{"pendingRef":"pending-123","loginId":"user@example.com"}`,
|
prefixToken + "token-123": `{"pendingRef":"pending-123","loginId":"user@example.com"}`,
|
||||||
}}
|
}}
|
||||||
kratosPublic := newKratosWhoamiTestServer(t, "kratos-other-user")
|
t.Setenv("KRATOS_PUBLIC_URL", newKratosWhoamiTestServer(t, "kratos-other-user"))
|
||||||
t.Setenv("KRATOS_PUBLIC_URL", kratosPublic.URL)
|
|
||||||
|
|
||||||
h := &AuthHandler{
|
h := &AuthHandler{
|
||||||
RedisService: redis,
|
RedisService: redis,
|
||||||
@@ -302,8 +301,7 @@ func TestVerifyLoginCode_VerifyOnlySharedBrowserDifferentSubjectApprovesOnly(t *
|
|||||||
prefixLoginCodePending + "user@example.com": "pending-123",
|
prefixLoginCodePending + "user@example.com": "pending-123",
|
||||||
prefixLoginCodeValue + "pending-123": "569765",
|
prefixLoginCodeValue + "pending-123": "569765",
|
||||||
}}
|
}}
|
||||||
kratosPublic := newKratosWhoamiTestServer(t, "kratos-other-user")
|
t.Setenv("KRATOS_PUBLIC_URL", newKratosWhoamiTestServer(t, "kratos-other-user"))
|
||||||
t.Setenv("KRATOS_PUBLIC_URL", kratosPublic.URL)
|
|
||||||
|
|
||||||
h := &AuthHandler{
|
h := &AuthHandler{
|
||||||
RedisService: redis,
|
RedisService: redis,
|
||||||
@@ -393,8 +391,7 @@ func TestPollEnchantedLink_SharedBrowserSameSubjectIssuesSession(t *testing.T) {
|
|||||||
redis := &mockRedisRepo{data: map[string]string{
|
redis := &mockRedisRepo{data: map[string]string{
|
||||||
prefixSession + "pending-123": `{"status":"approved","loginId":"user@example.com"}`,
|
prefixSession + "pending-123": `{"status":"approved","loginId":"user@example.com"}`,
|
||||||
}}
|
}}
|
||||||
kratosPublic := newKratosWhoamiTestServer(t, "kratos-user-1")
|
t.Setenv("KRATOS_PUBLIC_URL", newKratosWhoamiTestServer(t, "kratos-user-1"))
|
||||||
t.Setenv("KRATOS_PUBLIC_URL", kratosPublic.URL)
|
|
||||||
|
|
||||||
h := &AuthHandler{
|
h := &AuthHandler{
|
||||||
RedisService: redis,
|
RedisService: redis,
|
||||||
@@ -425,8 +422,7 @@ func TestPollEnchantedLink_SharedBrowserDifferentSubjectConflicts(t *testing.T)
|
|||||||
redis := &mockRedisRepo{data: map[string]string{
|
redis := &mockRedisRepo{data: map[string]string{
|
||||||
prefixSession + "pending-123": `{"status":"approved","loginId":"user@example.com"}`,
|
prefixSession + "pending-123": `{"status":"approved","loginId":"user@example.com"}`,
|
||||||
}}
|
}}
|
||||||
kratosPublic := newKratosWhoamiTestServer(t, "kratos-other-user")
|
t.Setenv("KRATOS_PUBLIC_URL", newKratosWhoamiTestServer(t, "kratos-other-user"))
|
||||||
t.Setenv("KRATOS_PUBLIC_URL", kratosPublic.URL)
|
|
||||||
|
|
||||||
h := &AuthHandler{
|
h := &AuthHandler{
|
||||||
RedisService: redis,
|
RedisService: redis,
|
||||||
@@ -456,18 +452,11 @@ func TestPollEnchantedLink_SharedBrowserDifferentSubjectConflicts(t *testing.T)
|
|||||||
func TestHeadlessLinkInit_HeadlessLoginClientSuccess(t *testing.T) {
|
func TestHeadlessLinkInit_HeadlessLoginClientSuccess(t *testing.T) {
|
||||||
t.Setenv("BACKEND_PUBLIC_URL", "")
|
t.Setenv("BACKEND_PUBLIC_URL", "")
|
||||||
|
|
||||||
if !testsupport.PortBindingAvailable() {
|
|
||||||
t.Skip("skipping headless link tests because this environment cannot bind local TCP listeners")
|
|
||||||
}
|
|
||||||
|
|
||||||
redis := &mockRedisRepo{data: make(map[string]string)}
|
redis := &mockRedisRepo{data: make(map[string]string)}
|
||||||
privateKey, jwks := mustHeadlessRSAJWK(t)
|
privateKey, jwks := mustHeadlessRSAJWK(t)
|
||||||
jwksBody, _ := json.Marshal(jwks)
|
jwksBody, _ := json.Marshal(jwks)
|
||||||
jwksServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
jwksClient := newJWKSHTTPClient(t, jwksBody)
|
||||||
w.Header().Set("Content-Type", "application/json")
|
jwksURI := jwksURL()
|
||||||
_, _ = w.Write(jwksBody)
|
|
||||||
}))
|
|
||||||
defer jwksServer.Close()
|
|
||||||
|
|
||||||
idp := &mockIdpProvider{
|
idp := &mockIdpProvider{
|
||||||
userExists: true,
|
userExists: true,
|
||||||
@@ -485,7 +474,7 @@ func TestHeadlessLinkInit_HeadlessLoginClientSuccess(t *testing.T) {
|
|||||||
"status": "active",
|
"status": "active",
|
||||||
"headless_login_enabled": true,
|
"headless_login_enabled": true,
|
||||||
"headless_token_endpoint_auth_method": "private_key_jwt",
|
"headless_token_endpoint_auth_method": "private_key_jwt",
|
||||||
"headless_jwks_uri": jwksServer.URL + "/.well-known/jwks.json",
|
"headless_jwks_uri": jwksURI,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -497,6 +486,7 @@ func TestHeadlessLinkInit_HeadlessLoginClientSuccess(t *testing.T) {
|
|||||||
h := &AuthHandler{
|
h := &AuthHandler{
|
||||||
RedisService: redis,
|
RedisService: redis,
|
||||||
IdpProvider: idp,
|
IdpProvider: idp,
|
||||||
|
HeadlessJWKS: service.NewHeadlessJWKSCacheService(nil, jwksClient),
|
||||||
SmsService: &mockSmsService{},
|
SmsService: &mockSmsService{},
|
||||||
Hydra: &service.HydraAdminService{
|
Hydra: &service.HydraAdminService{
|
||||||
AdminURL: "http://hydra.test",
|
AdminURL: "http://hydra.test",
|
||||||
@@ -529,10 +519,6 @@ func TestHeadlessLinkInit_HeadlessLoginClientSuccess(t *testing.T) {
|
|||||||
func TestHeadlessLinkPoll_AfterApprovalReturnsRedirect(t *testing.T) {
|
func TestHeadlessLinkPoll_AfterApprovalReturnsRedirect(t *testing.T) {
|
||||||
t.Setenv("BACKEND_PUBLIC_URL", "")
|
t.Setenv("BACKEND_PUBLIC_URL", "")
|
||||||
|
|
||||||
if !testsupport.PortBindingAvailable() {
|
|
||||||
t.Skip("skipping headless link tests because this environment cannot bind local TCP listeners")
|
|
||||||
}
|
|
||||||
|
|
||||||
redis := &mockRedisRepo{data: make(map[string]string)}
|
redis := &mockRedisRepo{data: make(map[string]string)}
|
||||||
privateKey, jwks := mustHeadlessRSAJWK(t)
|
privateKey, jwks := mustHeadlessRSAJWK(t)
|
||||||
jwksBody, _ := json.Marshal(jwks)
|
jwksBody, _ := json.Marshal(jwks)
|
||||||
@@ -659,10 +645,6 @@ func TestHeadlessLinkPoll_AfterApprovalReturnsRedirect(t *testing.T) {
|
|||||||
func TestHeadlessLinkPoll_ApproverSubjectConflictBlocksMixedRP(t *testing.T) {
|
func TestHeadlessLinkPoll_ApproverSubjectConflictBlocksMixedRP(t *testing.T) {
|
||||||
t.Setenv("BACKEND_PUBLIC_URL", "")
|
t.Setenv("BACKEND_PUBLIC_URL", "")
|
||||||
|
|
||||||
if !testsupport.PortBindingAvailable() {
|
|
||||||
t.Skip("skipping headless link tests because this environment cannot bind local TCP listeners")
|
|
||||||
}
|
|
||||||
|
|
||||||
redis := &mockRedisRepo{data: make(map[string]string)}
|
redis := &mockRedisRepo{data: make(map[string]string)}
|
||||||
privateKey, jwks := mustHeadlessRSAJWK(t)
|
privateKey, jwks := mustHeadlessRSAJWK(t)
|
||||||
jwksBody, _ := json.Marshal(jwks)
|
jwksBody, _ := json.Marshal(jwks)
|
||||||
@@ -748,8 +730,7 @@ func TestHeadlessLinkPoll_ApproverSubjectConflictBlocksMixedRP(t *testing.T) {
|
|||||||
}
|
}
|
||||||
assert.NotEmpty(t, token)
|
assert.NotEmpty(t, token)
|
||||||
|
|
||||||
kratosPublic := newKratosWhoamiTestServer(t, "kratos-userfront-a")
|
t.Setenv("KRATOS_PUBLIC_URL", newKratosWhoamiTestServer(t, "kratos-userfront-a"))
|
||||||
t.Setenv("KRATOS_PUBLIC_URL", kratosPublic.URL)
|
|
||||||
|
|
||||||
verifyBody, _ := json.Marshal(map[string]any{
|
verifyBody, _ := json.Marshal(map[string]any{
|
||||||
"token": token,
|
"token": token,
|
||||||
@@ -785,10 +766,6 @@ func TestHeadlessLinkPoll_ApproverSubjectConflictBlocksMixedRP(t *testing.T) {
|
|||||||
func TestHeadlessLinkPoll_RequestCookieSubjectConflictBlocksMixedRP(t *testing.T) {
|
func TestHeadlessLinkPoll_RequestCookieSubjectConflictBlocksMixedRP(t *testing.T) {
|
||||||
t.Setenv("BACKEND_PUBLIC_URL", "")
|
t.Setenv("BACKEND_PUBLIC_URL", "")
|
||||||
|
|
||||||
if !testsupport.PortBindingAvailable() {
|
|
||||||
t.Skip("skipping headless link tests because this environment cannot bind local TCP listeners")
|
|
||||||
}
|
|
||||||
|
|
||||||
redis := &mockRedisRepo{data: make(map[string]string)}
|
redis := &mockRedisRepo{data: make(map[string]string)}
|
||||||
privateKey, jwks := mustHeadlessRSAJWK(t)
|
privateKey, jwks := mustHeadlessRSAJWK(t)
|
||||||
jwksBody, _ := json.Marshal(jwks)
|
jwksBody, _ := json.Marshal(jwks)
|
||||||
@@ -880,8 +857,7 @@ func TestHeadlessLinkPoll_RequestCookieSubjectConflictBlocksMixedRP(t *testing.T
|
|||||||
resp, _ = app.Test(req, -1)
|
resp, _ = app.Test(req, -1)
|
||||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
|
||||||
kratosPublic := newKratosWhoamiTestServer(t, "kratos-userfront-a")
|
t.Setenv("KRATOS_PUBLIC_URL", newKratosWhoamiTestServer(t, "kratos-userfront-a"))
|
||||||
t.Setenv("KRATOS_PUBLIC_URL", kratosPublic.URL)
|
|
||||||
|
|
||||||
pollBody, _ := json.Marshal(map[string]string{
|
pollBody, _ := json.Marshal(map[string]string{
|
||||||
"client_id": "headless-login-client",
|
"client_id": "headless-login-client",
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"baron-sso-backend/internal/domain"
|
"baron-sso-backend/internal/domain"
|
||||||
"baron-sso-backend/internal/middleware"
|
"baron-sso-backend/internal/middleware"
|
||||||
"baron-sso-backend/internal/service"
|
"baron-sso-backend/internal/service"
|
||||||
"baron-sso-backend/internal/testsupport"
|
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
@@ -446,10 +445,6 @@ func runHeadlessPasswordLoginWithAssertionRequest(
|
|||||||
headers map[string]string,
|
headers map[string]string,
|
||||||
) *http.Response {
|
) *http.Response {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
if !testsupport.PortBindingAvailable() {
|
|
||||||
t.Skip("skipping headless login tests because this environment cannot bind local TCP listeners")
|
|
||||||
}
|
|
||||||
|
|
||||||
mockIdp := new(MockIdentityProvider)
|
mockIdp := new(MockIdentityProvider)
|
||||||
mockIdp.On("SignIn", "employee001", "password").Return(&domain.AuthInfo{
|
mockIdp.On("SignIn", "employee001", "password").Return(&domain.AuthInfo{
|
||||||
SessionToken: &domain.Token{JWT: "valid-jwt"},
|
SessionToken: &domain.Token{JWT: "valid-jwt"},
|
||||||
@@ -463,11 +458,8 @@ func runHeadlessPasswordLoginWithAssertionRequest(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to marshal jwks body: %v", err)
|
t.Fatalf("failed to marshal jwks body: %v", err)
|
||||||
}
|
}
|
||||||
jwksServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
jwksClient := newJWKSHTTPClient(t, jwksBody)
|
||||||
w.Header().Set("Content-Type", "application/json")
|
jwksURI := jwksURL()
|
||||||
_, _ = w.Write(jwksBody)
|
|
||||||
}))
|
|
||||||
t.Cleanup(jwksServer.Close)
|
|
||||||
|
|
||||||
hydraHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
hydraHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
switch {
|
switch {
|
||||||
@@ -481,7 +473,7 @@ func runHeadlessPasswordLoginWithAssertionRequest(
|
|||||||
"status": "active",
|
"status": "active",
|
||||||
"headless_login_enabled": true,
|
"headless_login_enabled": true,
|
||||||
"headless_token_endpoint_auth_method": "private_key_jwt",
|
"headless_token_endpoint_auth_method": "private_key_jwt",
|
||||||
"headless_jwks_uri": jwksServer.URL + "/.well-known/jwks.json",
|
"headless_jwks_uri": jwksURI,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -496,6 +488,7 @@ func runHeadlessPasswordLoginWithAssertionRequest(
|
|||||||
h := &AuthHandler{
|
h := &AuthHandler{
|
||||||
IdpProvider: mockIdp,
|
IdpProvider: mockIdp,
|
||||||
KratosAdmin: mockKratos,
|
KratosAdmin: mockKratos,
|
||||||
|
HeadlessJWKS: service.NewHeadlessJWKSCacheService(nil, jwksClient),
|
||||||
Hydra: &service.HydraAdminService{
|
Hydra: &service.HydraAdminService{
|
||||||
AdminURL: "http://hydra.test",
|
AdminURL: "http://hydra.test",
|
||||||
HTTPClient: &http.Client{Transport: mockHydraTransport(hydraHandler)},
|
HTTPClient: &http.Client{Transport: mockHydraTransport(hydraHandler)},
|
||||||
@@ -551,10 +544,6 @@ func runHeadlessPasswordLoginWithAssertionAndLoggerRequest(
|
|||||||
logger *slog.Logger,
|
logger *slog.Logger,
|
||||||
) *http.Response {
|
) *http.Response {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
if !testsupport.PortBindingAvailable() {
|
|
||||||
t.Skip("skipping headless login tests because this environment cannot bind local TCP listeners")
|
|
||||||
}
|
|
||||||
|
|
||||||
mockIdp := new(MockIdentityProvider)
|
mockIdp := new(MockIdentityProvider)
|
||||||
mockIdp.On("SignIn", "employee001", "password").Return(&domain.AuthInfo{
|
mockIdp.On("SignIn", "employee001", "password").Return(&domain.AuthInfo{
|
||||||
SessionToken: &domain.Token{JWT: "valid-jwt"},
|
SessionToken: &domain.Token{JWT: "valid-jwt"},
|
||||||
@@ -568,11 +557,8 @@ func runHeadlessPasswordLoginWithAssertionAndLoggerRequest(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to marshal jwks body: %v", err)
|
t.Fatalf("failed to marshal jwks body: %v", err)
|
||||||
}
|
}
|
||||||
jwksServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
jwksClient := newJWKSHTTPClient(t, jwksBody)
|
||||||
w.Header().Set("Content-Type", "application/json")
|
jwksURI := jwksURL()
|
||||||
_, _ = w.Write(jwksBody)
|
|
||||||
}))
|
|
||||||
t.Cleanup(jwksServer.Close)
|
|
||||||
|
|
||||||
hydraHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
hydraHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
switch {
|
switch {
|
||||||
@@ -586,7 +572,7 @@ func runHeadlessPasswordLoginWithAssertionAndLoggerRequest(
|
|||||||
"status": "active",
|
"status": "active",
|
||||||
"headless_login_enabled": true,
|
"headless_login_enabled": true,
|
||||||
"headless_token_endpoint_auth_method": "private_key_jwt",
|
"headless_token_endpoint_auth_method": "private_key_jwt",
|
||||||
"headless_jwks_uri": jwksServer.URL + "/.well-known/jwks.json",
|
"headless_jwks_uri": jwksURI,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -601,6 +587,7 @@ func runHeadlessPasswordLoginWithAssertionAndLoggerRequest(
|
|||||||
h := &AuthHandler{
|
h := &AuthHandler{
|
||||||
IdpProvider: mockIdp,
|
IdpProvider: mockIdp,
|
||||||
KratosAdmin: mockKratos,
|
KratosAdmin: mockKratos,
|
||||||
|
HeadlessJWKS: service.NewHeadlessJWKSCacheService(nil, jwksClient),
|
||||||
Hydra: &service.HydraAdminService{
|
Hydra: &service.HydraAdminService{
|
||||||
AdminURL: "http://hydra.test",
|
AdminURL: "http://hydra.test",
|
||||||
HTTPClient: &http.Client{Transport: mockHydraTransport(hydraHandler)},
|
HTTPClient: &http.Client{Transport: mockHydraTransport(hydraHandler)},
|
||||||
@@ -879,10 +866,6 @@ func TestPasswordLogin_UserFront_AuditIncludesDefaultClientMetadata(t *testing.T
|
|||||||
func TestHeadlessPasswordLogin_HeadlessLoginClientSuccess(t *testing.T) {
|
func TestHeadlessPasswordLogin_HeadlessLoginClientSuccess(t *testing.T) {
|
||||||
t.Setenv("BACKEND_PUBLIC_URL", "")
|
t.Setenv("BACKEND_PUBLIC_URL", "")
|
||||||
|
|
||||||
if !testsupport.PortBindingAvailable() {
|
|
||||||
t.Skip("skipping headless login tests because this environment cannot bind local TCP listeners")
|
|
||||||
}
|
|
||||||
|
|
||||||
mockIdp := new(MockIdentityProvider)
|
mockIdp := new(MockIdentityProvider)
|
||||||
mockIdp.On("SignIn", "employee001", "password").Return(&domain.AuthInfo{
|
mockIdp.On("SignIn", "employee001", "password").Return(&domain.AuthInfo{
|
||||||
SessionToken: &domain.Token{JWT: "valid-jwt"},
|
SessionToken: &domain.Token{JWT: "valid-jwt"},
|
||||||
@@ -891,11 +874,8 @@ func TestHeadlessPasswordLogin_HeadlessLoginClientSuccess(t *testing.T) {
|
|||||||
|
|
||||||
privateKey, jwks := mustHeadlessRSAJWK(t)
|
privateKey, jwks := mustHeadlessRSAJWK(t)
|
||||||
jwksBody, _ := json.Marshal(jwks)
|
jwksBody, _ := json.Marshal(jwks)
|
||||||
jwksServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
jwksClient := newJWKSHTTPClient(t, jwksBody)
|
||||||
w.Header().Set("Content-Type", "application/json")
|
jwksURI := jwksURL()
|
||||||
_, _ = w.Write(jwksBody)
|
|
||||||
}))
|
|
||||||
defer jwksServer.Close()
|
|
||||||
|
|
||||||
hydraHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
hydraHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
switch {
|
switch {
|
||||||
@@ -909,7 +889,7 @@ func TestHeadlessPasswordLogin_HeadlessLoginClientSuccess(t *testing.T) {
|
|||||||
"status": "active",
|
"status": "active",
|
||||||
"headless_login_enabled": true,
|
"headless_login_enabled": true,
|
||||||
"headless_token_endpoint_auth_method": "private_key_jwt",
|
"headless_token_endpoint_auth_method": "private_key_jwt",
|
||||||
"headless_jwks_uri": jwksServer.URL + "/.well-known/jwks.json",
|
"headless_jwks_uri": jwksURI,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -926,6 +906,7 @@ func TestHeadlessPasswordLogin_HeadlessLoginClientSuccess(t *testing.T) {
|
|||||||
h := &AuthHandler{
|
h := &AuthHandler{
|
||||||
IdpProvider: mockIdp,
|
IdpProvider: mockIdp,
|
||||||
KratosAdmin: mockKratos,
|
KratosAdmin: mockKratos,
|
||||||
|
HeadlessJWKS: service.NewHeadlessJWKSCacheService(nil, jwksClient),
|
||||||
Hydra: &service.HydraAdminService{
|
Hydra: &service.HydraAdminService{
|
||||||
AdminURL: "http://hydra.test",
|
AdminURL: "http://hydra.test",
|
||||||
HTTPClient: &http.Client{Transport: mockHydraTransport(hydraHandler)},
|
HTTPClient: &http.Client{Transport: mockHydraTransport(hydraHandler)},
|
||||||
@@ -979,10 +960,6 @@ func TestHeadlessPasswordLogin_HeadlessLoginClientSuccess(t *testing.T) {
|
|||||||
func TestHeadlessPasswordLogin_OIDCSubjectConflictBlocksMixedRP(t *testing.T) {
|
func TestHeadlessPasswordLogin_OIDCSubjectConflictBlocksMixedRP(t *testing.T) {
|
||||||
t.Setenv("BACKEND_PUBLIC_URL", "")
|
t.Setenv("BACKEND_PUBLIC_URL", "")
|
||||||
|
|
||||||
if !testsupport.PortBindingAvailable() {
|
|
||||||
t.Skip("skipping headless login tests because this environment cannot bind local TCP listeners")
|
|
||||||
}
|
|
||||||
|
|
||||||
mockIdp := new(MockIdentityProvider)
|
mockIdp := new(MockIdentityProvider)
|
||||||
mockIdp.On("SignIn", "employee002", "password").Return(&domain.AuthInfo{
|
mockIdp.On("SignIn", "employee002", "password").Return(&domain.AuthInfo{
|
||||||
SessionToken: &domain.Token{JWT: "valid-jwt"},
|
SessionToken: &domain.Token{JWT: "valid-jwt"},
|
||||||
@@ -991,11 +968,8 @@ func TestHeadlessPasswordLogin_OIDCSubjectConflictBlocksMixedRP(t *testing.T) {
|
|||||||
|
|
||||||
privateKey, jwks := mustHeadlessRSAJWK(t)
|
privateKey, jwks := mustHeadlessRSAJWK(t)
|
||||||
jwksBody, _ := json.Marshal(jwks)
|
jwksBody, _ := json.Marshal(jwks)
|
||||||
jwksServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
jwksClient := newJWKSHTTPClient(t, jwksBody)
|
||||||
w.Header().Set("Content-Type", "application/json")
|
jwksURI := jwksURL()
|
||||||
_, _ = w.Write(jwksBody)
|
|
||||||
}))
|
|
||||||
defer jwksServer.Close()
|
|
||||||
|
|
||||||
acceptCalled := false
|
acceptCalled := false
|
||||||
hydraHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
hydraHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -1012,7 +986,7 @@ func TestHeadlessPasswordLogin_OIDCSubjectConflictBlocksMixedRP(t *testing.T) {
|
|||||||
"status": "active",
|
"status": "active",
|
||||||
"headless_login_enabled": true,
|
"headless_login_enabled": true,
|
||||||
"headless_token_endpoint_auth_method": "private_key_jwt",
|
"headless_token_endpoint_auth_method": "private_key_jwt",
|
||||||
"headless_jwks_uri": jwksServer.URL + "/.well-known/jwks.json",
|
"headless_jwks_uri": jwksURI,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -1030,6 +1004,7 @@ func TestHeadlessPasswordLogin_OIDCSubjectConflictBlocksMixedRP(t *testing.T) {
|
|||||||
h := &AuthHandler{
|
h := &AuthHandler{
|
||||||
IdpProvider: mockIdp,
|
IdpProvider: mockIdp,
|
||||||
KratosAdmin: mockKratos,
|
KratosAdmin: mockKratos,
|
||||||
|
HeadlessJWKS: service.NewHeadlessJWKSCacheService(nil, jwksClient),
|
||||||
Hydra: &service.HydraAdminService{
|
Hydra: &service.HydraAdminService{
|
||||||
AdminURL: "http://hydra.test",
|
AdminURL: "http://hydra.test",
|
||||||
HTTPClient: &http.Client{Transport: mockHydraTransport(hydraHandler)},
|
HTTPClient: &http.Client{Transport: mockHydraTransport(hydraHandler)},
|
||||||
@@ -1065,10 +1040,6 @@ func TestHeadlessPasswordLogin_OIDCSubjectConflictBlocksMixedRP(t *testing.T) {
|
|||||||
func TestHeadlessPasswordLogin_OIDCSubjectSameAllowsMixedRP(t *testing.T) {
|
func TestHeadlessPasswordLogin_OIDCSubjectSameAllowsMixedRP(t *testing.T) {
|
||||||
t.Setenv("BACKEND_PUBLIC_URL", "")
|
t.Setenv("BACKEND_PUBLIC_URL", "")
|
||||||
|
|
||||||
if !testsupport.PortBindingAvailable() {
|
|
||||||
t.Skip("skipping headless login tests because this environment cannot bind local TCP listeners")
|
|
||||||
}
|
|
||||||
|
|
||||||
mockIdp := new(MockIdentityProvider)
|
mockIdp := new(MockIdentityProvider)
|
||||||
mockIdp.On("SignIn", "employee001", "password").Return(&domain.AuthInfo{
|
mockIdp.On("SignIn", "employee001", "password").Return(&domain.AuthInfo{
|
||||||
SessionToken: &domain.Token{JWT: "valid-jwt"},
|
SessionToken: &domain.Token{JWT: "valid-jwt"},
|
||||||
@@ -1077,11 +1048,8 @@ func TestHeadlessPasswordLogin_OIDCSubjectSameAllowsMixedRP(t *testing.T) {
|
|||||||
|
|
||||||
privateKey, jwks := mustHeadlessRSAJWK(t)
|
privateKey, jwks := mustHeadlessRSAJWK(t)
|
||||||
jwksBody, _ := json.Marshal(jwks)
|
jwksBody, _ := json.Marshal(jwks)
|
||||||
jwksServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
jwksClient := newJWKSHTTPClient(t, jwksBody)
|
||||||
w.Header().Set("Content-Type", "application/json")
|
jwksURI := jwksURL()
|
||||||
_, _ = w.Write(jwksBody)
|
|
||||||
}))
|
|
||||||
defer jwksServer.Close()
|
|
||||||
|
|
||||||
hydraHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
hydraHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
switch {
|
switch {
|
||||||
@@ -1097,7 +1065,7 @@ func TestHeadlessPasswordLogin_OIDCSubjectSameAllowsMixedRP(t *testing.T) {
|
|||||||
"status": "active",
|
"status": "active",
|
||||||
"headless_login_enabled": true,
|
"headless_login_enabled": true,
|
||||||
"headless_token_endpoint_auth_method": "private_key_jwt",
|
"headless_token_endpoint_auth_method": "private_key_jwt",
|
||||||
"headless_jwks_uri": jwksServer.URL + "/.well-known/jwks.json",
|
"headless_jwks_uri": jwksURI,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -1114,6 +1082,7 @@ func TestHeadlessPasswordLogin_OIDCSubjectSameAllowsMixedRP(t *testing.T) {
|
|||||||
h := &AuthHandler{
|
h := &AuthHandler{
|
||||||
IdpProvider: mockIdp,
|
IdpProvider: mockIdp,
|
||||||
KratosAdmin: mockKratos,
|
KratosAdmin: mockKratos,
|
||||||
|
HeadlessJWKS: service.NewHeadlessJWKSCacheService(nil, jwksClient),
|
||||||
Hydra: &service.HydraAdminService{
|
Hydra: &service.HydraAdminService{
|
||||||
AdminURL: "http://hydra.test",
|
AdminURL: "http://hydra.test",
|
||||||
HTTPClient: &http.Client{Transport: mockHydraTransport(hydraHandler)},
|
HTTPClient: &http.Client{Transport: mockHydraTransport(hydraHandler)},
|
||||||
@@ -1271,10 +1240,6 @@ func TestHeadlessPasswordLogin_AuditIncludesClientMetadata(t *testing.T) {
|
|||||||
func TestHeadlessPasswordLogin_IgnoresInlineHeadlessJWKSWhenJWKSURIIsConfigured(t *testing.T) {
|
func TestHeadlessPasswordLogin_IgnoresInlineHeadlessJWKSWhenJWKSURIIsConfigured(t *testing.T) {
|
||||||
t.Setenv("BACKEND_PUBLIC_URL", "")
|
t.Setenv("BACKEND_PUBLIC_URL", "")
|
||||||
|
|
||||||
if !testsupport.PortBindingAvailable() {
|
|
||||||
t.Skip("skipping headless login tests because this environment cannot bind local TCP listeners")
|
|
||||||
}
|
|
||||||
|
|
||||||
mockIdp := new(MockIdentityProvider)
|
mockIdp := new(MockIdentityProvider)
|
||||||
mockIdp.On("SignIn", "employee001", "password").Return(&domain.AuthInfo{
|
mockIdp.On("SignIn", "employee001", "password").Return(&domain.AuthInfo{
|
||||||
SessionToken: &domain.Token{JWT: "valid-jwt"},
|
SessionToken: &domain.Token{JWT: "valid-jwt"},
|
||||||
@@ -1283,11 +1248,8 @@ func TestHeadlessPasswordLogin_IgnoresInlineHeadlessJWKSWhenJWKSURIIsConfigured(
|
|||||||
|
|
||||||
privateKey, jwks := mustHeadlessRSAJWK(t)
|
privateKey, jwks := mustHeadlessRSAJWK(t)
|
||||||
jwksBody, _ := json.Marshal(jwks)
|
jwksBody, _ := json.Marshal(jwks)
|
||||||
jwksServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
jwksClient := newJWKSHTTPClient(t, jwksBody)
|
||||||
w.Header().Set("Content-Type", "application/json")
|
jwksURI := jwksURL()
|
||||||
_, _ = w.Write(jwksBody)
|
|
||||||
}))
|
|
||||||
defer jwksServer.Close()
|
|
||||||
|
|
||||||
hydraHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
hydraHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
switch {
|
switch {
|
||||||
@@ -1301,7 +1263,7 @@ func TestHeadlessPasswordLogin_IgnoresInlineHeadlessJWKSWhenJWKSURIIsConfigured(
|
|||||||
"status": "active",
|
"status": "active",
|
||||||
"headless_login_enabled": true,
|
"headless_login_enabled": true,
|
||||||
"headless_token_endpoint_auth_method": "private_key_jwt",
|
"headless_token_endpoint_auth_method": "private_key_jwt",
|
||||||
"headless_jwks_uri": jwksServer.URL + "/.well-known/jwks.json",
|
"headless_jwks_uri": jwksURI,
|
||||||
"headless_jwks": map[string]any{
|
"headless_jwks": map[string]any{
|
||||||
"keys": []map[string]any{},
|
"keys": []map[string]any{},
|
||||||
},
|
},
|
||||||
@@ -1321,6 +1283,7 @@ func TestHeadlessPasswordLogin_IgnoresInlineHeadlessJWKSWhenJWKSURIIsConfigured(
|
|||||||
h := &AuthHandler{
|
h := &AuthHandler{
|
||||||
IdpProvider: mockIdp,
|
IdpProvider: mockIdp,
|
||||||
KratosAdmin: mockKratos,
|
KratosAdmin: mockKratos,
|
||||||
|
HeadlessJWKS: service.NewHeadlessJWKSCacheService(nil, jwksClient),
|
||||||
Hydra: &service.HydraAdminService{
|
Hydra: &service.HydraAdminService{
|
||||||
AdminURL: "http://hydra.test",
|
AdminURL: "http://hydra.test",
|
||||||
HTTPClient: &http.Client{Transport: mockHydraTransport(hydraHandler)},
|
HTTPClient: &http.Client{Transport: mockHydraTransport(hydraHandler)},
|
||||||
@@ -1360,10 +1323,6 @@ func TestHeadlessPasswordLogin_IgnoresInlineHeadlessJWKSWhenJWKSURIIsConfigured(
|
|||||||
func TestHeadlessPasswordLogin_RefreshesJWKSWhenSignatureFailsForCachedKid(t *testing.T) {
|
func TestHeadlessPasswordLogin_RefreshesJWKSWhenSignatureFailsForCachedKid(t *testing.T) {
|
||||||
t.Setenv("BACKEND_PUBLIC_URL", "")
|
t.Setenv("BACKEND_PUBLIC_URL", "")
|
||||||
|
|
||||||
if !testsupport.PortBindingAvailable() {
|
|
||||||
t.Skip("skipping headless login tests because this environment cannot bind local TCP listeners")
|
|
||||||
}
|
|
||||||
|
|
||||||
mockIdp := new(MockIdentityProvider)
|
mockIdp := new(MockIdentityProvider)
|
||||||
mockIdp.On("SignIn", "employee001", "password").Return(&domain.AuthInfo{
|
mockIdp.On("SignIn", "employee001", "password").Return(&domain.AuthInfo{
|
||||||
SessionToken: &domain.Token{JWT: "valid-jwt"},
|
SessionToken: &domain.Token{JWT: "valid-jwt"},
|
||||||
@@ -1383,12 +1342,11 @@ func TestHeadlessPasswordLogin_RefreshesJWKSWhenSignatureFailsForCachedKid(t *te
|
|||||||
}
|
}
|
||||||
|
|
||||||
fetchCount := 0
|
fetchCount := 0
|
||||||
jwksServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
jwksClient := &http.Client{Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) {
|
||||||
fetchCount++
|
fetchCount++
|
||||||
w.Header().Set("Content-Type", "application/json")
|
return httpResponse(r, http.StatusOK, string(freshRaw)), nil
|
||||||
_, _ = w.Write(freshRaw)
|
})}
|
||||||
}))
|
jwksURI := jwksURL()
|
||||||
defer jwksServer.Close()
|
|
||||||
|
|
||||||
hydraHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
hydraHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
switch {
|
switch {
|
||||||
@@ -1402,7 +1360,7 @@ func TestHeadlessPasswordLogin_RefreshesJWKSWhenSignatureFailsForCachedKid(t *te
|
|||||||
"status": "active",
|
"status": "active",
|
||||||
"headless_login_enabled": true,
|
"headless_login_enabled": true,
|
||||||
"headless_token_endpoint_auth_method": "private_key_jwt",
|
"headless_token_endpoint_auth_method": "private_key_jwt",
|
||||||
"headless_jwks_uri": jwksServer.URL + "/.well-known/jwks.json",
|
"headless_jwks_uri": jwksURI,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -1417,12 +1375,12 @@ func TestHeadlessPasswordLogin_RefreshesJWKSWhenSignatureFailsForCachedKid(t *te
|
|||||||
mockKratos.On("FindIdentityIDByIdentifier", mock.Anything, "employee001").Return("kratos-identity-id", nil)
|
mockKratos.On("FindIdentityIDByIdentifier", mock.Anything, "employee001").Return("kratos-identity-id", nil)
|
||||||
|
|
||||||
redisRepo := &testRedisRepo{values: map[string]string{}}
|
redisRepo := &testRedisRepo{values: map[string]string{}}
|
||||||
cacheService := service.NewHeadlessJWKSCacheService(redisRepo, jwksServer.Client())
|
cacheService := service.NewHeadlessJWKSCacheService(redisRepo, jwksClient)
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
expiresAt := now.Add(30 * time.Minute)
|
expiresAt := now.Add(30 * time.Minute)
|
||||||
if err := cacheService.SaveState("headless-login-client", domain.HeadlessJWKSCacheState{
|
if err := cacheService.SaveState("headless-login-client", domain.HeadlessJWKSCacheState{
|
||||||
ClientID: "headless-login-client",
|
ClientID: "headless-login-client",
|
||||||
JWKSURI: jwksServer.URL + "/.well-known/jwks.json",
|
JWKSURI: jwksURI,
|
||||||
RawJWKS: string(staleRaw),
|
RawJWKS: string(staleRaw),
|
||||||
CachedKids: []string{"test-kid"},
|
CachedKids: []string{"test-kid"},
|
||||||
CachedAt: &now,
|
CachedAt: &now,
|
||||||
@@ -1546,10 +1504,6 @@ func TestHeadlessPasswordLogin_MissingClientAssertionRejected(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestHeadlessPasswordLogin_InvalidClientAssertionRejected(t *testing.T) {
|
func TestHeadlessPasswordLogin_InvalidClientAssertionRejected(t *testing.T) {
|
||||||
if !testsupport.PortBindingAvailable() {
|
|
||||||
t.Skip("skipping headless login tests because this environment cannot bind local TCP listeners")
|
|
||||||
}
|
|
||||||
|
|
||||||
mockIdp := new(MockIdentityProvider)
|
mockIdp := new(MockIdentityProvider)
|
||||||
mockIdp.On("SignIn", "employee001", "password").Return(&domain.AuthInfo{
|
mockIdp.On("SignIn", "employee001", "password").Return(&domain.AuthInfo{
|
||||||
SessionToken: &domain.Token{JWT: "valid-jwt"},
|
SessionToken: &domain.Token{JWT: "valid-jwt"},
|
||||||
@@ -1562,11 +1516,8 @@ func TestHeadlessPasswordLogin_InvalidClientAssertionRejected(t *testing.T) {
|
|||||||
invalidKey, _ := mustHeadlessRSAJWK(t)
|
invalidKey, _ := mustHeadlessRSAJWK(t)
|
||||||
_ = validKey
|
_ = validKey
|
||||||
jwksBody, _ := json.Marshal(jwks)
|
jwksBody, _ := json.Marshal(jwks)
|
||||||
jwksServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
jwksClient := newJWKSHTTPClient(t, jwksBody)
|
||||||
w.Header().Set("Content-Type", "application/json")
|
jwksURI := jwksURL()
|
||||||
_, _ = w.Write(jwksBody)
|
|
||||||
}))
|
|
||||||
defer jwksServer.Close()
|
|
||||||
|
|
||||||
hydraHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
hydraHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
switch {
|
switch {
|
||||||
@@ -1580,7 +1531,7 @@ func TestHeadlessPasswordLogin_InvalidClientAssertionRejected(t *testing.T) {
|
|||||||
"status": "active",
|
"status": "active",
|
||||||
"headless_login_enabled": true,
|
"headless_login_enabled": true,
|
||||||
"headless_token_endpoint_auth_method": "private_key_jwt",
|
"headless_token_endpoint_auth_method": "private_key_jwt",
|
||||||
"headless_jwks_uri": jwksServer.URL + "/.well-known/jwks.json",
|
"headless_jwks_uri": jwksURI,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -1595,6 +1546,7 @@ func TestHeadlessPasswordLogin_InvalidClientAssertionRejected(t *testing.T) {
|
|||||||
h := &AuthHandler{
|
h := &AuthHandler{
|
||||||
IdpProvider: mockIdp,
|
IdpProvider: mockIdp,
|
||||||
KratosAdmin: mockKratos,
|
KratosAdmin: mockKratos,
|
||||||
|
HeadlessJWKS: service.NewHeadlessJWKSCacheService(nil, jwksClient),
|
||||||
Hydra: &service.HydraAdminService{
|
Hydra: &service.HydraAdminService{
|
||||||
AdminURL: "http://hydra.test",
|
AdminURL: "http://hydra.test",
|
||||||
HTTPClient: &http.Client{Transport: mockHydraTransport(hydraHandler)},
|
HTTPClient: &http.Client{Transport: mockHydraTransport(hydraHandler)},
|
||||||
@@ -2198,8 +2150,7 @@ func TestPasswordLogin_SharedBrowserSameSubjectAllowed(t *testing.T) {
|
|||||||
Subject: "kratos-user-1",
|
Subject: "kratos-user-1",
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
kratosPublic := newKratosWhoamiTestServer(t, "kratos-user-1")
|
t.Setenv("KRATOS_PUBLIC_URL", newKratosWhoamiTestServer(t, "kratos-user-1"))
|
||||||
t.Setenv("KRATOS_PUBLIC_URL", kratosPublic.URL)
|
|
||||||
|
|
||||||
mockKratos := new(MockKratosAdminService)
|
mockKratos := new(MockKratosAdminService)
|
||||||
mockKratos.On("FindIdentityIDByIdentifier", mock.Anything, "user@example.com").Return("kratos-user-1", nil)
|
mockKratos.On("FindIdentityIDByIdentifier", mock.Anything, "user@example.com").Return("kratos-user-1", nil)
|
||||||
@@ -2237,8 +2188,7 @@ func TestPasswordLogin_SharedBrowserDifferentSubjectConflicts(t *testing.T) {
|
|||||||
Subject: "kratos-user-1",
|
Subject: "kratos-user-1",
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
kratosPublic := newKratosWhoamiTestServer(t, "kratos-other-user")
|
t.Setenv("KRATOS_PUBLIC_URL", newKratosWhoamiTestServer(t, "kratos-other-user"))
|
||||||
t.Setenv("KRATOS_PUBLIC_URL", kratosPublic.URL)
|
|
||||||
|
|
||||||
mockKratos := new(MockKratosAdminService)
|
mockKratos := new(MockKratosAdminService)
|
||||||
mockKratos.On("FindIdentityIDByIdentifier", mock.Anything, "user@example.com").Return("kratos-user-1", nil)
|
mockKratos.On("FindIdentityIDByIdentifier", mock.Anything, "user@example.com").Return("kratos-user-1", nil)
|
||||||
|
|||||||
@@ -1590,10 +1590,8 @@ func TestCreateClient_ApprovedDeveloperRequestAllowsCreateWhenTenantGrantNotVisi
|
|||||||
mockKeto.On("CheckPermission", mock.Anything, "User:user-1", "System", "global", "manage_all").Return(false, nil).Maybe()
|
mockKeto.On("CheckPermission", mock.Anything, "User:user-1", "System", "global", "manage_all").Return(false, nil).Maybe()
|
||||||
|
|
||||||
developerSvc := new(devMockDeveloperService)
|
developerSvc := new(devMockDeveloperService)
|
||||||
developerSvc.On("GetRequestStatus", mock.Anything, "user-1", "tenant-a").Return(&domain.DeveloperRequest{
|
developerSvc.On("GetRequestStatus", mock.Anything, "user-1", "tenant-a").Return(&domain.DeveloperAccessStatus{
|
||||||
UserID: "user-1",
|
Status: domain.DeveloperRequestStatusApproved,
|
||||||
TenantID: "tenant-a",
|
|
||||||
Status: domain.DeveloperRequestStatusApproved,
|
|
||||||
}, nil).Maybe()
|
}, nil).Maybe()
|
||||||
|
|
||||||
h := &DevHandler{
|
h := &DevHandler{
|
||||||
|
|||||||
91
backend/internal/handler/test_server_helper_test.go
Normal file
91
backend/internal/handler/test_server_helper_test.go
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newIPv4TestServer(t *testing.T, handler http.Handler) *httptest.Server {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
ln, err := net.Listen("tcp4", "127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to bind test server listener: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
server := httptest.NewUnstartedServer(handler)
|
||||||
|
server.Listener = ln
|
||||||
|
server.Start()
|
||||||
|
t.Cleanup(server.Close)
|
||||||
|
|
||||||
|
return server
|
||||||
|
}
|
||||||
|
|
||||||
|
func newJWKSHTTPClient(t *testing.T, jwksBody []byte) *http.Client {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
return &http.Client{
|
||||||
|
Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) {
|
||||||
|
if r.URL.Path == "/.well-known/jwks.json" {
|
||||||
|
return httpResponse(r, http.StatusOK, string(jwksBody)), nil
|
||||||
|
}
|
||||||
|
return httpResponse(r, http.StatusNotFound, "not found"), nil
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func installKratosWhoamiClient(t *testing.T, identityID string) string {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
origDefaultClient := http.DefaultClient
|
||||||
|
http.DefaultClient = &http.Client{
|
||||||
|
Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) {
|
||||||
|
if r.URL.Path != "/sessions/whoami" {
|
||||||
|
return httpResponse(r, http.StatusNotFound, "not found"), nil
|
||||||
|
}
|
||||||
|
if r.Header.Get("Cookie") == "" && r.Header.Get("X-Session-Token") == "" {
|
||||||
|
return httpResponse(r, http.StatusUnauthorized, "missing session"), nil
|
||||||
|
}
|
||||||
|
body, err := json.Marshal(map[string]any{
|
||||||
|
"id": "session-123",
|
||||||
|
"authenticated_at": "2026-05-21T00:00:00Z",
|
||||||
|
"identity": map[string]any{
|
||||||
|
"id": identityID,
|
||||||
|
"traits": map[string]any{
|
||||||
|
"email": "user@example.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
resp := httpResponse(r, http.StatusOK, string(body))
|
||||||
|
resp.Header.Set("Content-Type", "application/json")
|
||||||
|
return resp, nil
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
t.Cleanup(func() {
|
||||||
|
http.DefaultClient = origDefaultClient
|
||||||
|
})
|
||||||
|
|
||||||
|
return "http://kratos.test"
|
||||||
|
}
|
||||||
|
|
||||||
|
func jwksURL() string {
|
||||||
|
u := &url.URL{Scheme: "http", Host: "jwks.test", Path: "/.well-known/jwks.json"}
|
||||||
|
return u.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustJSONBody(t *testing.T, value any) []byte {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
body, err := json.Marshal(value)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to marshal test body: %v", err)
|
||||||
|
}
|
||||||
|
return body
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user