forked from baron/baron-sso
애플리케이션(RP) 관리 기능 구현 및 Ory Keto 권한 연동
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"baron-sso-backend/internal/domain"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
@@ -73,7 +74,7 @@ func NewHydraAdminService() *HydraAdminService {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HydraAdminService) ListClients(ctx context.Context, limit, offset int) ([]HydraClient, error) {
|
||||
func (s *HydraAdminService) ListClients(ctx context.Context, limit, offset int) ([]domain.HydraClient, error) {
|
||||
endpoint, err := s.buildURL("/clients", map[string]int{
|
||||
"limit": limit,
|
||||
"offset": offset,
|
||||
@@ -101,14 +102,14 @@ func (s *HydraAdminService) ListClients(ctx context.Context, limit, offset int)
|
||||
return nil, fmt.Errorf("hydra admin: list clients failed status=%d body=%s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
var clients []HydraClient
|
||||
var clients []domain.HydraClient
|
||||
if err := json.NewDecoder(resp.Body).Decode(&clients); err != nil {
|
||||
return nil, fmt.Errorf("hydra admin: decode clients failed: %w", err)
|
||||
}
|
||||
return clients, nil
|
||||
}
|
||||
|
||||
func (s *HydraAdminService) GetClient(ctx context.Context, clientID string) (*HydraClient, error) {
|
||||
func (s *HydraAdminService) GetClient(ctx context.Context, clientID string) (*domain.HydraClient, error) {
|
||||
endpoint := fmt.Sprintf("%s/clients/%s", strings.TrimRight(s.AdminURL, "/"), url.PathEscape(clientID))
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
|
||||
if err != nil {
|
||||
@@ -129,14 +130,14 @@ func (s *HydraAdminService) GetClient(ctx context.Context, clientID string) (*Hy
|
||||
return nil, fmt.Errorf("hydra admin: get client failed status=%d body=%s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
var client HydraClient
|
||||
var client domain.HydraClient
|
||||
if err := json.NewDecoder(resp.Body).Decode(&client); err != nil {
|
||||
return nil, fmt.Errorf("hydra admin: decode client failed: %w", err)
|
||||
}
|
||||
return &client, nil
|
||||
}
|
||||
|
||||
func (s *HydraAdminService) PatchClientStatus(ctx context.Context, clientID, status string) (*HydraClient, error) {
|
||||
func (s *HydraAdminService) PatchClientStatus(ctx context.Context, clientID, status string) (*domain.HydraClient, error) {
|
||||
payload := map[string]interface{}{
|
||||
"metadata": map[string]interface{}{
|
||||
"status": status,
|
||||
@@ -165,14 +166,14 @@ func (s *HydraAdminService) PatchClientStatus(ctx context.Context, clientID, sta
|
||||
return nil, fmt.Errorf("hydra admin: patch client failed status=%d body=%s", resp.StatusCode, string(respBody))
|
||||
}
|
||||
|
||||
var updated HydraClient
|
||||
var updated domain.HydraClient
|
||||
if err := json.NewDecoder(resp.Body).Decode(&updated); err != nil {
|
||||
return nil, fmt.Errorf("hydra admin: decode patched client failed: %w", err)
|
||||
}
|
||||
return &updated, nil
|
||||
}
|
||||
|
||||
func (s *HydraAdminService) CreateClient(ctx context.Context, client HydraClient) (*HydraClient, error) {
|
||||
func (s *HydraAdminService) CreateClient(ctx context.Context, client domain.HydraClient) (*domain.HydraClient, error) {
|
||||
body, _ := json.Marshal(client)
|
||||
endpoint := fmt.Sprintf("%s/clients", strings.TrimRight(s.AdminURL, "/"))
|
||||
|
||||
@@ -193,14 +194,14 @@ func (s *HydraAdminService) CreateClient(ctx context.Context, client HydraClient
|
||||
return nil, fmt.Errorf("hydra admin: create client failed status=%d body=%s", resp.StatusCode, string(respBody))
|
||||
}
|
||||
|
||||
var created HydraClient
|
||||
var created domain.HydraClient
|
||||
if err := json.NewDecoder(resp.Body).Decode(&created); err != nil {
|
||||
return nil, fmt.Errorf("hydra admin: decode created client failed: %w", err)
|
||||
}
|
||||
return &created, nil
|
||||
}
|
||||
|
||||
func (s *HydraAdminService) UpdateClient(ctx context.Context, clientID string, client HydraClient) (*HydraClient, error) {
|
||||
func (s *HydraAdminService) UpdateClient(ctx context.Context, clientID string, client domain.HydraClient) (*domain.HydraClient, error) {
|
||||
client.ClientID = clientID
|
||||
body, _ := json.Marshal(client)
|
||||
endpoint := fmt.Sprintf("%s/clients/%s", strings.TrimRight(s.AdminURL, "/"), url.PathEscape(clientID))
|
||||
@@ -225,7 +226,7 @@ func (s *HydraAdminService) UpdateClient(ctx context.Context, clientID string, c
|
||||
return nil, fmt.Errorf("hydra admin: update client failed status=%d body=%s", resp.StatusCode, string(respBody))
|
||||
}
|
||||
|
||||
var updated HydraClient
|
||||
var updated domain.HydraClient
|
||||
if err := json.NewDecoder(resp.Body).Decode(&updated); err != nil {
|
||||
return nil, fmt.Errorf("hydra admin: decode updated client failed: %w", err)
|
||||
}
|
||||
@@ -255,7 +256,7 @@ func (s *HydraAdminService) DeleteClient(ctx context.Context, clientID string) e
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *HydraAdminService) ListConsentSessions(ctx context.Context, subject, clientID string) ([]HydraConsentSession, error) {
|
||||
func (s *HydraAdminService) ListConsentSessions(ctx context.Context, subject, clientID string) ([]domain.HydraConsentSession, error) {
|
||||
params := map[string]string{
|
||||
"subject": subject,
|
||||
}
|
||||
@@ -283,7 +284,7 @@ func (s *HydraAdminService) ListConsentSessions(ctx context.Context, subject, cl
|
||||
return nil, fmt.Errorf("hydra admin: list consent sessions failed status=%d body=%s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
var sessions []HydraConsentSession
|
||||
var sessions []domain.HydraConsentSession
|
||||
if err := json.Unmarshal(body, &sessions); err != nil {
|
||||
return nil, fmt.Errorf("hydra admin: decode consent sessions failed: %w", err)
|
||||
}
|
||||
@@ -376,7 +377,7 @@ type AcceptConsentRequestResponse struct {
|
||||
RedirectTo string `json:"redirectTo"`
|
||||
}
|
||||
|
||||
func (s *HydraAdminService) GetConsentRequest(ctx context.Context, challenge string) (*HydraConsentRequest, error) {
|
||||
func (s *HydraAdminService) GetConsentRequest(ctx context.Context, challenge string) (*domain.HydraConsentRequest, error) {
|
||||
params := map[string]string{
|
||||
"consent_challenge": challenge,
|
||||
}
|
||||
@@ -401,7 +402,7 @@ func (s *HydraAdminService) GetConsentRequest(ctx context.Context, challenge str
|
||||
return nil, fmt.Errorf("hydra admin: get consent failed status=%d body=%s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
var consentReq HydraConsentRequest
|
||||
var consentReq domain.HydraConsentRequest
|
||||
if err := json.Unmarshal(body, &consentReq); err != nil {
|
||||
return nil, fmt.Errorf("hydra admin: decode get consent response failed: %w", err)
|
||||
}
|
||||
@@ -442,7 +443,7 @@ func (s *HydraAdminService) GetLoginRequest(ctx context.Context, challenge strin
|
||||
return &loginReq, nil
|
||||
}
|
||||
|
||||
func (s *HydraAdminService) AcceptConsentRequest(ctx context.Context, challenge string, grantInfo *HydraConsentRequest, sessionClaims map[string]any) (*AcceptConsentRequestResponse, error) {
|
||||
func (s *HydraAdminService) AcceptConsentRequest(ctx context.Context, challenge string, grantInfo *domain.HydraConsentRequest, sessionClaims map[string]any) (*AcceptConsentRequestResponse, error) {
|
||||
params := map[string]string{
|
||||
"consent_challenge": challenge,
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
type KetoService interface {
|
||||
@@ -87,22 +88,34 @@ func (s *ketoService) CreateRelation(ctx context.Context, namespace, object, rel
|
||||
}
|
||||
body, _ := json.Marshal(payload)
|
||||
|
||||
req, _ := http.NewRequestWithContext(ctx, "PUT", u, bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
// Exponential Backoff Retry Logic
|
||||
var lastErr error
|
||||
maxRetries := 3
|
||||
backoff := 100 * time.Millisecond
|
||||
|
||||
resp, err := s.client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
for i := 0; i < maxRetries; i++ {
|
||||
req, _ := http.NewRequestWithContext(ctx, "PUT", u, bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusNoContent && resp.StatusCode != http.StatusOK {
|
||||
resBody, _ := io.ReadAll(resp.Body)
|
||||
return fmt.Errorf("keto returned status %d: %s", resp.StatusCode, string(resBody))
|
||||
resp, err := s.client.Do(req)
|
||||
if err == nil {
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode == http.StatusCreated || resp.StatusCode == http.StatusNoContent || resp.StatusCode == http.StatusOK {
|
||||
slog.Info("Keto relation created", "namespace", namespace, "object", object, "relation", relation, "subject", subject)
|
||||
return nil
|
||||
}
|
||||
resBody, _ := io.ReadAll(resp.Body)
|
||||
lastErr = fmt.Errorf("keto returned status %d: %s", resp.StatusCode, string(resBody))
|
||||
} else {
|
||||
lastErr = err
|
||||
}
|
||||
|
||||
time.Sleep(backoff)
|
||||
backoff *= 2
|
||||
}
|
||||
|
||||
slog.Info("Keto relation created", "namespace", namespace, "object", object, "relation", relation, "subject", subject)
|
||||
return nil
|
||||
slog.Error("Keto create relation failed after retries", "error", lastErr)
|
||||
return lastErr
|
||||
}
|
||||
|
||||
func (s *ketoService) DeleteRelation(ctx context.Context, namespace, object, relation, subject string) error {
|
||||
|
||||
Reference in New Issue
Block a user