1
0
forked from baron/baron-sso
This commit is contained in:
2026-03-24 14:22:05 +09:00
parent b0e18cc724
commit a4f283e4e6
18 changed files with 197 additions and 93 deletions

View File

@@ -34,9 +34,10 @@ type UserHandler struct {
KetoService service.KetoService
KetoOutboxRepo repository.KetoOutboxRepository
UserRepo repository.UserRepository
UserGroupRepo repository.UserGroupRepository
}
func NewUserHandler(kratosAdmin service.KratosAdminService, oryProvider OryProviderAPI, tenantService service.TenantService, ketoService service.KetoService, ketoOutboxRepo repository.KetoOutboxRepository, userRepo repository.UserRepository) *UserHandler {
func NewUserHandler(kratosAdmin service.KratosAdminService, oryProvider OryProviderAPI, tenantService service.TenantService, ketoService service.KetoService, ketoOutboxRepo repository.KetoOutboxRepository, userRepo repository.UserRepository, userGroupRepo repository.UserGroupRepository) *UserHandler {
return &UserHandler{
KratosAdmin: kratosAdmin,
OryProvider: oryProvider,
@@ -44,6 +45,7 @@ func NewUserHandler(kratosAdmin service.KratosAdminService, oryProvider OryProvi
KetoService: ketoService,
KetoOutboxRepo: ketoOutboxRepo,
UserRepo: userRepo,
UserGroupRepo: userGroupRepo,
}
}
@@ -83,7 +85,7 @@ func (h *UserHandler) ListUsers(c *fiber.Ctx) error {
limit := c.QueryInt("limit", 50)
offset := c.QueryInt("offset", 0)
search := strings.TrimSpace(c.Query("search"))
companyCode := strings.TrimSpace(c.Query("companyCode"))
tenantSlug := strings.TrimSpace(c.Query("tenantSlug"))
if limit <= 0 {
limit = 50
@@ -125,8 +127,8 @@ func (h *UserHandler) ListUsers(c *fiber.Ctx) error {
}
}
// Dedicated companyCode filter
if companyCode != "" && !strings.EqualFold(compCode, companyCode) {
// Dedicated tenantSlug filter
if tenantSlug != "" && !strings.EqualFold(compCode, tenantSlug) {
continue
}
@@ -176,7 +178,7 @@ func (h *UserHandler) ListUsers(c *fiber.Ctx) error {
slog.Warn("Kratos unavailable, falling back to local DB for user list", "error", err)
// Fetch from UserRepo
users, total, err := h.UserRepo.List(c.Context(), offset, limit, search, companyCode)
users, total, err := h.UserRepo.List(c.Context(), offset, limit, search, tenantSlug)
if err != nil {
return errorJSON(c, fiber.StatusInternalServerError, "failed to fetch users from both kratos and local db")
}
@@ -414,7 +416,7 @@ type bulkUserItem struct {
Name string `json:"name"`
Phone string `json:"phone"`
Role string `json:"role"`
CompanyCode string `json:"companyCode"`
TenantSlug string `json:"tenantSlug"`
Department string `json:"department"`
Metadata map[string]any `json:"metadata"`
}
@@ -456,13 +458,14 @@ func (h *UserHandler) BulkCreateUsers(c *fiber.Ctx) error {
type tenantCacheItem struct {
ID string
Schema []interface{}
Groups []domain.UserGroup
}
tenantCache := make(map[string]tenantCacheItem)
for _, item := range req.Users {
email := strings.TrimSpace(item.Email)
name := strings.TrimSpace(item.Name)
compCode := strings.TrimSpace(item.CompanyCode)
tenantSlug := strings.TrimSpace(item.TenantSlug)
dept := strings.TrimSpace(item.Department)
if email == "" || name == "" {
@@ -470,14 +473,14 @@ func (h *UserHandler) BulkCreateUsers(c *fiber.Ctx) error {
continue
}
if compCode == "" {
results = append(results, bulkUserResult{Email: email, Success: false, Message: "companyCode (tenant) is required"})
if tenantSlug == "" {
results = append(results, bulkUserResult{Email: email, Success: false, Message: "tenantSlug is required"})
continue
}
// Role-based access check
if requester != nil && requester.Role == domain.RoleTenantAdmin {
if compCode != requester.CompanyCode {
if tenantSlug != requester.CompanyCode {
results = append(results, bulkUserResult{Email: email, Success: false, Message: "forbidden: cannot add users to another tenant"})
continue
}
@@ -486,18 +489,25 @@ func (h *UserHandler) BulkCreateUsers(c *fiber.Ctx) error {
// Verify Tenant Existence and Resolve ID (with Cache)
var tItem tenantCacheItem
var exists bool
if tItem, exists = tenantCache[compCode]; !exists {
if tItem, exists = tenantCache[tenantSlug]; !exists {
if h.TenantService != nil {
tenant, err := h.TenantService.GetTenantBySlug(c.Context(), compCode)
tenant, err := h.TenantService.GetTenantBySlug(c.Context(), tenantSlug)
if err != nil || tenant == nil {
results = append(results, bulkUserResult{Email: email, Success: false, Message: "invalid companyCode: tenant not found"})
results = append(results, bulkUserResult{Email: email, Success: false, Message: "invalid tenantSlug: tenant not found"})
continue
}
tItem.ID = tenant.ID
if s, ok := tenant.Config["userSchema"].([]interface{}); ok {
tItem.Schema = s
}
tenantCache[compCode] = tItem
// [Fix] Cache user groups for this tenant to match department
if h.UserGroupRepo != nil {
if groups, err := h.UserGroupRepo.ListByTenantID(c.Context(), tenant.ID); err == nil {
tItem.Groups = groups
}
}
tenantCache[tenantSlug] = tItem
} else {
results = append(results, bulkUserResult{Email: email, Success: false, Message: "tenant service unavailable"})
continue
@@ -521,7 +531,7 @@ func (h *UserHandler) BulkCreateUsers(c *fiber.Ctx) error {
attributes := map[string]interface{}{
"department": dept,
"affiliationType": "internal",
"companyCode": compCode,
"companyCode": tenantSlug,
"tenant_id": tItem.ID,
"grade": role,
"role": role,
@@ -541,8 +551,18 @@ func (h *UserHandler) BulkCreateUsers(c *fiber.Ctx) error {
Attributes: attributes,
}, password)
if err != nil {
results = append(results, bulkUserResult{Email: email, Success: false, Message: err.Error()})
continue
// 만약 이미 존재하는 사용자라면 로컬 DB 및 Keto 관계만 업데이트(Sync)를 시도
if strings.Contains(err.Error(), "already exists") {
identityID, err = h.KratosAdmin.FindIdentityIDByIdentifier(c.Context(), email)
if err != nil || identityID == "" {
results = append(results, bulkUserResult{Email: email, Success: false, Message: "이미 존재하는 사용자지만 ID를 찾을 수 없습니다."})
continue
}
slog.Info("BulkCreate: User already exists, syncing local DB and Keto", "email", email, "identityID", identityID)
} else {
results = append(results, bulkUserResult{Email: email, Success: false, Message: err.Error()})
continue
}
}
// [CRITICAL FIX] Sync to local DB directly using current data
@@ -555,7 +575,7 @@ func (h *UserHandler) BulkCreateUsers(c *fiber.Ctx) error {
Phone: normalizePhoneNumber(item.Phone),
Role: role,
Status: "active",
CompanyCode: compCode,
CompanyCode: tenantSlug,
Department: dept,
AffiliationType: "internal",
CreatedAt: time.Now(),
@@ -590,6 +610,22 @@ func (h *UserHandler) BulkCreateUsers(c *fiber.Ctx) error {
Action: domain.KetoOutboxActionCreate,
})
}
// 3. Sync membership to UserGroup if department matches
if dept != "" {
for _, g := range tItem.Groups {
if strings.EqualFold(strings.TrimSpace(g.Name), dept) {
_ = h.KetoOutboxRepo.Create(c.Context(), &domain.KetoOutbox{
Namespace: "Tenant",
Object: g.ID,
Relation: "members",
Subject: "User:" + localUser.ID,
Action: domain.KetoOutboxActionCreate,
})
break
}
}
}
}
}