1
0
forked from baron/baron-sso

dev API 관계 사용자 검색 및 관계 목록 사용자 정보 추가

This commit is contained in:
2026-04-15 18:23:07 +09:00
parent f494d8e50a
commit f955d23ef1
3 changed files with 282 additions and 5 deletions

View File

@@ -127,12 +127,26 @@ type clientRelationSummary struct {
Subject string `json:"subject"`
SubjectType string `json:"subjectType"`
SubjectID string `json:"subjectId"`
UserName string `json:"userName,omitempty"`
UserEmail string `json:"userEmail,omitempty"`
UserLoginID string `json:"userLoginId,omitempty"`
}
type clientRelationListResponse struct {
Items []clientRelationSummary `json:"items"`
}
type devUserSummary struct {
ID string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
LoginID string `json:"loginId,omitempty"`
}
type devUserListResponse struct {
Items []devUserSummary `json:"items"`
}
type clientRelationUpsertRequest struct {
Relation string `json:"relation"`
Subject string `json:"subject"`
@@ -416,6 +430,68 @@ func isRPAdminClientAllowed(profile *domain.UserProfileResponse, clientID string
return ok
}
func manageableTenantKeysFromProfile(profile *domain.UserProfileResponse) map[string]struct{} {
keys := make(map[string]struct{})
if profile == nil {
return keys
}
addKey := func(value string) {
trimmed := strings.ToLower(strings.TrimSpace(value))
if trimmed != "" {
keys[trimmed] = struct{}{}
}
}
addKey(profile.CompanyCode)
if profile.TenantID != nil {
addKey(*profile.TenantID)
}
for _, tenant := range profile.ManageableTenants {
addKey(tenant.ID)
addKey(tenant.Slug)
}
for _, tenant := range profile.JoinedTenants {
addKey(tenant.ID)
addKey(tenant.Slug)
}
return keys
}
func canAccessIdentityByTenant(profile *domain.UserProfileResponse, identity service.KratosIdentity) bool {
if normalizeUserRole(profileRole(profile)) == domain.RoleSuperAdmin {
return true
}
keys := manageableTenantKeysFromProfile(profile)
if len(keys) == 0 {
return false
}
for _, raw := range []string{
extractTraitString(identity.Traits, "tenant_id"),
extractTraitString(identity.Traits, "companyCode"),
extractTraitString(identity.Traits, "company_code"),
} {
if _, ok := keys[strings.ToLower(strings.TrimSpace(raw))]; ok {
return true
}
}
return false
}
func mapDevUserSummary(identity service.KratosIdentity) devUserSummary {
traits := identity.Traits
return devUserSummary{
ID: identity.ID,
Name: extractTraitString(traits, "name"),
Email: extractTraitString(traits, "email"),
LoginID: resolvePasswordLoginID(traits),
}
}
func profileRole(profile *domain.UserProfileResponse) string {
if profile == nil {
return ""
@@ -496,14 +572,20 @@ func validateClientRelationWriteInput(relation, subject string) error {
return nil
}
func mapRelationTupleSummary(tuple service.RelationTuple) clientRelationSummary {
func mapRelationTupleSummary(tuple service.RelationTuple, identity *service.KratosIdentity) clientRelationSummary {
subjectType, subjectID := parseClientRelationSubject(tuple.SubjectID)
return clientRelationSummary{
summary := clientRelationSummary{
Relation: tuple.Relation,
Subject: tuple.SubjectID,
SubjectType: subjectType,
SubjectID: subjectID,
}
if identity != nil {
summary.UserName = extractTraitString(identity.Traits, "name")
summary.UserEmail = extractTraitString(identity.Traits, "email")
summary.UserLoginID = resolvePasswordLoginID(identity.Traits)
}
return summary
}
func (h *DevHandler) loadClientSummary(ctx context.Context, clientID string) (clientSummary, error) {
@@ -522,6 +604,65 @@ func (h *DevHandler) getRelationRequestProfile(c *fiber.Ctx) *domain.UserProfile
return h.getCurrentProfile(c)
}
func (h *DevHandler) SearchUsers(c *fiber.Ctx) error {
profile := h.getCurrentProfile(c)
if profile == nil {
return errorJSON(c, fiber.StatusUnauthorized, "unauthorized: authentication required")
}
if !isDevConsoleRoleAllowed(normalizeUserRole(profile.Role)) {
return errorJSON(c, fiber.StatusForbidden, "forbidden")
}
if h.KratosAdmin == nil {
return errorJSON(c, fiber.StatusServiceUnavailable, "kratos admin unavailable")
}
search := strings.ToLower(strings.TrimSpace(c.Query("search")))
limit := c.QueryInt("limit", 10)
if limit <= 0 {
limit = 10
}
if limit > 20 {
limit = 20
}
identities, err := h.KratosAdmin.ListIdentities(c.Context())
if err != nil {
return errorJSON(c, fiber.StatusInternalServerError, err.Error())
}
items := make([]devUserSummary, 0, limit)
for _, identity := range identities {
if !canAccessIdentityByTenant(profile, identity) {
continue
}
summary := mapDevUserSummary(identity)
if search != "" {
matched := false
for _, candidate := range []string{
strings.ToLower(summary.Name),
strings.ToLower(summary.Email),
strings.ToLower(summary.LoginID),
} {
if candidate != "" && strings.Contains(candidate, search) {
matched = true
break
}
}
if !matched {
continue
}
}
items = append(items, summary)
if len(items) >= limit {
break
}
}
return c.JSON(devUserListResponse{Items: items})
}
func validateReservedSystemClientName(clientID, name string) error {
ownerID, reserved := reservedSystemClientOwnerID(name)
if !reserved {
@@ -892,7 +1033,14 @@ func (h *DevHandler) ListClientRelations(c *fiber.Ctx) error {
return errorJSON(c, fiber.StatusInternalServerError, err.Error())
}
for _, tuple := range tuples {
items = append(items, mapRelationTupleSummary(tuple))
var identity *service.KratosIdentity
if tuple.SubjectID != "" && h.KratosAdmin != nil {
_, subjectID := parseClientRelationSubject(tuple.SubjectID)
if subjectID != "" {
identity, _ = h.KratosAdmin.GetIdentity(c.Context(), subjectID)
}
}
items = append(items, mapRelationTupleSummary(tuple, identity))
}
}
@@ -950,7 +1098,7 @@ func (h *DevHandler) AddClientRelation(c *fiber.Ctx) error {
Object: clientID,
Relation: req.Relation,
SubjectID: req.Subject,
}))
}, nil))
}
func (h *DevHandler) RemoveClientRelation(c *fiber.Ctx) error {