1
0
forked from baron/baron-sso

권한 동의 거부 api 구현 및 hydra 연동

This commit is contained in:
2026-02-03 15:03:06 +09:00
parent a6836cad09
commit 4e068f76a7
3 changed files with 85 additions and 72 deletions

View File

@@ -491,8 +491,10 @@ func main() {
auth.Post("/login/code/verify", authHandler.VerifyLoginCode) auth.Post("/login/code/verify", authHandler.VerifyLoginCode)
auth.Post("/login/code/verify-short", authHandler.VerifyLoginShortCode) auth.Post("/login/code/verify-short", authHandler.VerifyLoginShortCode)
auth.Post("/password/login", authHandler.PasswordLogin) auth.Post("/password/login", authHandler.PasswordLogin)
auth.Get("/consent", authHandler.GetConsentRequest) auth.Get("/consent", authHandler.GetConsentRequest)
auth.Post("/consent/accept", authHandler.AcceptConsentRequest) auth.Post("/consent/accept", authHandler.AcceptConsentRequest)
auth.Post("/consent/reject", authHandler.RejectConsentRequest)
auth.Post("/oidc/login/accept", authHandler.AcceptOidcLoginRequest) auth.Post("/oidc/login/accept", authHandler.AcceptOidcLoginRequest)
auth.Post("/enchanted-link/init", authHandler.InitEnchantedLink) auth.Post("/enchanted-link/init", authHandler.InitEnchantedLink)

View File

@@ -3359,104 +3359,50 @@ func (h *AuthHandler) GetConsentRequest(c *fiber.Ctx) error {
return c.JSON(response) return c.JSON(response)
} }
// AcceptConsentRequest - 프론트엔드에서 동의한 내용을 바탕으로 Hydra에 승인을 요청합니다.
func (h *AuthHandler) AcceptConsentRequest(c *fiber.Ctx) error { func (h *AuthHandler) AcceptConsentRequest(c *fiber.Ctx) error {
var req struct { var req struct {
ConsentChallenge string `json:"consent_challenge"` ConsentChallenge string `json:"consent_challenge"`
GrantScope []string `json:"grant_scope"` // 사용자가 선택한 스코프 GrantScope []string `json:"grant_scope"` // 사용자가 선택한 스코프
} }
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
}
if reqJson, err := json.Marshal(req); err == nil {
slog.Info("AcceptConsentRequest: received request body", "body", string(reqJson))
} else {
slog.Error("AcceptConsentRequest: failed to marshal request for logging", "error", err)
}
if req.ConsentChallenge == "" {
return fiber.NewError(fiber.StatusBadRequest, "consent_challenge is required")
} }
if reqJson, err := json.Marshal(req); err == nil {
slog.Info("AcceptConsentRequest: received request body", "body", string(reqJson))
} else {
slog.Error("AcceptConsentRequest: failed to marshal request for logging", "error", err)
}
if req.ConsentChallenge == "" {
return fiber.NewError(fiber.StatusBadRequest, "consent_challenge is required")
}
// 1. Hydra에서 원래 요청 정보 조회 // 1. Hydra에서 원래 요청 정보 조회
consentRequest, err := h.Hydra.GetConsentRequest(c.Context(), req.ConsentChallenge) consentRequest, err := h.Hydra.GetConsentRequest(c.Context(), req.ConsentChallenge)
if err != nil { if err != nil {
slog.Error("failed to get hydra consent request before accepting", "error", err) slog.Error("failed to get hydra consent request before accepting", "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get consent information") return fiber.NewError(fiber.StatusInternalServerError, "Failed to get consent information")
} }
// 2. 스코프 필터링 (사용자가 선택한 것만 허용) // 2. 스코프 필터링 (사용자가 선택한 것만 허용)
// 만약 프론트엔드에서 grant_scope를 보내지 않았다면(기존 동작), 전체 허용으로 간주하거나 에러 처리.
// 여기서는 명시적으로 보낸 경우에만 필터링하고, 없으면 다 승인(하위 호환)하도록 함.
if len(req.GrantScope) > 0 { if len(req.GrantScope) > 0 {
// 유효성 검증: 사용자가 선택한 스코프가 실제로 요청된 스코프에 포함되는지 확인
allowedScopes := make(map[string]bool) allowedScopes := make(map[string]bool)
for _, s := range consentRequest.RequestedScope { for _, s := range consentRequest.RequestedScope {
allowedScopes[s] = true allowedScopes[s] = true
} }
filteredScopes := make([]string, 0, len(req.GrantScope)) filteredScopes := make([]string, 0, len(req.GrantScope))
for _, s := range req.GrantScope { for _, s := range req.GrantScope {
if allowedScopes[s] { if allowedScopes[s] {
filteredScopes = append(filteredScopes, s) filteredScopes = append(filteredScopes, s)
} }
} }
// 덮어씌우기 (Hydra 서비스는 이 필드를 grant_scope로 사용함)
consentRequest.RequestedScope = filteredScopes consentRequest.RequestedScope = filteredScopes
} }
// 3. Hydra에 승인 요청 // 3. Hydra에 승인 요청
if consentRequest.Subject == "" { if consentRequest.Subject == "" {
return fiber.NewError(fiber.StatusInternalServerError, "Consent subject missing") return fiber.NewError(fiber.StatusInternalServerError, "Consent subject missing")
} }
@@ -3475,19 +3421,38 @@ func (h *AuthHandler) AcceptConsentRequest(c *fiber.Ctx) error {
acceptResp, err := h.Hydra.AcceptConsentRequest(c.Context(), req.ConsentChallenge, consentRequest, sessionClaims) acceptResp, err := h.Hydra.AcceptConsentRequest(c.Context(), req.ConsentChallenge, consentRequest, sessionClaims)
if err != nil { if err != nil {
slog.Error("failed to accept hydra consent request", "error", err) slog.Error("failed to accept hydra consent request", "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to accept consent request") return fiber.NewError(fiber.StatusInternalServerError, "Failed to accept consent request")
} }
return c.JSON(acceptResp) return c.JSON(acceptResp)
} }
func (h *AuthHandler) RejectConsentRequest(c *fiber.Ctx) error {
var req struct {
ConsentChallenge string `json:"consent_challenge"`
}
if err := c.BodyParser(&req); err != nil {
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
}
if req.ConsentChallenge == "" {
return fiber.NewError(fiber.StatusBadRequest, "consent_challenge is required")
}
slog.Info("RejectConsentRequest called", "challenge", req.ConsentChallenge)
rejectResp, err := h.Hydra.RejectConsentRequest(c.Context(), req.ConsentChallenge)
if err != nil {
slog.Error("failed to reject hydra consent request", "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to reject consent request")
}
return c.JSON(rejectResp)
}
func (h *AuthHandler) AcceptOidcLoginRequest(c *fiber.Ctx) error { func (h *AuthHandler) AcceptOidcLoginRequest(c *fiber.Ctx) error {
var req struct { var req struct {
LoginChallenge string `json:"login_challenge"` LoginChallenge string `json:"login_challenge"`

View File

@@ -377,7 +377,11 @@ type AcceptConsentRequestResponse struct {
RedirectTo string `json:"redirectTo"` RedirectTo string `json:"redirectTo"`
} }
func (s *HydraAdminService) GetConsentRequest(ctx context.Context, challenge string) (*domain.HydraConsentRequest, error) { type RejectConsentRequestResponse struct {
RedirectTo string `json:"redirectTo"`
}
func (s *HydraAdminService) GetConsentRequest(ctx context.Context, challenge string) (*HydraConsentRequest, error) {
params := map[string]string{ params := map[string]string{
"consent_challenge": challenge, "consent_challenge": challenge,
} }
@@ -410,6 +414,48 @@ func (s *HydraAdminService) GetConsentRequest(ctx context.Context, challenge str
return &consentReq, nil return &consentReq, nil
} }
func (s *HydraAdminService) RejectConsentRequest(ctx context.Context, challenge string) (*RejectConsentRequestResponse, error) {
params := map[string]string{
"consent_challenge": challenge,
}
endpoint, err := s.buildURLWithParams("/oauth2/auth/requests/consent/reject", params)
if err != nil {
return nil, err
}
payload := map[string]interface{}{
"error": "access_denied",
"error_description": "The user decided to reject the consent request.",
}
body, _ := json.Marshal(payload)
req, err := http.NewRequestWithContext(ctx, "PUT", endpoint, bytes.NewReader(body))
if err != nil {
return nil, fmt.Errorf("hydra admin: create request for reject consent failed: %w", err)
}
req.Header.Set("Content-Type", "application/json")
resp, err := s.httpClient().Do(req)
if err != nil {
return nil, fmt.Errorf("hydra admin: reject consent request failed: %w", err)
}
defer resp.Body.Close()
respBody, _ := io.ReadAll(resp.Body)
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("hydra admin: reject consent failed status=%d body=%s", resp.StatusCode, string(respBody))
}
var hydraResp struct {
RedirectTo string `json:"redirect_to"`
}
if err := json.Unmarshal(respBody, &hydraResp); err != nil {
return nil, fmt.Errorf("hydra admin: decode reject consent response failed: %w", err)
}
return &RejectConsentRequestResponse{RedirectTo: hydraResp.RedirectTo}, nil
}
func (s *HydraAdminService) GetLoginRequest(ctx context.Context, challenge string) (*HydraLoginRequest, error) { func (s *HydraAdminService) GetLoginRequest(ctx context.Context, challenge string) (*HydraLoginRequest, error) {
params := map[string]string{ params := map[string]string{
"login_challenge": challenge, "login_challenge": challenge,