package auth import ( "context" "go.uber.org/zap" "github.com/kandev/kandev/internal/auth/store" usermodels "github.com/kandev/kandev/internal/user/models" userstore "github.com/kandev/kandev/internal/user/store" ) // Setup completes the first-run wizard: it promotes the pre-auth default-user // row into the admin account (preserving all existing user settings), claims // unowned workspaces/secrets for that admin, persists the enabled toggle, and // mints the first session. // // Ordering is deliberate: backfills run first (harmless if repeated), the // local identity insert is the commit point that flips ModeSetup → ModeEnabled, // and a partial failure before it leaves the instance safely in setup mode. func (s *Service) Setup(ctx context.Context, email, password, displayName, userAgent, ip string) (*usermodels.User, string, error) { s.setupMu.Lock() defer s.setupMu.Unlock() if s.Mode() != ModeSetup { return nil, "", ErrSetupNotAvailable } email = normalizeEmail(email) if err := validateEmailPassword(email, password); err != nil { return nil, "", err } adminID := userstore.DefaultUserID for _, backfill := range s.backfills { if err := backfill(ctx, adminID); err != nil { return nil, "", err } } user, err := s.users.UpdateUserProfile(ctx, adminID, email, displayName, usermodels.RoleAdmin) if err != nil { return nil, "", err } hash, err := HashPassword(password) if err != nil { return nil, "", err } // Tenant placement must complete before the identity commit point below. // If it fails, setup remains retryable instead of returning an org-less, // non-operator session that cannot repair itself. if s.adminCreated != nil { if err := s.adminCreated(ctx, adminID); err != nil { return nil, "", err } } identity := &store.LoginIdentity{ UserID: adminID, Provider: store.ProviderLocal, Subject: email, PasswordHash: hash, } if err := s.store.CreateIdentity(ctx, identity); err != nil { // A concurrent setup already created the admin identity. return nil, "", ErrSetupNotAvailable } // An admin identity now exists, so refreshMode derives Enabled from the // (still-on) features.auth flag — no separate toggle to persist. if err := s.refreshMode(ctx); err != nil { return nil, "", err } token, err := s.createSession(ctx, adminID, userAgent, ip) if err != nil { return nil, "", err } if s.log != nil { s.log.Info("authentication setup completed", zap.String("admin_email", email)) } return user, token, nil }