diff --git a/backend/cmd/server/main.go b/backend/cmd/server/main.go index 4e8fabf7..5978a0d0 100644 --- a/backend/cmd/server/main.go +++ b/backend/cmd/server/main.go @@ -489,6 +489,7 @@ func main() { // Auth Proxy Routes auth := api.Group("/auth") + auth.All("/oidc/*", authHandler.ProxyOidc) auth.Post("/enchanted-link/init", authHandler.InitEnchantedLink) auth.Post("/enchanted-link/poll", authHandler.PollEnchantedLink) auth.Post("/magic-link/verify", authHandler.VerifyMagicLink) diff --git a/backend/internal/handler/auth_handler.go b/backend/internal/handler/auth_handler.go index 5903c80a..efbc5676 100644 --- a/backend/internal/handler/auth_handler.go +++ b/backend/internal/handler/auth_handler.go @@ -1526,7 +1526,7 @@ func (h *AuthHandler) PasswordLogin(c *fiber.Ctx) error { loginID := strings.TrimSpace(req.LoginID) ale.LoginIDs["loginId"] = req.LoginID // 원문 ale.LoginIDs["loginId_normalized"] = loginID - ale.NewPassword = req.Password // For test only, logging password (sensitive) + // ale.NewPassword = req.Password // For test only, logging password (sensitive) ale.Log(slog.LevelInfo, "Attempting to login") @@ -1568,22 +1568,25 @@ func (h *AuthHandler) PasswordLogin(c *fiber.Ctx) error { // --- OIDC 로그인 흐름 처리 --- if req.LoginChallenge != "" { - slog.Info("OIDC login flow detected", "challenge", req.LoginChallenge) + slog.Info("OIDC login flow detected", "challenge", req.LoginChallenge, "subject", subject) // Check if the client is active loginReq, err := h.Hydra.GetLoginRequest(c.Context(), req.LoginChallenge) - if err == nil && loginReq != nil && loginReq.Client.Metadata != nil { - if status, ok := loginReq.Client.Metadata["status"].(string); ok { - if strings.ToLower(status) == "inactive" { - slog.Warn("Login rejected for inactive client in PasswordLogin", "client_id", loginReq.Client.ClientID) - return fiber.NewError(fiber.StatusForbidden, "The client application is disabled.") + if err == nil && loginReq != nil { + slog.Info("OIDC Client Info", "client_id", loginReq.Client.ClientID, "name", loginReq.Client.ClientName) + if loginReq.Client.Metadata != nil { + if status, ok := loginReq.Client.Metadata["status"].(string); ok { + if strings.ToLower(status) == "inactive" { + slog.Warn("Login rejected for inactive client in PasswordLogin", "client_id", loginReq.Client.ClientID) + return fiber.NewError(fiber.StatusForbidden, "The client application is disabled.") + } } } } acceptResp, err := h.Hydra.AcceptLoginRequest(c.Context(), req.LoginChallenge, subject) if err != nil { - slog.Error("failed to accept hydra login request", "error", err) + slog.Error("failed to accept hydra login request", "error", err, "challenge", req.LoginChallenge) return fiber.NewError(fiber.StatusInternalServerError, "Failed to accept OIDC login request") } slog.Info("Hydra login request accepted", "redirectTo", acceptResp.RedirectTo) @@ -2480,7 +2483,56 @@ func (h *AuthHandler) formatPhoneForStorage(phone string) string { return phone } -// GetMe - Returns current user's profile with enriched data from local DB +// ProxyOidc - 프론트엔드의 OIDC 요청을 내부 Hydra 서비스로 프록시합니다. +func (h *AuthHandler) ProxyOidc(c *fiber.Ctx) error { + path := c.Params("*") + // [Strict] Always use internal Docker network address for proxying to avoid external loops + targetURL := "http://hydra:4444" + + // 프록시 URL 구성 + u, err := url.Parse(targetURL) + if err != nil { + return fiber.NewError(fiber.StatusInternalServerError, "invalid hydra public url") + } + u.Path = strings.TrimRight(u.Path, "/") + "/" + path + u.RawQuery = string(c.Request().URI().QueryString()) + + slog.Debug("Proxying OIDC request", "from", c.Path(), "to", u.String()) + + // 요청 준비 + req, err := http.NewRequestWithContext(c.Context(), c.Method(), u.String(), bytes.NewReader(c.Body())) + if err != nil { + return fiber.NewError(fiber.StatusInternalServerError, "failed to create proxy request") + } + + // 헤더 복사 + c.Request().Header.VisitAll(func(key, value []byte) { + k := string(key) + if k != "Host" && k != "Connection" { + req.Header.Add(k, string(value)) + } + }) + + // 요청 실행 (Hydra 내부 HttpClient 사용) + resp, err := h.Hydra.HttpClient().Do(req) + if err != nil { + return fiber.NewError(fiber.StatusServiceUnavailable, "hydra public api unavailable") + } + defer resp.Body.Close() + + // 응답 헤더 복사 + for k, values := range resp.Header { + for _, v := range values { + c.Set(k, v) + } + } + + // 상태 코드 및 바디 설정 + c.Status(resp.StatusCode) + _, err = io.Copy(c.Response().BodyWriter(), resp.Body) + return err +} + func (h *AuthHandler) GetMe(c *fiber.Ctx) error { profile, err := h.resolveCurrentProfile(c) if err != nil { diff --git a/backend/internal/service/hydra_admin_service.go b/backend/internal/service/hydra_admin_service.go index ef1e0911..df497204 100644 --- a/backend/internal/service/hydra_admin_service.go +++ b/backend/internal/service/hydra_admin_service.go @@ -47,7 +47,7 @@ func (s *HydraAdminService) ListClients(ctx context.Context, limit, offset int) return nil, err } - resp, err := s.httpClient().Do(req) + resp, err := s.HttpClient().Do(req) if err != nil { return nil, err } @@ -75,7 +75,7 @@ func (s *HydraAdminService) GetClient(ctx context.Context, clientID string) (*do return nil, err } - resp, err := s.httpClient().Do(req) + resp, err := s.HttpClient().Do(req) if err != nil { return nil, err } @@ -114,7 +114,7 @@ func (s *HydraAdminService) PatchClientStatus(ctx context.Context, clientID, sta } req.Header.Set("Content-Type", "application/json-patch+json") - resp, err := s.httpClient().Do(req) + resp, err := s.HttpClient().Do(req) if err != nil { return nil, err } @@ -145,7 +145,7 @@ func (s *HydraAdminService) CreateClient(ctx context.Context, client domain.Hydr } req.Header.Set("Content-Type", "application/json") - resp, err := s.httpClient().Do(req) + resp, err := s.HttpClient().Do(req) if err != nil { return nil, err } @@ -174,7 +174,7 @@ func (s *HydraAdminService) UpdateClient(ctx context.Context, clientID string, c } req.Header.Set("Content-Type", "application/json") - resp, err := s.httpClient().Do(req) + resp, err := s.HttpClient().Do(req) if err != nil { return nil, err } @@ -202,7 +202,7 @@ func (s *HydraAdminService) DeleteClient(ctx context.Context, clientID string) e return err } - resp, err := s.httpClient().Do(req) + resp, err := s.HttpClient().Do(req) if err != nil { return err } @@ -235,7 +235,7 @@ func (s *HydraAdminService) ListConsentSessions(ctx context.Context, subject, cl return nil, err } - resp, err := s.httpClient().Do(req) + resp, err := s.HttpClient().Do(req) if err != nil { return nil, err } @@ -276,7 +276,7 @@ func (s *HydraAdminService) RevokeConsentSessions(ctx context.Context, subject, return err } - resp, err := s.httpClient().Do(req) + resp, err := s.HttpClient().Do(req) if err != nil { return err } @@ -289,7 +289,7 @@ func (s *HydraAdminService) RevokeConsentSessions(ctx context.Context, subject, return nil } -func (s *HydraAdminService) httpClient() *http.Client { +func (s *HydraAdminService) HttpClient() *http.Client { if s.HTTPClient != nil { return s.HTTPClient } @@ -367,7 +367,7 @@ func (s *HydraAdminService) GetConsentRequest(ctx context.Context, challenge str return nil, fmt.Errorf("hydra admin: create request for get consent failed: %w", err) } - resp, err := s.httpClient().Do(req) + resp, err := s.HttpClient().Do(req) if err != nil { return nil, fmt.Errorf("hydra admin: get consent request failed: %w", err) } @@ -407,7 +407,7 @@ func (s *HydraAdminService) RejectConsentRequest(ctx context.Context, challenge } req.Header.Set("Content-Type", "application/json") - resp, err := s.httpClient().Do(req) + resp, err := s.HttpClient().Do(req) if err != nil { return nil, fmt.Errorf("hydra admin: reject consent request failed: %w", err) } @@ -449,7 +449,7 @@ func (s *HydraAdminService) RejectLoginRequest(ctx context.Context, challenge, e } req.Header.Set("Content-Type", "application/json") - resp, err := s.httpClient().Do(req) + resp, err := s.HttpClient().Do(req) if err != nil { return nil, fmt.Errorf("hydra admin: reject login request failed: %w", err) } @@ -484,7 +484,7 @@ func (s *HydraAdminService) GetLoginRequest(ctx context.Context, challenge strin return nil, fmt.Errorf("hydra admin: create request for get login failed: %w", err) } - resp, err := s.httpClient().Do(req) + resp, err := s.HttpClient().Do(req) if err != nil { return nil, fmt.Errorf("hydra admin: get login request failed: %w", err) } @@ -532,7 +532,7 @@ func (s *HydraAdminService) AcceptConsentRequest(ctx context.Context, challenge } req.Header.Set("Content-Type", "application/json") - resp, err := s.httpClient().Do(req) + resp, err := s.HttpClient().Do(req) if err != nil { return nil, fmt.Errorf("hydra admin: accept consent request failed: %w", err) } @@ -576,7 +576,7 @@ func (s *HydraAdminService) AcceptLoginRequest(ctx context.Context, challenge st } req.Header.Set("Content-Type", "application/json") - resp, err := s.httpClient().Do(req) + resp, err := s.HttpClient().Do(req) if err != nil { return nil, fmt.Errorf("hydra admin: accept login request failed: %w", err) }