forked from baron/baron-sso
237 lines
6.2 KiB
Go
237 lines
6.2 KiB
Go
package main
|
|
|
|
import (
|
|
"baron-sso-backend/internal/bootstrap"
|
|
"baron-sso-backend/internal/idp"
|
|
"baron-sso-backend/internal/logger"
|
|
"baron-sso-backend/internal/repository"
|
|
"baron-sso-backend/internal/service"
|
|
"context"
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"log/slog"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/joho/godotenv"
|
|
"gorm.io/driver/postgres"
|
|
"gorm.io/gorm"
|
|
gormLogger "gorm.io/gorm/logger"
|
|
)
|
|
|
|
type createSuperAdminConfig struct {
|
|
Email string
|
|
Password string
|
|
Name string
|
|
UpdatePassword bool
|
|
}
|
|
|
|
type clearOrphanUserTenantMembershipsConfig struct {
|
|
DryRun bool
|
|
}
|
|
|
|
func main() {
|
|
loadEnv()
|
|
logger.Init(logger.Config{
|
|
ServiceName: "baron-sso-adminctl",
|
|
Environment: getenv("APP_ENV", getenv("GO_ENV", "dev")),
|
|
LevelOverride: getenv("BACKEND_LOG_LEVEL", ""),
|
|
})
|
|
|
|
if len(os.Args) < 2 {
|
|
printUsage()
|
|
os.Exit(2)
|
|
}
|
|
|
|
switch os.Args[1] {
|
|
case "create-super-admin":
|
|
if err := runCreateSuperAdmin(os.Args[2:]); err != nil {
|
|
slog.Error("create-super-admin failed", "error", err)
|
|
os.Exit(1)
|
|
}
|
|
case "clear-orphan-user-tenant-memberships":
|
|
if err := runClearOrphanUserTenantMemberships(os.Args[2:]); err != nil {
|
|
slog.Error("clear-orphan-user-tenant-memberships failed", "error", err)
|
|
os.Exit(1)
|
|
}
|
|
case "worksmobile-sync":
|
|
if err := runWorksmobileSync(os.Args[2:]); err != nil {
|
|
slog.Error("worksmobile-sync failed", "error", err)
|
|
os.Exit(1)
|
|
}
|
|
default:
|
|
printUsage()
|
|
os.Exit(2)
|
|
}
|
|
}
|
|
|
|
func runCreateSuperAdmin(args []string) error {
|
|
config, err := resolveCreateSuperAdminConfig(args)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
db, err := openDB()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := bootstrap.Run(db); err != nil {
|
|
return err
|
|
}
|
|
|
|
provider, err := idp.InitializeProvider()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if provider == nil {
|
|
return fmt.Errorf("idp provider is required")
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
result, err := bootstrap.EnsureSuperAdmin(
|
|
ctx,
|
|
service.NewKratosAdminService(),
|
|
bootstrap.NewGormSuperAdminStore(db, repository.NewKetoOutboxRepository(db)),
|
|
bootstrap.EnsureSuperAdminOptions{
|
|
Email: config.Email,
|
|
Password: config.Password,
|
|
Name: config.Name,
|
|
Source: "adminctl",
|
|
UpdatePassword: config.UpdatePassword,
|
|
},
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Printf("super admin ensured: email=%s identity_id=%s user_id=%s identity_created=%t local_created=%t local_updated=%t password_updated=%t keto_relation_queued=%t\n",
|
|
result.Email,
|
|
result.IdentityID,
|
|
result.LocalUserID,
|
|
result.IdentityCreated,
|
|
result.LocalUserCreated,
|
|
result.LocalUserUpdated,
|
|
result.PasswordUpdated,
|
|
result.KetoRelationQueued,
|
|
)
|
|
return nil
|
|
}
|
|
|
|
func runClearOrphanUserTenantMemberships(args []string) error {
|
|
config, err := resolveClearOrphanUserTenantMembershipsConfig(args)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
db, err := openDB()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
if config.DryRun {
|
|
count, err := repository.CountOrphanUserTenantMemberships(ctx, db)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Printf("orphan user tenant memberships dry-run: count=%d\n", count)
|
|
return nil
|
|
}
|
|
|
|
affected, err := repository.ClearOrphanUserTenantMemberships(ctx, db)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Printf("orphan user tenant memberships cleared: count=%d\n", affected)
|
|
return nil
|
|
}
|
|
|
|
func resolveCreateSuperAdminConfig(args []string) (createSuperAdminConfig, error) {
|
|
fs := flag.NewFlagSet("create-super-admin", flag.ContinueOnError)
|
|
fs.SetOutput(os.Stderr)
|
|
|
|
config := createSuperAdminConfig{}
|
|
fs.StringVar(&config.Email, "email", getenv("ADMIN_EMAIL", ""), "admin email")
|
|
fs.StringVar(&config.Password, "password", getenv("ADMIN_PASSWORD", ""), "admin password")
|
|
fs.StringVar(&config.Name, "name", getenv("ADMIN_NAME", "System Admin"), "admin display name")
|
|
fs.BoolVar(&config.UpdatePassword, "update-password", false, "update password when identity already exists")
|
|
|
|
if err := fs.Parse(args); err != nil {
|
|
return config, err
|
|
}
|
|
|
|
config.Email = strings.TrimSpace(config.Email)
|
|
config.Name = strings.TrimSpace(config.Name)
|
|
if config.Email == "" {
|
|
return config, fmt.Errorf("admin email is required; pass --email or set ADMIN_EMAIL")
|
|
}
|
|
if strings.TrimSpace(config.Password) == "" {
|
|
return config, fmt.Errorf("admin password is required; pass --password or set ADMIN_PASSWORD")
|
|
}
|
|
if config.Name == "" {
|
|
config.Name = "System Admin"
|
|
}
|
|
return config, nil
|
|
}
|
|
|
|
func resolveClearOrphanUserTenantMembershipsConfig(args []string) (clearOrphanUserTenantMembershipsConfig, error) {
|
|
fs := flag.NewFlagSet("clear-orphan-user-tenant-memberships", flag.ContinueOnError)
|
|
fs.SetOutput(os.Stderr)
|
|
|
|
config := clearOrphanUserTenantMembershipsConfig{}
|
|
fs.BoolVar(&config.DryRun, "dry-run", false, "count orphan memberships without updating users")
|
|
|
|
if err := fs.Parse(args); err != nil {
|
|
return config, err
|
|
}
|
|
return config, nil
|
|
}
|
|
|
|
func openDB() (*gorm.DB, error) {
|
|
dsn := fmt.Sprintf(
|
|
"host=%s user=%s password=%s dbname=%s port=%s sslmode=disable TimeZone=Asia/Seoul",
|
|
getenv("DB_HOST", "localhost"),
|
|
getenv("DB_USER", "baron"),
|
|
getenv("DB_PASSWORD", "password"),
|
|
getenv("DB_NAME", "baron_sso"),
|
|
getenv("DB_PORT", "5432"),
|
|
)
|
|
return gorm.Open(postgres.Open(dsn), &gorm.Config{
|
|
Logger: gormLogger.New(
|
|
log.New(os.Stdout, "\r\n", log.LstdFlags),
|
|
gormLogger.Config{
|
|
SlowThreshold: time.Second,
|
|
LogLevel: gormLogger.Warn,
|
|
IgnoreRecordNotFoundError: true,
|
|
Colorful: true,
|
|
},
|
|
),
|
|
})
|
|
}
|
|
|
|
func loadEnv() {
|
|
_ = godotenv.Load(".env")
|
|
_ = godotenv.Load("../.env")
|
|
_ = godotenv.Load("../../.env")
|
|
}
|
|
|
|
func getenv(key string, fallback string) string {
|
|
if value, ok := os.LookupEnv(key); ok {
|
|
return value
|
|
}
|
|
return fallback
|
|
}
|
|
|
|
func printUsage() {
|
|
fmt.Fprintln(os.Stderr, "usage:")
|
|
fmt.Fprintln(os.Stderr, " adminctl create-super-admin [--email EMAIL] [--password PASSWORD] [--name NAME] [--update-password]")
|
|
fmt.Fprintln(os.Stderr, " adminctl clear-orphan-user-tenant-memberships [--dry-run]")
|
|
fmt.Fprintln(os.Stderr, " adminctl worksmobile-sync [--orgunits] [--users-csv PATH] [--credential-batch-id ID] [--process] [--serialize-orgunits] [--serialize-users-batch ID] [--batch-size N] [--delay DURATION]")
|
|
}
|