1
0
forked from baron/baron-sso

fix: improve keto sync reliability and initial rebac permissions for super admin

This commit is contained in:
2026-04-06 10:10:27 +09:00
parent bd296f9425
commit 583755c189
11 changed files with 254 additions and 81 deletions

View File

@@ -2,52 +2,91 @@ package bootstrap
import (
"baron-sso-backend/internal/domain"
"baron-sso-backend/internal/service"
"baron-sso-backend/internal/repository"
"context"
"log/slog"
"gorm.io/gorm"
)
// SyncKetoRelations synchronizes all existing DB users and tenants to Ory Keto.
// SyncKetoRelations synchronizes all existing DB users, tenants and RPs to Ory Keto via Outbox.
// This ensures data consistency for existing data when ReBAC is introduced.
func SyncKetoRelations(db *gorm.DB, keto service.KetoService) error {
slog.Info("🚀 Starting Keto ReBAC relation synchronization...")
func SyncKetoRelations(db *gorm.DB, outbox repository.KetoOutboxRepository) error {
slog.Info("🚀 Starting Keto ReBAC relation synchronization (via Outbox)...")
ctx := context.Background()
// 1. Sync All Tenants (Ensure they exist in Keto if needed)
// 1. Sync All Tenants
var tenants []domain.Tenant
if err := db.Find(&tenants).Error; err != nil {
return err
}
slog.Info("Syncing tenants to Keto", "count", len(tenants))
slog.Info("Syncing tenants to Keto Outbox", "count", len(tenants))
for _, t := range tenants {
// Global Super Admin access to every tenant
_ = outbox.Create(ctx, &domain.KetoOutbox{
Namespace: "Tenant",
Object: t.ID,
Relation: "admins",
Subject: "System:global#super_admins",
Action: domain.KetoOutboxActionCreate,
})
if t.ParentID != nil {
_ = keto.CreateRelation(ctx, "Tenant", t.ID, "parents", "Tenant:"+*t.ParentID)
_ = outbox.Create(ctx, &domain.KetoOutbox{
Namespace: "Tenant",
Object: t.ID,
Relation: "parents",
Subject: "Tenant:" + *t.ParentID,
Action: domain.KetoOutboxActionCreate,
})
}
}
// 2. Sync All Users
// 2. Sync All RelyingParties (if needed)
// Note: We'll need a way to list them from Hydra or local DB if we had them.
// Assuming they are in a table domain.RelyingParty (though it was removed, let's see)
// Actually, the comment said SSOT is Hydra. But we might have them in a local table for metadata.
// If not, we skip for now or fetch from Hydra.
// 3. Sync All Users Roles and Tenant Memberships
var users []domain.User
if err := db.Find(&users).Error; err != nil {
return err
}
slog.Info("Syncing users to Keto", "count", len(users))
slog.Info("Syncing users to Keto Outbox", "count", len(users))
for _, u := range users {
role := domain.NormalizeRole(u.Role)
// Membership
// Tenant Membership
if u.TenantID != nil {
_ = keto.CreateRelation(ctx, "Tenant", *u.TenantID, "members", "User:"+u.ID)
_ = outbox.Create(ctx, &domain.KetoOutbox{
Namespace: "Tenant",
Object: *u.TenantID,
Relation: "members",
Subject: "User:" + u.ID,
Action: domain.KetoOutboxActionCreate,
})
}
// Roles
role := domain.NormalizeRole(u.Role)
if role == domain.RoleSuperAdmin {
_ = keto.CreateRelation(ctx, "System", "global", "super_admins", "User:"+u.ID)
_ = outbox.Create(ctx, &domain.KetoOutbox{
Namespace: "System",
Object: "global",
Relation: "super_admins",
Subject: "User:" + u.ID,
Action: domain.KetoOutboxActionCreate,
})
} else if role == domain.RoleTenantAdmin && u.TenantID != nil {
_ = keto.CreateRelation(ctx, "Tenant", *u.TenantID, "admins", "User:"+u.ID)
_ = outbox.Create(ctx, &domain.KetoOutbox{
Namespace: "Tenant",
Object: *u.TenantID,
Relation: "admins",
Subject: "User:" + u.ID,
Action: domain.KetoOutboxActionCreate,
})
}
}
slog.Info("✅ Keto ReBAC synchronization completed.")
slog.Info("✅ Keto ReBAC synchronization items added to Outbox.")
return nil
}