Skip to content

Commit e75a492

Browse files
authored
Merge pull request #397 from kenjis/refactor-auth-actions
refactor: Auth Actions (action classes are executed in the order set in `Config/Auth::$actions`)
2 parents 716240f + a18f55e commit e75a492

File tree

9 files changed

+160
-102
lines changed

9 files changed

+160
-102
lines changed

docs/auth_actions.md

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,32 +7,35 @@
77
Authentication Actions are a way to group actions that can happen after login or registration.
88
Shield ships with two actions you can use, and makes it simple for you to define your own.
99

10-
1. **Email-based Two Factor Authentication** (Email2FA) will send a 6-digit code to the user's
10+
1. **Email-based Account Activation** (EmailActivate) confirms a new user's email address by
11+
sending them an email with a link they must follow in order to have their account activated.
12+
2. **Email-based Two Factor Authentication** (Email2FA) will send a 6-digit code to the user's
1113
email address that they must confirm before they can continue.
12-
2. **Email-based Account Activation** (EmailActivate) confirms a new user's email address by
13-
sending them an email with a link they must follow in order to have their account activated.
1414

1515
## Configuring Actions
1616

1717
Actions are setup in the `Auth` config file, with the `$actions` variable.
1818

1919
```php
2020
public $actions = [
21-
'login' => null,
2221
'register' => null,
22+
'login' => null,
2323
];
2424
```
2525

2626
To define an action to happen you will specify the class name as the value for the appropriate task:
2727

2828
```php
2929
public $actions = [
30-
'login' => 'CodeIgniter\Shield\Authentication\Actions\Email2FA',
3130
'register' => 'CodeIgniter\Shield\Authentication\Actions\EmailActivator',
31+
'login' => 'CodeIgniter\Shield\Authentication\Actions\Email2FA',
3232
];
3333
```
3434

35-
Once configured, everything should work out of the box. The routes are added with the basic `auth()->routes($routes)`
35+
You must register actions in the order of the actions to be performed.
36+
Once configured, everything should work out of the box.
37+
38+
The routes are added with the basic `auth()->routes($routes)`
3639
call, but can be manually added if you choose not to use this helper method.
3740

