3 namespace BookStack\Console\Commands;
5 use BookStack\Users\Models\Role;
6 use BookStack\Users\UserRepo;
7 use Illuminate\Console\Command;
8 use Illuminate\Support\Facades\Validator;
9 use Illuminate\Support\Str;
10 use Illuminate\Validation\Rules\Password;
11 use Illuminate\Validation\Rules\Unique;
13 class CreateAdminCommand extends Command
16 * The name and signature of the console command.
20 protected $signature = 'bookstack:create-admin
21 {--email= : The email address for the new admin user}
22 {--name= : The name of the new admin user}
23 {--password= : The password to assign to the new admin user}
24 {--external-auth-id= : The external authentication system id for the new admin user (SAML2/LDAP/OIDC)}
25 {--generate-password : Generate a random password for the new admin user}
26 {--first-admin : Indicate if this should set/update the details of the initial admin user}';
29 * The console command description.
33 protected $description = 'Add a new admin user to the system';
36 * Execute the console command.
38 public function handle(UserRepo $userRepo): int
40 $firstAdminOnly = $this->option('first-admin');
41 $shouldGeneratePassword = $this->option('generate-password');
42 $details = $this->gatherDetails($shouldGeneratePassword);
44 $validator = Validator::make($details, [
45 'email' => ['required', 'email', 'min:5', new Unique('users', 'email')],
46 'name' => ['required', 'min:2'],
47 'password' => ['required_without:external_auth_id', Password::default()],
48 'external_auth_id' => ['required_without:password'],
51 if ($validator->fails()) {
52 foreach ($validator->errors()->all() as $error) {
59 $adminRole = Role::getSystemRole('admin');
61 if ($firstAdminOnly) {
62 $handled = $this->handleFirstAdminIfExists($userRepo, $details, $shouldGeneratePassword, $adminRole);
68 $user = $userRepo->createWithoutActivity($validator->validated());
69 $user->attachRole($adminRole);
70 $user->email_confirmed = true;
73 if ($shouldGeneratePassword) {
74 $this->line($details['password']);
76 $this->info("Admin account with email \"{$user->email}\" successfully created!");
83 * Handle updates to the first admin if exists.
84 * Returns true if the action has been handled (user updated or already a non-default admin user) otherwise
85 * returns false if no action has been taken, and we therefore need to proceed with a normal account creation.
87 protected function handleFirstAdminIfExists(UserRepo $userRepo, array $data, bool $generatePassword, Role $adminRole): bool
89 $defaultAdmin = $userRepo->getByEmail('admin@admin.com');
90 if ($defaultAdmin && $defaultAdmin->hasSystemRole('admin')) {
91 $userRepo->updateWithoutActivity($defaultAdmin, $data, true);
92 if ($generatePassword) {
93 $this->line($data['password']);
95 $this->info("The default admin user has been updated with the provided details!");
99 } else if ($adminRole->users()->count() > 0) {
100 $this->warn('Non-default admin user already exists. Skipping creation of new admin user.');
107 protected function gatherDetails(bool $generatePassword): array
109 $details = $this->snakeCaseOptions();
111 if (empty($details['email'])) {
112 $details['email'] = $this->ask('Please specify an email address for the new admin user');
115 if (empty($details['name'])) {
116 $details['name'] = $this->ask('Please specify a name for the new admin user');
119 if (empty($details['password'])) {
120 if (empty($details['external_auth_id'])) {
121 if ($generatePassword) {
122 $details['password'] = Str::random(32);
124 $details['password'] = $this->ask('Please specify a password for the new admin user (8 characters min)');
127 $details['password'] = Str::random(32);
134 protected function snakeCaseOptions(): array
137 foreach ($this->options() as $key => $value) {
138 $returnOpts[str_replace('-', '_', $key)] = $value;