Skip to content

Commit d136ff7

Browse files
authored
Merge pull request #126 from kenjis/fix-events-trigger
fix: Event triggers
2 parents 9555b45 + 3e65c27 commit d136ff7

File tree

17 files changed

+170
-74
lines changed

17 files changed

+170
-74
lines changed

docs/2 - authentication.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ if($result->isOK()) {
9797
}
9898
```
9999

100-
If the attempt fails a `failedLoginAttempt` event is triggered with the credentials array as
100+
If the attempt fails a `failedLogin` event is triggered with the credentials array as
101101
the only parameter. Whether or not they pass, a login attempt is recorded in the `auth_logins` table.
102102

103103
If `allowRemembering` is `true` in the `Auth` config file, you can tell the Session authenticator

docs/5 - events.md

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,24 @@ When you want to respond to an event that Shield publishes, you will need to add
88

99
## Event List
1010

11-
#### didRegister
11+
#### register
1212

1313
Triggered when a new user has registered in the system. It's only argument is the `User` entity itself.
1414

1515
```php
16-
Events::trigger('didRegister', $user);
16+
Events::trigger('register', $user);
1717

18-
Events::on('didRegister', 'SomeLibrary::handleRegister');
18+
Events::on('register', 'SomeLibrary::handleRegister');
1919
```
2020

21-
#### didLogin
21+
#### login
2222

2323
Fired immediately after a successful login. The only argument is the `User` entity.
2424

2525
```php
26-
Events::trigger('didLogin', $user);
26+
Events::trigger('login', $user);
2727

28-
Events::on('didLogin', 'SomeLibrary::handleLogin');
28+
Events::on('login', 'SomeLibrary::handleLogin');
2929
```
3030

3131
#### failedLogin
@@ -42,6 +42,9 @@ Events::on('failedLogin', function($credentials) {
4242
});
4343

4444
// Outputs:
45-
4645
['email' => 'foo@example.com'];
4746
```
47+
48+
#### logout
49+
50+
Fired immediately after a successful logout. The only argument is the `User` entity.

src/Auth.php

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,17 @@
1010
use CodeIgniter\Shield\Models\UserModel;
1111