3841
```php
@@ -50,8 +53,8 @@ Views for all of these pages are defined in the `Auth` config file, with the `$v
5053
'action_email_2fa' => '\CodeIgniter\Shield\Views\email_2fa_show',
5154
'action_email_2fa_verify' => '\CodeIgniter\Shield\Views\email_2fa_verify',
5255
'action_email_2fa_email' => '\CodeIgniter\Shield\Views\Email\email_2fa_email',
53-
'action_email_activate_email' => '\CodeIgniter\Shield\Views\Email\email_activate_email',
5456
'action_email_activate_show' => '\CodeIgniter\Shield\Views\email_activate_show',
57+
'action_email_activate_email' => '\CodeIgniter\Shield\Views\Email\email_activate_email',
5558
];
5659
```
5760

@@ -61,7 +64,7 @@ While the provided email-based activation and 2FA will work for many sites, othe
6164
needs, like using SMS to verify or something completely different. Actions have only one requirement:
6265
they must implement `CodeIgniter\Shield\Authentication\Actions\ActionInterface`.
6366

64-
The interface defines three methods:
67+
The interface defines three methods for `ActionController`:
6568

6669
**show()** should display the initial page the user lands on immediately after the authentication task,
6770
like login. It will typically display instructions to the user and provide an action to take, like
@@ -77,4 +80,4 @@ and provides feedback. In the `Email2FA` class, it verifies the code against wha
7780
database and either sends them back to the previous form to try again or redirects the user to the
7881
page that a `login` task would have redirected them to anyway.
7982

80-
All methods should return either a `RedirectResponse` or a view string (e.g. using the `view()` function).
83+
All methods should return either a `Response` or a view string (e.g. using the `view()` function).

docs/quickstart.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,8 @@ Turned off by default, Shield's Email-based 2FA can be enabled by specifying the
110110

111111
```php
112112
public array $actions = [
113-
'login' => 'CodeIgniter\Shield\Authentication\Actions\Email2FA',
114113
'register' => null,
114+
'login' => 'CodeIgniter\Shield\Authentication\Actions\Email2FA',
115115
];
116116
```
117117

@@ -121,8 +121,8 @@ By default, once a user registers they have an active account that can be used.
121121

122122
```php
123123
public array $actions = [
124-
'login' => null,
125124
'register' => 'CodeIgniter\Shield\Authentication\Actions\EmailActivator',
125+
'login' => null,
126126
];
127127
```
128128

src/Authentication/Actions/ActionInterface.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use CodeIgniter\HTTP\IncomingRequest;
88
use CodeIgniter\HTTP\Response;
9+
use CodeIgniter\Shield\Entities\User;
910

1011
/**
1112
* Interface ActionInterface
@@ -47,4 +48,11 @@ public function verify(IncomingRequest $request);
4748
* E.g., 'email_2fa', 'email_activate'.
4849
*/
4950
public function getType(): string;
51+
52+
/**
53+
* Creates an identity for the action of the user.
54+
*
55+
* @return string secret
56+
*/
57+
public function createIdentity(User $user): string;
5058
}

src/Authentication/Actions/Email2FA.php

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use CodeIgniter\HTTP\RedirectResponse;
99
use CodeIgniter\Shield\Authentication\Authenticators\Session;
1010
use CodeIgniter\Shield\Entities\User;
11+
use CodeIgniter\Shield\Entities\UserIdentity;
1112
use CodeIgniter\Shield\Exceptions\RuntimeException;
1213
use CodeIgniter\Shield\Models\UserIdentityModel;
1314

@@ -94,13 +95,20 @@ public function handle(IncomingRequest $request)
9495
*/
9596
public function verify(IncomingRequest $request)
9697
{
97-
$token = $request->getPost('token');
98-
9998
/** @var Session $authenticator */
10099
$authenticator = auth('session')->getAuthenticator();
101100

101+
$postedToken = $request->getPost('token');
102+
103+
$user = $authenticator->getPendingUser();
104+
if ($user === null) {
105+
throw new RuntimeException('Cannot get the pending login User.');
106+
}
107+
108+
$identity = $this->getIdentity($user);
109+
102110
// Token mismatch? Let them try again...
103-
if (! $authenticator->checkAction($this->type, $token)) {
111+
if (! $authenticator->checkAction($identity, $postedToken)) {
104112
session()->setFlashdata('error', lang('Auth.invalid2FAToken'));
105113

106114
return view(setting('Auth.views')['action_email_2fa_verify']);
@@ -111,21 +119,21 @@ public function verify(IncomingRequest $request)
111119
}
112120

113121
/**
114-
* Called from `Session::attempt()`.
122+
* Creates an identity for the action of the user.
123+
*
124+
* @return string secret
115125
*/
116-
public function afterLogin(User $user): void
117-
{
118-
$this->createIdentity($user);
119-
}
120-
121-
final protected function createIdentity(User $user): void
126+
public function createIdentity(User $user): string
122127
{
123128
/** @var UserIdentityModel $identityModel */
124129
$identityModel = model(UserIdentityModel::class);
125130

131+
// Delete any previous identities for action
132+
$identityModel->deleteIdentitiesByType($user, $this->type);
133+
126134
$generator = static fn (): string => random_string('nozero', 6);
127135

128-
$identityModel->createCodeIdentity(
136+
return $identityModel->createCodeIdentity(
129137
$user,
130138
[
131139
'type' => $this->type,
@@ -136,6 +144,23 @@ final protected function createIdentity(User $user): void
136144
);
137145
}
138146

147+
/**
148+
* Returns an identity for the action of the user.
149+
*/
150+
private function getIdentity(User $user): ?UserIdentity
151+
{
152+
/** @var UserIdentityModel $identityModel */
153+
$identityModel = model(UserIdentityModel::class);
154+
155+
return $identityModel->getIdentityByType(
156+
$user,
157+
$this->type
158+
);
159+
}
160+
161+
/**
162+
* Returns the string type of the action class.
163+
*/
139164
public function getType(): string
140165
{
141166
return $this->type;

src/Authentication/Actions/EmailActivator.php

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use CodeIgniter\HTTP\Response;
1111
use CodeIgniter\Shield\Authentication\Authenticators\Session;
1212
use CodeIgniter\Shield\Entities\User;
13+
use CodeIgniter\Shield\Entities\UserIdentity;
1314
use CodeIgniter\Shield\Exceptions\LogicException;
1415
use CodeIgniter\Shield\Exceptions\RuntimeException;
1516
use CodeIgniter\Shield\Models\UserIdentityModel;
@@ -77,13 +78,20 @@ public function handle(IncomingRequest $request)
7778
*/
7879
public function verify(IncomingRequest $request)
7980
{
80-
$token = $request->getVar('token');
81-
8281
/** @var Session $authenticator */
8382
$authenticator = auth('session')->getAuthenticator();
8483

84+
$postedToken = $request->getVar('token');
85+
86+
$user = $authenticator->getPendingUser();
87+
if ($user === null) {
88+
throw new RuntimeException('Cannot get the pending login User.');
89+
}
90+
91+
$identity = $this->getIdentity($user);
92+
8593
// No match - let them try again.
86-
if (! $authenticator->checkAction($this->type, $token)) {
94+
if (! $authenticator->checkAction($identity, $postedToken)) {
8795
session()->setFlashdata('error', lang('Auth.invalidActivateToken'));
8896

8997
return view(setting('Auth.views')['action_email_activate_show']);
@@ -100,18 +108,18 @@ public function verify(IncomingRequest $request)
100108
}
101109

102110
/**
103-
* Called from `RegisterController::registerAction()`
111+
* Creates an identity for the action of the user.
112+
*
113+
* @return string secret
104114
*/
105-
public function afterRegister(User $user): void
106-
{
107-
$this->createIdentity($user);
108-
}
109-
110-
final protected function createIdentity(User $user): string
115+
public function createIdentity(User $user): string
111116
{
112117
/** @var UserIdentityModel $identityModel */
113118
$identityModel = model(UserIdentityModel::class);
114119

120+
// Delete any previous identities for action
121+
$identityModel->deleteIdentitiesByType($user, $this->type);
122+
115123
$generator = static fn (): string => random_string('nozero', 6);
116124

117125
return $identityModel->createCodeIdentity(
@@ -125,6 +133,23 @@ final protected function createIdentity(User $user): string
125133
);
126134
}
127135

136+
/**
137+
* Returns an identity for the action of the user.
138+
*/
139+
private function getIdentity(User $user): ?UserIdentity
140+
{
141+
/** @var UserIdentityModel $identityModel */
142+
$identityModel = model(UserIdentityModel::class);
143+
144+
return $identityModel->getIdentityByType(
145+
$user,
146+
$this->type
147+
);
148+
}
149+
150+
/**
151+
* Returns the string type of the action class.
152+
*/
128153
public function getType(): string
129154
{
130155
return $this->type;

0 commit comments

Comments
 (0)