1
0
forked from baron/baron-sso

세션 카드 디버그용 시나리오 및 테스트 추가

This commit is contained in:
2026-04-06 11:15:48 +09:00
parent 6b115799c3
commit fe70fd216b
3 changed files with 595 additions and 0 deletions

View File

@@ -7,6 +7,7 @@ package handler
import (
"baron-sso-backend/internal/domain"
"baron-sso-backend/internal/middleware"
"baron-sso-backend/internal/service"
"bytes"
"context"
@@ -637,6 +638,156 @@ func TestPasswordLogin_OIDC_Success(t *testing.T) {
}
}
func TestPasswordLogin_OIDC_AuditIncludesClientMetadata(t *testing.T) {
mockIdp := new(MockIdentityProvider)
mockIdp.On("SignIn", "user@example.com", "password").Return(&domain.AuthInfo{
SessionToken: &domain.Token{JWT: "valid-jwt", SessionID: "session-123"},
Subject: "kratos-identity-id",
}, nil)
hydraHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch {
case strings.Contains(r.URL.Path, "/oauth2/auth/requests/login") && r.Method == http.MethodGet:
json.NewEncoder(w).Encode(domain.HydraLoginRequest{
Challenge: "challenge-123",
Client: domain.HydraClient{
ClientID: "devfront",
ClientName: "DevFront",
Metadata: map[string]interface{}{"status": "active"},
},
})
case strings.Contains(r.URL.Path, "/oauth2/auth/requests/login/accept") && r.Method == http.MethodPut:
json.NewEncoder(w).Encode(map[string]string{"redirect_to": "http://rp/cb"})
default:
http.NotFound(w, r)
}
})
mockKratos := new(MockKratosAdminService)
mockKratos.On("FindIdentityIDByIdentifier", mock.Anything, "user@example.com").Return("kratos-identity-id", nil)
auditRepo := &mockAuditRepo{}
h := &AuthHandler{
IdpProvider: mockIdp,
KratosAdmin: mockKratos,
AuditRepo: auditRepo,
Hydra: &service.HydraAdminService{
AdminURL: "http://hydra.test",
HTTPClient: &http.Client{Transport: mockHydraTransport(hydraHandler)},
},
}
app := fiber.New()
app.Use(middleware.AuditMiddleware(middleware.AuditConfig{
Repo: auditRepo,
BodyDump: true,
}))
app.Post("/api/v1/auth/password/login", h.PasswordLogin)
body, _ := json.Marshal(map[string]string{
"loginId": "user@example.com",
"password": "password",
"login_challenge": "challenge-123",
})
req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/password/login", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
resp, err := app.Test(req)
if err != nil {
t.Fatalf("request failed: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
bodyBytes, _ := io.ReadAll(resp.Body)
t.Fatalf("expected 200, got %d, body: %s", resp.StatusCode, string(bodyBytes))
}
if len(auditRepo.logs) != 1 {
t.Fatalf("expected 1 audit log, got %d", len(auditRepo.logs))
}
log := auditRepo.logs[0]
if log.EventType != "POST /api/v1/auth/password/login" {
t.Fatalf("expected password login audit event, got %q", log.EventType)
}
if log.UserID != "kratos-identity-id" {
t.Fatalf("expected audit user_id kratos-identity-id, got %q", log.UserID)
}
details, err := parseAuditDetails(log.Details)
if err != nil {
t.Fatalf("failed to parse audit details: %v", err)
}
if got, _ := details["client_id"].(string); got != "devfront" {
t.Fatalf("expected client_id devfront, got %v", details["client_id"])
}
if got, _ := details["client_name"].(string); got != "DevFront" {
t.Fatalf("expected client_name DevFront, got %v", details["client_name"])
}
}
func TestPasswordLogin_UserFront_AuditIncludesDefaultClientMetadata(t *testing.T) {
mockIdp := new(MockIdentityProvider)
mockIdp.On("SignIn", "user@example.com", "password").Return(&domain.AuthInfo{
SessionToken: &domain.Token{JWT: "valid-jwt", SessionID: "session-123"},
Subject: "kratos-identity-id",
}, nil)
mockKratos := new(MockKratosAdminService)
mockKratos.On("FindIdentityIDByIdentifier", mock.Anything, "user@example.com").Return("kratos-identity-id", nil)
auditRepo := &mockAuditRepo{}
h := &AuthHandler{
IdpProvider: mockIdp,
KratosAdmin: mockKratos,
AuditRepo: auditRepo,
}
app := fiber.New()
app.Use(middleware.AuditMiddleware(middleware.AuditConfig{
Repo: auditRepo,
BodyDump: true,
}))
app.Post("/api/v1/auth/password/login", h.PasswordLogin)
body, _ := json.Marshal(map[string]string{
"loginId": "user@example.com",
"password": "password",
})
req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/password/login", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
resp, err := app.Test(req)
if err != nil {
t.Fatalf("request failed: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
bodyBytes, _ := io.ReadAll(resp.Body)
t.Fatalf("expected 200, got %d, body: %s", resp.StatusCode, string(bodyBytes))
}
if len(auditRepo.logs) != 1 {
t.Fatalf("expected 1 audit log, got %d", len(auditRepo.logs))
}
if auditRepo.logs[0].UserID != "kratos-identity-id" {
t.Fatalf("expected audit user_id kratos-identity-id, got %q", auditRepo.logs[0].UserID)
}
details, err := parseAuditDetails(auditRepo.logs[0].Details)
if err != nil {
t.Fatalf("failed to parse audit details: %v", err)
}
if got, _ := details["client_id"].(string); got != "userfront" {
t.Fatalf("expected client_id userfront, got %v", details["client_id"])
}
if got, _ := details["client_name"].(string); got != "UserFront" {
t.Fatalf("expected client_name UserFront, got %v", details["client_name"])
}
}
func TestHeadlessPasswordLogin_HeadlessLoginClientSuccess(t *testing.T) {
mockIdp := new(MockIdentityProvider)
mockIdp.On("SignIn", "employee001", "password").Return(&domain.AuthInfo{