1
0
forked from baron/baron-sso

테넌트 소유자, 관리자 분리

This commit is contained in:
2026-03-03 12:38:27 +09:00
parent 7bb1f3f702
commit 86ef9c6f60
23 changed files with 1091 additions and 516 deletions

View File

@@ -13,7 +13,7 @@ import (
)
type TenantService interface {
RegisterTenant(ctx context.Context, name, slug, tenantType, description string, domains []string, parentID *string) (*domain.Tenant, error)
RegisterTenant(ctx context.Context, name, slug, tenantType, description string, domains []string, parentID *string, creatorID string) (*domain.Tenant, error)
RequestRegistration(ctx context.Context, name, slug, description string, domainName string, adminEmail string) (*domain.Tenant, error)
GetTenantByDomain(ctx context.Context, emailDomain string) (*domain.Tenant, error)
GetTenantBySlug(ctx context.Context, slug string) (*domain.Tenant, error)
@@ -90,7 +90,7 @@ func (s *tenantService) ListManageableTenants(ctx context.Context, userID string
return s.repo.FindByIDs(ctx, allIDs)
}
func (s *tenantService) RegisterTenant(ctx context.Context, name, slug, tenantType, description string, domains []string, parentID *string) (*domain.Tenant, error) {
func (s *tenantService) RegisterTenant(ctx context.Context, name, slug, tenantType, description string, domains []string, parentID *string, creatorID string) (*domain.Tenant, error) {
// Validate Slug
if ok, msg := utils.ValidateSlug(slug); !ok {
return nil, errors.New(msg)
@@ -119,15 +119,49 @@ func (s *tenantService) RegisterTenant(ctx context.Context, name, slug, tenantTy
return nil, err
}
// [Keto] Sync hierarchy via Outbox if ParentID exists
if s.outboxRepo != nil && tenant.ParentID != nil {
_ = s.outboxRepo.Create(ctx, &domain.KetoOutbox{
Namespace: "Tenant",
Object: tenant.ID,
Relation: "parents",
Subject: "Tenant:" + *tenant.ParentID,
Action: domain.KetoOutboxActionCreate,
})
// [Keto] Sync hierarchy and ownership via Outbox
if s.outboxRepo != nil {
// Sync hierarchy
if tenant.ParentID != nil {
if err := s.outboxRepo.Create(ctx, &domain.KetoOutbox{
Namespace: "Tenant",
Object: tenant.ID,
Relation: "parents",
Subject: "Tenant:" + *tenant.ParentID,
Action: domain.KetoOutboxActionCreate,
}); err != nil {
slog.Error("Failed to create outbox entry for tenant hierarchy", "tenant", tenant.ID, "error", err)
}
}
// Sync creator ownership
if creatorID != "" {
slog.Info("Creating outbox entries for tenant creator", "tenant", tenant.ID, "creator", creatorID)
// Add as owner
_ = s.outboxRepo.Create(ctx, &domain.KetoOutbox{
Namespace: "Tenant",
Object: tenant.ID,
Relation: "owners",
Subject: "User:" + creatorID,
Action: domain.KetoOutboxActionCreate,
})
// Add as admin
_ = s.outboxRepo.Create(ctx, &domain.KetoOutbox{
Namespace: "Tenant",
Object: tenant.ID,
Relation: "admins",
Subject: "User:" + creatorID,
Action: domain.KetoOutboxActionCreate,
})
// Add as member
_ = s.outboxRepo.Create(ctx, &domain.KetoOutbox{
Namespace: "Tenant",
Object: tenant.ID,
Relation: "members",
Subject: "User:" + creatorID,
Action: domain.KetoOutboxActionCreate,
})
}
}
// 3. Add Domains (Auto-verify for manual admin registration)
@@ -187,12 +221,20 @@ func (s *tenantService) ApproveTenant(ctx context.Context, id string) error {
// [Keto] Sync relation via Outbox
if s.outboxRepo != nil {
if adminEmail, ok := tenant.Config["adminEmail"].(string); ok && adminEmail != "" {
slog.Info("Queueing tenant admin sync to Keto", "tenant", tenant.Slug, "adminEmail", adminEmail)
slog.Info("Queueing tenant admin/owner sync to Keto", "tenant", tenant.Slug, "adminEmail", adminEmail)
// Check if user already exists in our Read-Model
if s.userRepo != nil {
user, err := s.userRepo.FindByEmail(ctx, adminEmail)
if err == nil && user != nil {
// User exists, assign Admin role in Keto via Outbox
// User exists, assign Admin, Owner, and Member roles in Keto via Outbox
slog.Info("Queueing tenant ownership/membership sync to Keto", "tenant", tenant.Slug, "userID", user.ID)
_ = s.outboxRepo.Create(ctx, &domain.KetoOutbox{
Namespace: "Tenant",
Object: tenant.ID,
Relation: "owners",
Subject: "User:" + user.ID,
Action: domain.KetoOutboxActionCreate,
})
_ = s.outboxRepo.Create(ctx, &domain.KetoOutbox{
Namespace: "Tenant",
Object: tenant.ID,
@@ -200,6 +242,13 @@ func (s *tenantService) ApproveTenant(ctx context.Context, id string) error {
Subject: "User:" + user.ID,
Action: domain.KetoOutboxActionCreate,
})
_ = s.outboxRepo.Create(ctx, &domain.KetoOutbox{
Namespace: "Tenant",
Object: tenant.ID,
Relation: "members",
Subject: "User:" + user.ID,
Action: domain.KetoOutboxActionCreate,
})
} else {
slog.Info("Tenant admin user not found in local DB, will need manual sync or sync on signup", "email", adminEmail)
}