• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

daycry / auth / 22540129920

01 Mar 2026 09:07AM UTC coverage: 63.267%. Remained the same
22540129920

push

github

daycry
Update CHANGELOG links and remove note

Remove an obsolete note and correct the release comparison link in CHANGELOG.md. Deleted the line stating "All features and fixes below were originally planned for v3.1.0." and updated the [4.0.0] comparison URL to use v3.1.0...v4.0.0 instead of v3.0.6...v4.0.0 to reflect the correct baseline for the v4.0.0 changelog entry.

3064 of 4843 relevant lines covered (63.27%)

41.53 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.24
/src/Commands/UserCommand.php
1
<?php
2

3
declare(strict_types=1);
4

5
/**
6
 * This file is part of Daycry Auth.
7
 *
8
 * (c) Daycry <daycry9@proton.me>
9
 *
10
 * For the full copyright and license information, please view
11
 * the LICENSE file that was distributed with this source code.
12
 */
13

14
namespace Daycry\Auth\Commands;
15

16
use Config\Services;
17
use Daycry\Auth\Authentication\Authenticators\Session;
18
use Daycry\Auth\Config\Auth;
19
use Daycry\Auth\Config\AuthSecurity;
20
use Daycry\Auth\Entities\User as UserEntity;
21
use Daycry\Auth\Exceptions\BadInputException;
22
use Daycry\Auth\Exceptions\CancelException;
23
use Daycry\Auth\Exceptions\UserNotFoundException;
24
use Daycry\Auth\Models\UserModel;
25
use Daycry\Auth\Validation\ValidationRules;
26

