1
0
forked from baron/baron-sso

Implement tenant import and RP auto login policies

This commit is contained in:
2026-04-30 15:45:34 +09:00
parent 24807eab0f
commit f7e4d43b16
76 changed files with 5307 additions and 441 deletions

View File

@@ -85,20 +85,21 @@ const (
)
type AuthHandler struct {
SmsService domain.SmsService
EmailService domain.EmailService
RedisService domain.RedisRepository
HeadlessJWKS *service.HeadlessJWKSCacheService
KratosAdmin service.KratosAdminService
IdpProvider domain.IdentityProvider
AuditRepo domain.AuditRepository
OathkeeperRepo domain.OathkeeperLogRepository
Hydra *service.HydraAdminService
TenantService service.TenantService
KetoService service.KetoService
KetoOutboxRepo repository.KetoOutboxRepository
UserRepo repository.UserRepository
ConsentRepo repository.ClientConsentRepository
SmsService domain.SmsService
EmailService domain.EmailService
RedisService domain.RedisRepository
HeadlessJWKS *service.HeadlessJWKSCacheService
KratosAdmin service.KratosAdminService
IdpProvider domain.IdentityProvider
AuditRepo domain.AuditRepository
OathkeeperRepo domain.OathkeeperLogRepository
Hydra *service.HydraAdminService
TenantService service.TenantService
KetoService service.KetoService
KetoOutboxRepo repository.KetoOutboxRepository
UserRepo repository.UserRepository
ConsentRepo repository.ClientConsentRepository
RPUserMetadataRepo repository.RPUserMetadataRepository
}
type signupState struct {
@@ -1157,6 +1158,120 @@ func withOidcSessionMetadata(claims map[string]any, sessionID string) map[string
return claims
}
func (h *AuthHandler) withRPProfileClaims(ctx context.Context, claims map[string]any, client domain.HydraClient, subject string) map[string]any {
if claims == nil {
claims = map[string]any{}
}
if h == nil || h.RPUserMetadataRepo == nil {
return claims
}
clientID := strings.TrimSpace(client.ClientID)
subject = strings.TrimSpace(subject)
if clientID == "" || subject == "" {
return claims
}
claimKeys := extractClaimEnabledCustomUserSchemaKeys(client.Metadata)
if len(claimKeys) == 0 {
return claims
}
row, err := h.RPUserMetadataRepo.Get(ctx, clientID, subject)
if err != nil || row == nil || len(row.Metadata) == 0 {
return claims
}
fields := make(map[string]any)
for _, key := range claimKeys {
raw, ok := row.Metadata[key]
if !ok || raw == nil {
continue
}
if value, ok := raw.(string); ok {
value = strings.TrimSpace(value)
if value == "" {
continue
}
fields[key] = value
continue
}
fields[key] = raw
}
if len(fields) == 0 {
return claims
}
profile := map[string]any{
"client_id": clientID,
"fields": fields,
}
if existing, ok := claims["rp_profiles"].([]any); ok {
claims["rp_profiles"] = append(existing, profile)
return claims
}
if existing, ok := claims["rp_profiles"].([]interface{}); ok {
claims["rp_profiles"] = append(existing, profile)
return claims
}
claims["rp_profiles"] = []any{profile}
return claims
}
func extractClaimEnabledCustomUserSchemaKeys(metadata map[string]interface{}) []string {
if metadata == nil {
return nil
}
rawSchema, ok := metadata["customUserSchema"]
if !ok || rawSchema == nil {
return nil
}
var items []interface{}
switch schema := rawSchema.(type) {
case []interface{}:
items = schema
case []map[string]interface{}:
items = make([]interface{}, 0, len(schema))
for _, item := range schema {
items = append(items, item)
}
default:
return nil
}
keys := make([]string, 0, len(items))
seen := make(map[string]struct{})
for _, item := range items {
field, ok := item.(map[string]interface{})
if !ok {
if typed, typedOK := item.(map[string]any); typedOK {
field = typed
} else {
continue
}
}
enabled, _ := field["claimEnabled"].(bool)
if !enabled {
enabled, _ = field["claim_enabled"].(bool)
}
if !enabled {
continue
}
key, _ := field["key"].(string)
key = strings.TrimSpace(key)
if key == "" {
continue
}
if _, exists := seen[key]; exists {
continue
}
seen[key] = struct{}{}
keys = append(keys, key)
}
return keys
}
func collectEmailList(traits map[string]any, primaryEmail string) []string {
emails := make([]string, 0)
seen := make(map[string]struct{})
@@ -4792,6 +4907,8 @@ type linkedRpSummary struct {
Logo string `json:"logo,omitempty"`
URL string `json:"url,omitempty"`
InitURL string `json:"init_url,omitempty"`
AutoLoginSupported bool `json:"auto_login_supported"`
AutoLoginURL string `json:"auto_login_url,omitempty"`
LastAuthenticatedAt string `json:"lastAuthenticatedAt,omitempty"`
Status string `json:"status"`
Scopes []string `json:"scopes,omitempty"`
@@ -4872,19 +4989,23 @@ func (h *AuthHandler) ListLinkedRps(c *fiber.Ctx) error {
if len(scopes) == 0 && strings.TrimSpace(client.Scope) != "" {
scopes = strings.Fields(client.Scope)
}
initURL := resolveLinkedRPInitURL(client.ClientID, scopes, client.RedirectURIs)
autoLoginSupported := resolveLinkedRPAutoLoginSupported(client.ClientID, client.Metadata)
autoLoginURL := resolveLinkedRPAutoLoginURL(client.ClientID, client.Metadata)
initURL := resolveLinkedRPInitURL(client.ClientID, client.Metadata)
existing := records[clientID]
if existing == nil {
records[clientID] = &linkedRpRecord{
linkedRpSummary: linkedRpSummary{
ID: clientID,
Name: name,
Logo: extractHydraClientLogo(client.Metadata),
URL: clientURL,
InitURL: initURL,
Status: "active", // Hydra 세션이 있으면 활성
Scopes: scopes,
ID: clientID,
Name: name,
Logo: extractHydraClientLogo(client.Metadata),
URL: clientURL,
InitURL: initURL,
AutoLoginSupported: autoLoginSupported,
AutoLoginURL: autoLoginURL,
Status: "active", // Hydra 세션이 있으면 활성
Scopes: scopes,
},
lastAuth: lastAuth,
}
@@ -4903,6 +5024,12 @@ func (h *AuthHandler) ListLinkedRps(c *fiber.Ctx) error {
if existing.InitURL == "" {
existing.InitURL = initURL
}
if !existing.AutoLoginSupported {
existing.AutoLoginSupported = autoLoginSupported
}
if existing.AutoLoginURL == "" {
existing.AutoLoginURL = autoLoginURL
}
existing.Scopes = mergeScopes(existing.Scopes, scopes)
if lastAuth.After(existing.lastAuth) {
existing.lastAuth = lastAuth
@@ -4943,11 +5070,13 @@ func (h *AuthHandler) ListLinkedRps(c *fiber.Ctx) error {
)
}
if record.InitURL == "" {
record.InitURL = resolveLinkedRPInitURL(
client.ClientID,
record.Scopes,
client.RedirectURIs,
)
record.InitURL = resolveLinkedRPInitURL(client.ClientID, client.Metadata)
}
if !record.AutoLoginSupported {
record.AutoLoginSupported = resolveLinkedRPAutoLoginSupported(client.ClientID, client.Metadata)
}
if record.AutoLoginURL == "" {
record.AutoLoginURL = resolveLinkedRPAutoLoginURL(client.ClientID, client.Metadata)
}
}
@@ -4999,21 +5128,21 @@ func (h *AuthHandler) ListLinkedRps(c *fiber.Ctx) error {
client.ClientURI,
client.RedirectURIs,
)
initURL := resolveLinkedRPInitURL(
client.ClientID,
dc.GrantedScopes,
client.RedirectURIs,
)
autoLoginSupported := resolveLinkedRPAutoLoginSupported(client.ClientID, client.Metadata)
autoLoginURL := resolveLinkedRPAutoLoginURL(client.ClientID, client.Metadata)
initURL := resolveLinkedRPInitURL(client.ClientID, client.Metadata)
records[dc.ClientID] = &linkedRpRecord{
linkedRpSummary: linkedRpSummary{
ID: dc.ClientID,
Name: name,
Logo: extractHydraClientLogo(client.Metadata),
URL: clientURL,
InitURL: initURL,
Status: status,
Scopes: dc.GrantedScopes,
ID: dc.ClientID,
Name: name,
Logo: extractHydraClientLogo(client.Metadata),
URL: clientURL,
InitURL: initURL,
AutoLoginSupported: autoLoginSupported,
AutoLoginURL: autoLoginURL,
Status: status,
Scopes: dc.GrantedScopes,
},
lastAuth: dc.UpdatedAt,
}
@@ -5087,11 +5216,9 @@ func (h *AuthHandler) ListLinkedRps(c *fiber.Ctx) error {
}
}
record.URL = clientURL
record.InitURL = resolveLinkedRPInitURL(
client.ClientID,
scopes,
client.RedirectURIs,
)
record.InitURL = resolveLinkedRPInitURL(client.ClientID, client.Metadata)
record.AutoLoginSupported = resolveLinkedRPAutoLoginSupported(client.ClientID, client.Metadata)
record.AutoLoginURL = resolveLinkedRPAutoLoginURL(client.ClientID, client.Metadata)
} else {
// Hydra 정보 없음 (삭제됨 등) -> Audit 정보나 ID로 대체
if record.Name == "" {
@@ -5239,6 +5366,7 @@ func (h *AuthHandler) GetConsentRequest(c *fiber.Ctx) error {
buildOidcClaimsFromTraits(identity.Traits, consentRequest.RequestedScope, tenantID),
currentSessionID,
)
sessionClaims = h.withRPProfileClaims(c.Context(), sessionClaims, consentRequest.Client, consentRequest.Subject)
acceptResp, err := h.Hydra.AcceptConsentRequest(c.Context(), challenge, consentRequest, sessionClaims)
if err == nil {
return c.JSON(acceptResp)
@@ -5268,6 +5396,7 @@ func (h *AuthHandler) GetConsentRequest(c *fiber.Ctx) error {
buildOidcClaimsFromTraits(identity.Traits, consentRequest.RequestedScope, tenantID),
currentSessionID,
)
sessionClaims = h.withRPProfileClaims(c.Context(), sessionClaims, consentRequest.Client, consentRequest.Subject)
// [Debug] 실제 생성된 클레임 출력 (요청사항 확인용 - 자동 승인 시)
appEnv := strings.ToLower(os.Getenv("APP_ENV"))
@@ -5450,6 +5579,7 @@ func (h *AuthHandler) AcceptConsentRequest(c *fiber.Ctx) error {
buildOidcClaimsFromTraits(identity.Traits, consentRequest.RequestedScope, tenantID),
currentSessionID,
)
sessionClaims = h.withRPProfileClaims(c.Context(), sessionClaims, consentRequest.Client, consentRequest.Subject)
// [Debug] 실제 생성된 클레임 출력 (요청사항 확인용)
appEnv := strings.ToLower(os.Getenv("APP_ENV"))
@@ -7255,6 +7385,10 @@ func resolveLinkedRPURL(clientID string, clientURI string, redirectURIs []string
if value := strings.TrimSpace(os.Getenv("DEVFRONT_URL")); value != "" {
return value
}
case "orgfront":
if value := strings.TrimSpace(os.Getenv("ORGFRONT_URL")); value != "" {
return value
}
}
clientURL := strings.TrimSpace(clientURI)
@@ -7271,10 +7405,22 @@ func resolveLinkedRPURL(clientID string, clientURI string, redirectURIs []string
return ""
}
func resolveLinkedRPInitURL(clientID string, scopes []string, redirectURIs []string) string {
func resolveLinkedRPAutoLoginSupported(clientID string, metadata map[string]interface{}) bool {
if readMetadataBoolValue(metadata, domain.MetadataAutoLoginSupported) {
return true
}
switch strings.TrimSpace(clientID) {
case "adminfront", "devfront", "orgfront":
return resolveLinkedRPAutoLoginURL(clientID, nil) != ""
default:
return false
}
}
func resolveLinkedRPAutoLoginURL(clientID string, metadata map[string]interface{}) string {
clientID = strings.TrimSpace(clientID)
if clientID == "" {
return ""
if metadataURL := readMetadataStringValue(metadata, domain.MetadataAutoLoginURL); metadataURL != "" {
return metadataURL
}
switch clientID {
@@ -7286,8 +7432,23 @@ func resolveLinkedRPInitURL(clientID string, scopes []string, redirectURIs []str
if value := strings.TrimRight(strings.TrimSpace(os.Getenv("DEVFRONT_URL")), "/"); value != "" {
return value + "/login?auto=1&returnTo=%2Fclients"
}
case "orgfront":
if value := strings.TrimRight(strings.TrimSpace(os.Getenv("ORGFRONT_URL")), "/"); value != "" {
return value + "/login?auto=1"
}
}
return ""
}
func resolveLinkedRPInitURL(clientID string, metadata map[string]interface{}) string {
if !resolveLinkedRPAutoLoginSupported(clientID, metadata) {
return ""
}
return resolveLinkedRPAutoLoginURL(clientID, metadata)
}
func buildHydraAuthorizationURL(clientID string, scopes []string, redirectURIs []string) string {
hydraPublicURL := strings.TrimRight(os.Getenv("HYDRA_PUBLIC_URL"), "/")
if hydraPublicURL == "" {
userfrontURL := strings.TrimRight(os.Getenv("USERFRONT_URL"), "/")