1212
/**
13-
* AuthenticatorInterface:
14-
*
13+
* @method void activateUser(User $user) [Session]
1514
* @method Result attempt(array $credentials)
1615
* @method Result check(array $credentials)
16+
* @method bool checkAction(string $token, string $type) [Session]
1717
* @method User|null getUser()
1818
* @method bool loggedIn()
1919
* @method bool login(User $user)
2020
* @method void loginById($userId)
2121
* @method bool logout()
2222
* @method void recordActiveDate()
23-
*
24-
* Authenticators\Session:
25-
* @method $this remember(bool $shouldRemember = true)
23+
* @method $this remember(bool $shouldRemember = true) [Session]
2624
*/
2725
class Auth
2826
{

src/Authentication/Actions/Email2FA.php

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use CodeIgniter\HTTP\IncomingRequest;
66
use CodeIgniter\HTTP\RedirectResponse;
7+
use CodeIgniter\Shield\Authentication\Authenticators\Session;
78
use CodeIgniter\Shield\Exceptions\RuntimeException;
89
use CodeIgniter\Shield\Models\UserIdentityModel;
910

@@ -94,26 +95,18 @@ public function handle(IncomingRequest $request)
9495
*/
9596
public function verify(IncomingRequest $request)
9697
{
97-
$token = $request->getPost('token');
98-
$user = auth()->user();
99-
$identity = $user->getIdentity('email_2fa');
98+
$token = $request->getPost('token');
99+
100+
/** @var Session $authenticator */
101+
$authenticator = auth('session')->getAuthenticator();
100102

101103
// Token mismatch? Let them try again...
102-
if (empty($token) || $token !== $identity->secret) {
104+
if (! $authenticator->checkAction('email_2fa', $token)) {
103105
session()->setFlashdata('error', lang('Auth.invalid2FAToken'));
104106

105107
return view(setting('Auth.views')['action_email_2fa_verify']);
106108
}
107109

108-
/** @var UserIdentityModel $identityModel */
109-
$identityModel = model(UserIdentityModel::class);
110-
111-
// On success - remove the identity and clean up session
112-
$identityModel->deleteIdentitiesByType($user->getAuthId(), 'email_2fa');
113-
114-
// Clean up our session
115-
session()->remove('auth_action');
116-
117110
// Get our login redirect url
118111
return redirect()->to(config('Auth')->loginRedirect());
119112
}

src/Authentication/Actions/EmailActivator.php

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
use CodeIgniter\Exceptions\PageNotFoundException;
66
use CodeIgniter\HTTP\IncomingRequest;
77
use CodeIgniter\HTTP\RedirectResponse;
8+
use CodeIgniter\Shield\Authentication\Authenticators\Session;
9+
use CodeIgniter\Shield\Entities\User;
810
use CodeIgniter\Shield\Exceptions\LogicException;
911
use CodeIgniter\Shield\Exceptions\RuntimeException;
1012
use CodeIgniter\Shield\Models\UserIdentityModel;
@@ -81,30 +83,25 @@ public function handle(IncomingRequest $request)
8183
*/
8284
public function verify(IncomingRequest $request)
8385
{
84-
$token = $request->getVar('token');
85-
$user = auth()->user();
86-
$identity = $user->getIdentity('email_activate');
86+
$token = $request->getVar('token');
87+
88+
$auth = auth('session');
89+
90+
/** @var Session $authenticator */
91+
$authenticator = $auth->getAuthenticator();
8792

8893
// No match - let them try again.
89-
if ($identity->secret !== $token) {
94+
if (! $authenticator->checkAction('email_activate', $token)) {
9095
session()->setFlashdata('error', lang('Auth.invalidActivateToken'));
9196

9297
return view(setting('Auth.views')['action_email_activate_show']);
9398
}
9499

95-
/** @var UserIdentityModel $identityModel */
96-
$identityModel = model(UserIdentityModel::class);
97-
98-
// Remove the identity
99-
$identityModel->delete($identity->id);
100+
/** @var User $user */
101+
$user = $auth->user();
100102

101103
// Set the user active now
102-
$provider = auth()->getProvider();
103-
$user->active = true;
104-
$provider->save($user);
105-
106-
// Clean up our session
107-
unset($_SESSION['auth_action']);
104+
$auth->activateUser($user);
108105

109106
// Get our login redirect url
110107
return redirect()->to(config('Auth')->loginRedirect());

src/Authentication/AuthenticatorInterface.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public function loggedIn(): bool;
3232
*
3333
* @see https://codeigniter4.github.io/CodeIgniter4/extending/authentication.html
3434
*/
35-
public function login(User $user): bool;
35+
public function login(User $user): void;
3636

3737
/**
3838
* Logs a user in based on their ID.

src/Authentication/Authenticators/AccessTokens.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,11 +141,9 @@ public function loggedIn(): bool
141141
/**
142142
* Logs the given user in by saving them to the class.
143143
*/
144-
public function login(User $user): bool
144+
public function login(User $user): void
145145
{
146146
$this->user = $user;
147-
148-
return true;
149147
}
150148

151149
/**

src/Authentication/Authenticators/Session.php

Lines changed: 90 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@
1010
use CodeIgniter\Shield\Authentication\AuthenticatorInterface;
1111
use CodeIgniter\Shield\Authentication\Passwords;
1212
use CodeIgniter\Shield\Entities\User;
13+
use CodeIgniter\Shield\Exceptions\LogicException;
1314
use CodeIgniter\Shield\Models\LoginModel;
1415
use CodeIgniter\Shield\Models\RememberModel;
16+
use CodeIgniter\Shield\Models\UserIdentityModel;
1517
use CodeIgniter\Shield\Models\UserModel;
1618
use CodeIgniter\Shield\Result;
1719
use Exception;
@@ -25,19 +27,23 @@ class Session implements AuthenticatorInterface
2527
*/
2628
protected UserModel $provider;
2729

30+
/**
31+
* The user logged in
32+
*/
2833
protected ?User $user = null;
29-
protected LoginModel $loginModel;
3034

3135
/**
3236
* Should the user be remembered?
3337
*/
3438
protected bool $shouldRemember = false;
3539

40+
protected LoginModel $loginModel;
3641
protected RememberModel $rememberModel;
3742

3843
public function __construct(UserModel $provider)
3944
{
4045
helper('setting');
46+
4147
$this->provider = $provider;
4248
$this->loginModel = model(LoginModel::class); // @phpstan-ignore-line
4349
$this->rememberModel = model(RememberModel::class); // @phpstan-ignore-line
@@ -58,6 +64,8 @@ public function remember(bool $shouldRemember = true): self
5864
/**
5965
* Attempts to authenticate a user with the given $credentials.
6066
* Logs the user in with a successful check.
67+
*
68+
* @phpstan-param array{email?: string, username?: string, password?: string} $credentials
6169
*/
6270
public function attempt(array $credentials): Result
6371
{
@@ -76,7 +84,8 @@ public function attempt(array $credentials): Result
7684

7785
// Fire an event on failure so devs have the chance to
7886
// let them know someone attempted to login to their account
79-
Events::trigger('failedLoginAttempt', $credentials);
87+
unset($credentials['password']);
88+
Events::trigger('failedLogin', $credentials);
8089

8190
return $result;
8291
}
@@ -88,9 +97,67 @@ public function attempt(array $credentials): Result
8897

8998
$this->recordLoginAttempt($credentials, true, $ipAddress, $userAgent, $user->getAuthId());
9099

100+
// If an action has been defined for login, start it up.
101+
$actionClass = setting('Auth.actions')['login'] ?? null;
102+
if (! empty($actionClass)) {
103+
session()->set('auth_action', $actionClass);
104+
} else {
105+
$this->completeLogin($user);
106+
}
107+
91108
return $result;
92109
}
93110

111+
/**
112+
* Check token in Action
113+
*
114+
* @param string $type Action type. 'email_2fa' or 'email_activate'
115+
* @param string $token Token to check
116+
*/
117+
public function checkAction(string $type, string $token): bool
118+
{
119+
$user = $this->loggedIn() ? $this->getUser() : null;
120+
121+
if ($user === null) {
122+
throw new LogicException('Cannot get the User.');
123+
}
124+
125+
$identity = $user->getIdentity($type);
126+
127+
if (empty($token) || $token !== $identity->secret) {
128+
return false;
129+
}
130+
131+
/** @var UserIdentityModel $identityModel */
132+
$identityModel = model(UserIdentityModel::class);
133+
134+
// On success - remove the identity and clean up session
135+
$identityModel->deleteIdentitiesByType($user->getAuthId(), $type);
136+
137+
// Clean up our session
138+
session()->remove('auth_action');
139+
140+
$this->user = $user;
141+
142+
$this->completeLogin($user);
143+
144+
return true;
145+
}
146+
147+
private function completeLogin(User $user): void
148+
{
149+
// a successful login
150+
Events::trigger('login', $user);
151+
}
152+
153+
/**
154+
* Activate a User
155+
*/
156+
public function activateUser(User $user): void
157+
{
158+
$this->provider->activate($user);
159+
}
160+
94161
/**
95162
* @param int|string|null $userId
96163
*/
@@ -113,6 +180,8 @@ private function recordLoginAttempt(
113180
/**
114181
* Checks a user's $credentials to see if they match an
115182
* existing user.
183+
*
184+
* @phpstan-param array{email?: string, username?: string, password?: string} $credentials
116185
*/
117186
public function check(array $credentials): Result
118187
{
@@ -126,7 +195,7 @@ public function check(array $credentials): Result
126195

127196
// Remove the password from credentials so we can
128197
// check afterword.
129-
$givenPassword = $credentials['password'] ?? null;
198+
$givenPassword = $credentials['password'];
130199
unset($credentials['password']);
131200

132201
// Find the existing user
@@ -243,30 +312,40 @@ private function checkRememberMeToken(string $remember)
243312
return $token;
244313
}
245314

246-
/**
247-
* Logs the given user in.
248-
*/
249-
public function login(User $user): bool
315+
private function startLogin(User $user): void
250316
{
251-
$this->user = $user;
252-
253317
// Update the user's last used date on their password identity.
254-
$this->user->touchIdentity($this->user->getEmailIdentity());
318+
$user->touchIdentity($user->getEmailIdentity());
255319

256320
// Regenerate the session ID to help protect against session fixation
257321
if (ENVIRONMENT !== 'testing') {
258322
session()->regenerate();
259323
}
260324

261325
// Let the session know we're logged in
262-
session()->set(setting('Auth.sessionConfig')['field'], $this->user->getAuthId());
326+
session()->set(setting('Auth.sessionConfig')['field'], $user->getAuthId());
263327

264328
/** @var Response $response */
265329
$response = service('response');
266330

267331
// When logged in, ensure cache control headers are in place
268332
$response->noCache();
333+
}
269334

335+
/**
336+
* Logs the given user in.
337+
*/
338+
public function login(User $user): void
339+
{
340+
$this->user = $user;
341+
342+
$this->startLogin($user);
343+
344+
$this->processRemember();
345+
}
346+
347+
private function processRemember()
348+
{
270349
if ($this->shouldRemember && setting('Auth.sessionConfig')['allowRemembering']) {
271350
$this->rememberUser($this->user->getAuthId());
272351

@@ -285,9 +364,6 @@ public function login(User $user): bool
285364
if (random_int(1, 100) <= 20) {
286365
$this->rememberModel->purgeOldRememberTokens();
287366
}
288-
289-
// Trigger login event, in case anyone cares
290-
return Events::trigger('login', $user);
291367
}
292368

293369
/**

src/Authentication/Traits/HasAccessTokens.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ trait HasAccessTokens
2222

2323
/**
2424
* Generates a new personal access token for this user.
25+
*
26+
* @param string $name Token name
27+
* @param string[] $scopes Permissions the token grants
2528
*/
2629
public function generateAccessToken(string $name, array $scopes = ['*']): AccessToken
2730
{

0 commit comments

Comments
 (0)