27
class UserCommand extends BaseCommand
28
{
29
    private array $validActions = [
30
        'create', 'activate', 'deactivate', 'changename', 'changeemail',
31
        'delete', 'password', 'list', 'addgroup', 'removegroup',
32
    ];
33

34
    /**
35
     * Command's name
36
     *
37
     * @var string
38
     */
39
    protected $name = 'auth:user';
40

41
    /**
42
     * Command's short description
43
     *
44
     * @var string
45
     */
46
    protected $description = 'Manage Auth users.';
47

48
    /**
49
     * Command's usage
50
     *
51
     * @var string
52
     */
53
    protected $usage = <<<'EOL'
54
        auth:user <action> options
55

56
            auth:user create -n newusername -e newuser@example.com
57

58
            auth:user activate -n username
59
            auth:user activate -e user@example.com
60

61
            auth:user deactivate -n username
62
            auth:user deactivate -e user@example.com
63

64
            auth:user changename -n username --new-name newusername
65
            auth:user changename -e user@example.com --new-name newusername
66

67
            auth:user changeemail -n username --new-email newuseremail@example.com
68
            auth:user changeemail -e user@example.com --new-email newuseremail@example.com
69

70
            auth:user delete -i 123
71
            auth:user delete -n username
72
            auth:user delete -e user@example.com
73

74
            auth:user password -n username
75
            auth:user password -e user@example.com
76

77
            auth:user list
78
            auth:user list -n username -e user@example.com
79

80
            auth:user addgroup -n username -g mygroup
81
            auth:user addgroup -e user@example.com -g mygroup
82

83
            auth:user removegroup -n username -g mygroup
84
            auth:user removegroup -e user@example.com -g mygroup
85
        EOL;
86

87
    /**
88
     * Command's Arguments
89
     *
90
     * @var array<string, string>
91
     */
92
    protected $arguments = [
93
        'action' => <<<'EOL'
94

95
                create:      Create a new user
96
                activate:    Activate a user
97
                deactivate:  Deactivate a user
98
                changename:  Change user name
99
                changeemail: Change user email
100
                delete:      Delete a user
101
                password:    Change a user password
102
                list:        List users
103
                addgroup:    Add a user to a group
104
                removegroup: Remove a user from a group
105
            EOL,
106
    ];
107

108
    /**
109
     * Command's Options
110
     *
111
     * @var array<string, string>
112
     */
113
    protected $options = [
114
        '-i'          => 'User id',
115
        '-n'          => 'User name',
116
        '-e'          => 'User email',
117
        '--new-name'  => 'New username',
118
        '--new-email' => 'New email',
119
        '-g'          => 'Group name',
120
    ];
121

122
    /**
123
     * Validation rules for user fields
124
     */
125
    private array $validationRules = [];
126

127
    /**
128
     * Auth Table names
129
     *
130
     * @var array<string, string>
131
     */
132
    private array $tables = [];
133

134
    /**
135
     * Displays the help for the spark cli script itself.
136
     */
137
    public function run(array $params): int
22✔
138
    {
139
        $this->setTables();
22✔
140
        $this->setValidationRules();
22✔
141

142
        $action = $params[0] ?? null;
22✔
143

144
        if ($action === null || ! in_array($action, $this->validActions, true)) {
22✔
145
            $this->write(
1✔
146
                'Specify a valid action: ' . implode(',', $this->validActions),
1✔
147
                'red',
1✔
148
            );
1✔
149

150
            return EXIT_ERROR;
1✔
151
        }
152

153
        $userid      = (int) ($params['i'] ?? 0);
21✔
154
        $username    = $params['n'] ?? null;
21✔
155
        $email       = $params['e'] ?? null;
21✔
156
        $newUsername = $params['new-name'] ?? null;
21✔
157
        $newEmail    = $params['new-email'] ?? null;
21✔
158
        $group       = $params['g'] ?? null;
21✔
159

160
        try {
161
            switch ($action) {
162
                case 'create':
21✔
163
                    $this->create($username, $email);
3✔
164
                    break;
1✔
165

166
                case 'activate':
18✔
167
                    $this->activate($username, $email);
1✔
168
                    break;
1✔
169

170
                case 'deactivate':
17✔
171
                    $this->deactivate($username, $email);
1✔
172
                    break;
1✔
173

174
                case 'changename':
16✔
175
                    $this->changename($username, $email, $newUsername);
2✔
176
                    break;
1✔
177

178
                case 'changeemail':
14✔
179
                    $this->changeemail($username, $email, $newEmail);
2✔
180
                    break;
1✔
181

182
                case 'delete':
12✔
183
                    $this->delete($userid, $username, $email);
3✔
184
                    break;
2✔
185

186
                case 'password':
9✔
187
                    $this->password($username, $email);
3✔
188
                    break;
2✔
189

190
                case 'list':
6✔
191
                    $this->list($username, $email);
2✔
192
                    break;
2✔
193

194
                case 'addgroup':
4✔
195
                    $this->addgroup($group, $username, $email);
2✔
196
                    break;
2✔
197

198
                case 'removegroup':
2✔
199
                    $this->removegroup($group, $username, $email);
2✔
200
                    break;
15✔
201
            }
202
        } catch (BadInputException|CancelException|UserNotFoundException $e) {
6✔
203
            $this->write($e->getMessage(), 'red');
6✔
204

205
            return EXIT_ERROR;
6✔
206
        }
207

208
        return EXIT_SUCCESS;
15✔
209
    }
210

211
    private function setTables(): void
22✔
212
    {
213
        /** @var Auth $config */
214
        $config       = config('Auth');
22✔
215
        $this->tables = $config->tables;
22✔
216
    }
217

218
    private function setValidationRules(): void
22✔
219
    {
220
        $validationRules = new ValidationRules();
22✔
221

222
        $rules = $validationRules->getRegistrationRules();
22✔
223

224
        // Remove `strong_password` rule because it only supports use cases
225
        // to check the user's own password.
226
        $passwordRules = $rules['password']['rules'];
22✔
227
        if (is_string($passwordRules)) {
22✔
228
            $passwordRules = explode('|', $passwordRules);
×
229
        }
230
        if (($key = array_search('strong_password[]', $passwordRules, true)) !== false) {
22✔
231
            unset($passwordRules[$key]);
22✔
232
        }
233
        if (($key = array_search('strong_password', $passwordRules, true)) !== false) {
22✔
234
            unset($passwordRules[$key]);
×
235
        }
236

237
        /** @var AuthSecurity $config */
238
        $config = config('AuthSecurity');
22✔
239

240
        // Add `min_length`
241
        $passwordRules[] = 'min_length[' . $config->minimumPasswordLength . ']';
22✔
242

243
        $rules['password']['rules'] = $passwordRules;
22✔
244

245
        // Remove `password_confirm` field.
246
        unset($rules['password_confirm']);
22✔
247

248
        $this->validationRules = $rules;
22✔
249
    }
250

251
    /**
252
     * Create a new user
253
     *
254
     * @param string|null $username User name to create (optional)
255
     * @param string|null $email    User email to create (optional)
256
     */
257
    private function create(?string $username = null, ?string $email = null): void
3✔
258
    {
259
        $data = [];
3✔
260

261
        // If you don't use `username`, remove the validation rules for it.
262
        if ($username === null && isset($this->validationRules['username'])) {
3✔
263
            $username = $this->prompt('Username', null, $this->validationRules['username']['rules']);
×
264
        }
265
        $data['username'] = $username;
3✔
266
        if ($username === null) {
3✔
267
            unset($data['username']);
×
268
        }
269

270
        if ($email === null) {
3✔
271
            $email = $this->prompt('Email', null, $this->validationRules['email']['rules'], config('Auth')->DBGroup);
×
272
        }
273
        $data['email'] = $email;
3✔
274

275
        $password = $this->prompt(
3✔
276
            'Password',
3✔
277
            null,
3✔
278
            $this->validationRules['password']['rules'],
3✔
279
            config('Auth')->DBGroup,
3✔
280
        );
3✔
281
        $passwordConfirm = $this->prompt(
3✔
282
            'Password confirmation',
3✔
283
            null,
3✔
284
            $this->validationRules['password']['rules'],
3✔
285
            config('Auth')->DBGroup,
3✔
286
        );
3✔
287

288
        if ($password !== $passwordConfirm) {
3✔
289
            throw new BadInputException("The passwords don't match");
1✔
290
        }
291
        $data['password'] = $password;
2✔
292

293
        // Run validation if the user has passed username and/or email via command line
294
        $validation = Services::validation();
2✔
295
        $validation->setRules($this->validationRules);
2✔
296

297
        if (! $validation->run($data, null, config('Auth')->DBGroup)) {
2✔
298
            foreach ($validation->getErrors() as $message) {
1✔
299
                $this->write($message, 'red');
1✔
300
            }
301

302
            throw new CancelException('User creation aborted');
1✔
303
        }
304

305
        $userModel = model(UserModel::class);
1✔
306

307
        $user = new UserEntity($data);
1✔
308

309
        if ($username === null) {
1✔
310
            $userModel->allowEmptyInserts()->save($user);
×
311
            $this->write('New User created', 'green');
×
312
        } else {
313
            $userModel->save($user);
1✔
314
            $this->write('User "' . $username . '" created', 'green');
1✔
315
        }
316
    }
317

318
    /**
319
     * Activate an existing user by username or email
320
     *
321
     * @param string|null $username User name to search for (optional)
322
     * @param string|null $email    User email to search for (optional)
323
     */
324
    private function activate(?string $username = null, ?string $email = null): void
1✔
325
    {
326
        $user = $this->findUser('Activate user', $username, $email);
1✔
327

328
        $confirm = $this->prompt('Activate the user ' . $user->username . ' ?', ['y', 'n']);
1✔
329

330
        if ($confirm === 'y') {
1✔
331
            $userModel = model(UserModel::class);
1✔
332

333
            $user->active = 1;
1✔
334
            $userModel->save($user);
1✔
335

336
            $this->write('User "' . $user->username . '" activated', 'green');
1✔
337
        } else {
338
            $this->write('User "' . $user->username . '" activation cancelled', 'yellow');
×
339
        }
340
    }
341

342
    /**
343
     * Deactivate an existing user by username or email
344
     *
345
     * @param string|null $username User name to search for (optional)
346
     * @param string|null $email    User email to search for (optional)
347
     */
348
    private function deactivate(?string $username = null, ?string $email = null): void
1✔
349
    {
350
        $user = $this->findUser('Deactivate user', $username, $email);
1✔
351

352
        $confirm = $this->prompt('Deactivate the user "' . $username . '" ?', ['y', 'n']);
1✔
353

354
        if ($confirm === 'y') {
1✔
355
            $userModel = model(UserModel::class);
1✔
356

357
            $user->active = 0;
1✔
358
            $userModel->save($user);
1✔
359

360
            $this->write('User "' . $user->username . '" deactivated', 'green');
1✔
361
        } else {
362
            $this->write('User "' . $user->username . '" deactivation cancelled', 'yellow');
×
363
        }
364
    }
365

366
    /**
367
     * Change the name of an existing user by username or email
368
     *
369
     * @param string|null $username    User name to search for (optional)
370
     * @param string|null $email       User email to search for (optional)
371
     * @param string|null $newUsername User new name (optional)
372
     */
373
    private function changename(
2✔
374
        ?string $username = null,
375
        ?string $email = null,
376
        ?string $newUsername = null,
377
    ): void {
378
        $user = $this->findUser('Change username', $username, $email);
2✔
379

380
        if ($newUsername === null) {
2✔
381
            $newUsername = $this->prompt('New username', null, $this->validationRules['username']['rules'], config('Auth')->DBGroup);
×
382
        } else {
383
            // Run validation if the user has passed username and/or email via command line
384
            $validation = Services::validation();
2✔
385
            $validation->setRules([
2✔
386
                'username' => $this->validationRules['username'],
2✔
387
            ]);
2✔
388

389
            if (! $validation->run(['username' => $newUsername])) {
2✔
390
                foreach ($validation->getErrors() as $message) {
1✔
391
                    $this->write($message, 'red');
1✔
392
                }
393

394
                throw new CancelException('User name change aborted');
1✔
395
            }
396
        }
397

398
        $userModel = model(UserModel::class);
1✔
399

400
        $oldUsername    = $user->username;
1✔
401
        $user->username = $newUsername;
1✔
402
        $userModel->save($user);
1✔
403

404
        $this->write('Username "' . $oldUsername . '" changed to "' . $newUsername . '"', 'green');
1✔
405
    }
406

407
    /**
408
     * Change the email of an existing user by username or email
409
     *
410
     * @param string|null $username User name to search for (optional)
411
     * @param string|null $email    User email to search for (optional)
412
     * @param string|null $newEmail User new email (optional)
413
     */
414
    private function changeemail(
2✔
415
        ?string $username = null,
416
        ?string $email = null,
417
        ?string $newEmail = null,
418
    ): void {
419
        $user = $this->findUser('Change email', $username, $email);
2✔
420

421
        if ($newEmail === null) {
2✔
422
            $newEmail = $this->prompt('New email', null, $this->validationRules['email']['rules'], config('Auth')->DBGroup);
×
423
        } else {
424
            // Run validation if the user has passed username and/or email via command line
425
            $validation = Services::validation();
2✔
426
            $validation->setRules([
2✔
427
                'email' => $this->validationRules['email'],
2✔
428
            ]);
2✔
429

430
            if (! $validation->run(['email' => $newEmail])) {
2✔
431
                foreach ($validation->getErrors() as $message) {
1✔
432
                    $this->write($message, 'red');
1✔
433
                }
434

435
                throw new CancelException('User email change aborted');
1✔
436
            }
437
        }
438

439
        $userModel = model(UserModel::class);
1✔
440

441
        $user->email = $newEmail;
1✔
442
        $userModel->save($user);
1✔
443

444
        $this->write('Email for "' . $user->username . '" changed to ' . $newEmail, 'green');
1✔
445
    }
446

447
    /**
448
     * Delete an existing user by username or email
449
     *
450
     * @param int         $userid   User id to delete (optional)
451
     * @param string|null $username User name to search for (optional)
452
     * @param string|null $email    User email to search for (optional)
453
     */
454
    private function delete(int $userid = 0, ?string $username = null, ?string $email = null): void
3✔
455
    {
456
        $userModel = model(UserModel::class);
3✔
457

458
        if ($userid !== 0) {
3✔
459
            $user = $userModel->findById($userid);
1✔
460

461
            $this->checkUserExists($user);
1✔
462
        } else {
463
            $user = $this->findUser('Delete user', $username, $email);
2✔
464
        }
465

466
        $confirm = $this->prompt(
2✔
467
            'Delete the user "' . $user->username . '" (' . $user->email . ') ?',
2✔
468
            ['y', 'n'],
2✔
469
        );
2✔
470

471
        if ($confirm === 'y') {
2✔
472
            $userModel->delete($user->id, true);
2✔
473

474
            $this->write('User "' . $user->username . '" deleted', 'green');
2✔
475
        } else {
476
            $this->write('User "' . $user->username . '" deletion cancelled', 'yellow');
×
477
        }
478
    }
479

480
    /**
481
     * @param UserEntity|null $user
482
     */
483
    private function checkUserExists($user): void
16✔
484
    {
485
        if ($user === null) {
16✔
486
            throw new UserNotFoundException("User doesn't exist");
1✔
487
        }
488
    }
489

490
    /**
491
     * Change the password of an existing user by username or email
492
     *
493
     * @param string|null $username User name to search for (optional)
494
     * @param string|null $email    User email to search for (optional)
495
     */
496
    private function password($username = null, $email = null): void
3✔
497
    {
498
        $user = $this->findUser('Change user password', $username, $email);
3✔
499

500
        $confirm = $this->prompt('Set the password for "' . $user->username . '" ?', ['y', 'n']);
3✔
501

502
        if ($confirm === 'y') {
3✔
503
            $password = $this->prompt(
3✔
504
                'Password',
3✔
505
                null,
3✔
506
                $this->validationRules['password']['rules'],
3✔
507
            );
3✔
508
            $passwordConfirm = $this->prompt(
3✔
509
                'Password confirmation',
3✔
510
                null,
3✔
511
                $this->validationRules['password']['rules'],
3✔
512
            );
3✔
513

514
            if ($password !== $passwordConfirm) {
3✔
515
                throw new BadInputException("The passwords don't match");
1✔
516
            }
517

518
            $userModel = model(UserModel::class);
2✔
519

520
            $user->password = $password;
2✔
521
            $userModel->save($user);
2✔
522

523
            $this->write('Password for "' . $user->username . '" set', 'green');
2✔
524
        } else {
525
            $this->write('Password setting for "' . $user->username . '" cancelled', 'yellow');
×
526
        }
527
    }
528

529
    /**
530
     * List users searching by username or email
531
     *
532
     * @param string|null $username User name to search for (optional)
533
     * @param string|null $email    User email to search for (optional)
534
     */
535
    private function list(?string $username = null, ?string $email = null): void
2✔
536
    {
537
        $userModel = model(UserModel::class);
2✔
538
        $userModel
2✔
539
            ->select($this->tables['users'] . '.id as id, username, secret as email')
2✔
540
            ->join(
2✔
541
                $this->tables['identities'],
2✔
542
                $this->tables['users'] . '.id = ' . $this->tables['identities'] . '.user_id',
2✔
543
                'LEFT',
2✔
544
            )
2✔
545
            ->groupStart()
2✔
546
            ->where($this->tables['identities'] . '.type', Session::ID_TYPE_EMAIL_PASSWORD)
2✔
547
            ->orGroupStart()
2✔
548
            ->where($this->tables['identities'] . '.type')
2✔
549
            ->groupEnd()
2✔
550
            ->groupEnd()
2✔
551
            ->asArray();
2✔
552

553
        if ($username !== null) {
2✔
554
            $userModel->like('username', $username);
×
555
        }
556
        if ($email !== null) {
2✔
557
            $userModel->like('secret', $email);
1✔
558
        }
559

560
        $this->write("Id\tUser");
2✔
561

562
        foreach ($userModel->findAll() as $user) {
2✔
563
            $this->write($user['id'] . "\t" . $user['username'] . ' (' . $user['email'] . ')');
2✔
564
        }
565
    }
566

567
    /**
568
     * Add a user by username or email to a group
569
     *
570
     * @param string|null $group    Group to add user to
571
     * @param string|null $username User name to search for (optional)
572
     * @param string|null $email    User email to search for (optional)
573
     */
574
    private function addgroup($group = null, $username = null, $email = null): void
2✔
575
    {
576
        if ($group === null) {
2✔
577
            $group = $this->prompt('Group', null, 'required');
×
578
        }
579

580
        $user = $this->findUser('Add user to group', $username, $email);
2✔
581

582
        $confirm = $this->prompt(
2✔
583
            'Add the user "' . $user->username . '" to the group "' . $group . '" ?',
2✔
584
            ['y', 'n'],
2✔
585
        );
2✔
586

587
        if ($confirm === 'y') {
2✔
588
            $user->addGroup($group);
1✔
589

590
            $this->write('User "' . $user->username . '" added to group "' . $group . '"', 'green');
1✔
591
        } else {
592
            $this->write(
1✔
593
                'Addition of the user "' . $user->username . '" to the group "' . $group . '" cancelled',
1✔
594
                'yellow',
1✔
595
            );
1✔
596
        }
597
    }
598

599
    /**
600
     * Remove a user by username or email from a group
601
     *
602
     * @param string|null $group    Group to remove user from
603
     * @param string|null $username User name to search for (optional)
604
     * @param string|null $email    User email to search for (optional)
605
     */
606
    private function removegroup($group = null, $username = null, $email = null): void
2✔
607
    {
608
        if ($group === null) {
2✔
609
            $group = $this->prompt('Group', null, 'required');
×
610
        }
611

612
        $user = $this->findUser('Remove user from group', $username, $email);
2✔
613

614
        $confirm = $this->prompt(
2✔
615
            'Remove the user "' . $user->username . '" from the group "' . $group . '" ?',
2✔
616
            ['y', 'n'],
2✔
617
        );
2✔
618

619
        if ($confirm === 'y') {
2✔
620
            $user->removeGroup($group);
1✔
621

622
            $this->write('User "' . $user->username . '" removed from group "' . $group . '"', 'green');
1✔
623
        } else {
624
            $this->write('Removal of the user "' . $user->username . '" from the group "' . $group . '" cancelled', 'yellow');
1✔
625
        }
626
    }
627

628
    /**
629
     * Find an existing user by username or email.
630
     *
631
     * @param string      $question Initial question at user prompt
632
     * @param string|null $username User name to search for (optional)
633
     * @param string|null $email    User email to search for (optional)
634
     */
635
    private function findUser($question = '', $username = null, $email = null): UserEntity
15✔
636
    {
637
        if ($username === null && $email === null) {
15✔
638
            $choice = $this->prompt($question . ' by username or email ?', ['u', 'e']);
2✔
639

640
            if ($choice === 'u') {
2✔
641
                $username = $this->prompt('Username', null, 'required');
1✔
642
            } elseif ($choice === 'e') {
1✔
643
                $email = $this->prompt(
1✔
644
                    'Email',
1✔
645
                    null,
1✔
646
                    'required',
1✔
647
                );
1✔
648
            }
649
        }
650

651
        $userModel = model(UserModel::class);
15✔
652
        $userModel
15✔
653
            ->select($this->tables['users'] . '.id as id, username, secret')
15✔
654
            ->join(
15✔
655
                $this->tables['identities'],
15✔
656
                $this->tables['users'] . '.id = ' . $this->tables['identities'] . '.user_id',
15✔
657
                'LEFT',
15✔
658
            )
15✔
659
            ->groupStart()
15✔
660
            ->where($this->tables['identities'] . '.type', Session::ID_TYPE_EMAIL_PASSWORD)
15✔
661
            ->orGroupStart()
15✔
662
            ->where($this->tables['identities'] . '.type')
15✔
663
            ->groupEnd()
15✔
664
            ->groupEnd()
15✔
665
            ->asArray();
15✔
666

667
        $user = null;
15✔
668
        if ($username !== null) {
15✔
669
            $user = $userModel->where('username', $username)->first();
14✔
670
        } elseif ($email !== null) {
1✔
671
            $user = $userModel->where('secret', $email)->first();
1✔
672
        }
673

674
        $this->checkUserExists($user);
15✔
675

676
        return $userModel->findById($user['id']);
14✔
677
    }
678
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc