forked from baron/baron-sso
205 lines
6.1 KiB
Go
205 lines
6.1 KiB
Go
package bootstrap
|
|
|
|
import (
|
|
"baron-sso-backend/internal/domain"
|
|
"baron-sso-backend/internal/repository"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/mail"
|
|
"strings"
|
|
"time"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type SuperAdminIdentityAdmin interface {
|
|
FindIdentityIDByIdentifier(ctx context.Context, identifier string) (string, error)
|
|
CreateUser(ctx context.Context, user *domain.BrokerUser, password string) (string, error)
|
|
UpdateIdentityPassword(ctx context.Context, identityID, newPassword string) error
|
|
}
|
|
|
|
type SuperAdminStore interface {
|
|
FindUserByEmail(ctx context.Context, email string) (*domain.User, error)
|
|
CreateUser(ctx context.Context, user *domain.User) error
|
|
UpdateUserSuperAdmin(ctx context.Context, userID string, name string) (*domain.User, error)
|
|
EnqueueSuperAdminRelation(ctx context.Context, userID string) error
|
|
}
|
|
|
|
type EnsureSuperAdminOptions struct {
|
|
Email string
|
|
Password string
|
|
Name string
|
|
Source string
|
|
UpdatePassword bool
|
|
}
|
|
|
|
type EnsureSuperAdminResult struct {
|
|
Email string
|
|
IdentityID string
|
|
LocalUserID string
|
|
IdentityCreated bool
|
|
PasswordUpdated bool
|
|
LocalUserCreated bool
|
|
LocalUserUpdated bool
|
|
KetoRelationQueued bool
|
|
}
|
|
|
|
func EnsureSuperAdmin(ctx context.Context, identityAdmin SuperAdminIdentityAdmin, store SuperAdminStore, opts EnsureSuperAdminOptions) (EnsureSuperAdminResult, error) {
|
|
email := strings.ToLower(strings.TrimSpace(opts.Email))
|
|
name := strings.TrimSpace(opts.Name)
|
|
if name == "" {
|
|
name = "System Admin"
|
|
}
|
|
source := strings.TrimSpace(opts.Source)
|
|
if source == "" {
|
|
source = "admin_cli"
|
|
}
|
|
result := EnsureSuperAdminResult{Email: email}
|
|
|
|
if _, err := mail.ParseAddress(email); err != nil {
|
|
return result, fmt.Errorf("invalid admin email: %w", err)
|
|
}
|
|
if identityAdmin == nil {
|
|
return result, errors.New("identity admin is required")
|
|
}
|
|
if store == nil {
|
|
return result, errors.New("super admin store is required")
|
|
}
|
|
|
|
identityID, err := identityAdmin.FindIdentityIDByIdentifier(ctx, email)
|
|
if err != nil {
|
|
return result, fmt.Errorf("find admin identity: %w", err)
|
|
}
|
|
if identityID == "" {
|
|
if strings.TrimSpace(opts.Password) == "" {
|
|
return result, errors.New("admin password is required to create identity")
|
|
}
|
|
identityID, err = identityAdmin.CreateUser(ctx, buildSuperAdminBrokerUser(email, name), opts.Password)
|
|
if err != nil {
|
|
return result, fmt.Errorf("create admin identity: %w", err)
|
|
}
|
|
result.IdentityCreated = true
|
|
} else if opts.UpdatePassword {
|
|
if strings.TrimSpace(opts.Password) == "" {
|
|
return result, errors.New("admin password is required to update identity password")
|
|
}
|
|
if err := identityAdmin.UpdateIdentityPassword(ctx, identityID, opts.Password); err != nil {
|
|
return result, fmt.Errorf("update admin identity password: %w", err)
|
|
}
|
|
result.PasswordUpdated = true
|
|
}
|
|
result.IdentityID = identityID
|
|
|
|
user, err := store.FindUserByEmail(ctx, email)
|
|
if err != nil {
|
|
return result, fmt.Errorf("find local admin user: %w", err)
|
|
}
|
|
if user == nil {
|
|
if identityID == "" {
|
|
return result, errors.New("identity id is required to create local admin user")
|
|
}
|
|
user = &domain.User{
|
|
ID: identityID,
|
|
Email: email,
|
|
Name: name,
|
|
Role: domain.RoleSuperAdmin,
|
|
Status: domain.UserStatusActive,
|
|
CreatedAt: time.Now(),
|
|
UpdatedAt: time.Now(),
|
|
Metadata: domain.JSONMap{
|
|
"source": source,
|
|
},
|
|
}
|
|
if err := store.CreateUser(ctx, user); err != nil {
|
|
return result, fmt.Errorf("create local admin user: %w", err)
|
|
}
|
|
result.LocalUserCreated = true
|
|
} else if domain.NormalizeRole(user.Role) != domain.RoleSuperAdmin || user.Status != domain.UserStatusActive || (name != "" && user.Name != name) {
|
|
user, err = store.UpdateUserSuperAdmin(ctx, user.ID, name)
|
|
if err != nil {
|
|
return result, fmt.Errorf("update local admin user: %w", err)
|
|
}
|
|
result.LocalUserUpdated = true
|
|
}
|
|
result.LocalUserID = user.ID
|
|
|
|
if err := store.EnqueueSuperAdminRelation(ctx, user.ID); err != nil {
|
|
return result, fmt.Errorf("enqueue super admin keto relation: %w", err)
|
|
}
|
|
result.KetoRelationQueued = true
|
|
return result, nil
|
|
}
|
|
|
|
func buildSuperAdminBrokerUser(email, name string) *domain.BrokerUser {
|
|
return &domain.BrokerUser{
|
|
Email: email,
|
|
Name: name,
|
|
PhoneNumber: "",
|
|
Attributes: map[string]interface{}{
|
|
"department": "Admin",
|
|
"affiliationType": "internal",
|
|
"companyCode": "",
|
|
"grade": "",
|
|
"role": domain.RoleSuperAdmin,
|
|
},
|
|
}
|
|
}
|
|
|
|
type gormSuperAdminStore struct {
|
|
db *gorm.DB
|
|
outbox repository.KetoOutboxRepository
|
|
}
|
|
|
|
func NewGormSuperAdminStore(db *gorm.DB, outbox repository.KetoOutboxRepository) SuperAdminStore {
|
|
return &gormSuperAdminStore{db: db, outbox: outbox}
|
|
}
|
|
|
|
func (s *gormSuperAdminStore) FindUserByEmail(ctx context.Context, email string) (*domain.User, error) {
|
|
var user domain.User
|
|
if err := s.db.WithContext(ctx).Where("email = ?", email).First(&user).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
return &user, nil
|
|
}
|
|
|
|
func (s *gormSuperAdminStore) CreateUser(ctx context.Context, user *domain.User) error {
|
|
return s.db.WithContext(ctx).Create(user).Error
|
|
}
|
|
|
|
func (s *gormSuperAdminStore) UpdateUserSuperAdmin(ctx context.Context, userID string, name string) (*domain.User, error) {
|
|
updates := map[string]interface{}{
|
|
"role": domain.RoleSuperAdmin,
|
|
"status": domain.UserStatusActive,
|
|
"updated_at": time.Now(),
|
|
}
|
|
if strings.TrimSpace(name) != "" {
|
|
updates["name"] = strings.TrimSpace(name)
|
|
}
|
|
if err := s.db.WithContext(ctx).Model(&domain.User{}).Where("id = ?", userID).Updates(updates).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var user domain.User
|
|
if err := s.db.WithContext(ctx).Where("id = ?", userID).First(&user).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return &user, nil
|
|
}
|
|
|
|
func (s *gormSuperAdminStore) EnqueueSuperAdminRelation(ctx context.Context, userID string) error {
|
|
if s.outbox == nil {
|
|
return nil
|
|
}
|
|
return s.outbox.Create(ctx, &domain.KetoOutbox{
|
|
Namespace: "System",
|
|
Object: "global",
|
|
Relation: "super_admins",
|
|
Subject: "User:" + userID,
|
|
Action: domain.KetoOutboxActionCreate,
|
|
})
|
|
}
|