Skip to content

Commit 2f01a86

Browse files
authored
Merge pull request #391 from kenjis/fix-auth-actions
fix: Session auth action checks
2 parents f9f82dd + 7f8ad99 commit 2f01a86

File tree

4 files changed

+179
-16
lines changed

4 files changed

+179
-16
lines changed

src/Authentication/Authenticators/Session.php

Lines changed: 81 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,17 @@ class Session implements AuthenticatorInterface
3535
*/
3636
public const ID_TYPE_USERNAME = 'username';
3737

38+
// Identity types
3839
public const ID_TYPE_EMAIL_PASSWORD = 'email_password';
3940
public const ID_TYPE_MAGIC_LINK = 'magic-link';
4041
public const ID_TYPE_EMAIL_2FA = 'email_2fa';
4142
public const ID_TYPE_EMAIL_ACTIVATE = 'email_activate';
42-
private const STATE_UNKNOWN = 0;
43-
private const STATE_ANONYMOUS = 1;
44-
private const STATE_PENDING = 2;
45-
private const STATE_LOGGED_IN = 3;
43+
44+
// User states
45+
private const STATE_UNKNOWN = 0; // Not checked yet.
46+
private const STATE_ANONYMOUS = 1;
47+
private const STATE_PENDING = 2; // 2FA or Activation required.
48+
private const STATE_LOGGED_IN = 3;
4649

4750
/**
4851
* The persistence engine
@@ -149,6 +152,9 @@ public function attempt(array $credentials): Result
149152
// Update the user's last used date on their password identity.
150153
$user->touchIdentity($user->getEmailIdentity());
151154

155+
// Check ID_TYPE_EMAIL_ACTIVATE identity
156+
$hasEmailActivate = $this->setAuthActionEmailActivate();
157+
152158
// If an action has been defined for login, start it up.
153159
$hasAction = $this->startUpAction('login', $user);
154160

@@ -158,7 +164,7 @@ public function attempt(array $credentials): Result
158164

159165
$this->issueRememberMeToken();
160166

161-
if (! $hasAction) {
167+
if (! $hasAction && ! $hasEmailActivate) {
162168
$this->completeLogin($user);
163169
}
164170

@@ -405,16 +411,52 @@ private function checkUserState(): void
405411
$this->userState = self::STATE_ANONYMOUS;
406412
}
407413

414+
/**
415+
* Has Auth Action?
416+
*/
417+
public function hasAction(): bool
418+
{
419+
// Check the Session
420+
if ($this->getSessionKey('auth_action')) {
421+
return true;
422+
}
423+
424+
// Check the database
425+
return $this->setAuthAction();
426+
}
427+
408428
/**
409429
* Gets identities for action from database, and set session.
430+
*
431+
* @return bool true if the action is set in the session.
410432
*/
411-
private function setAuthAction(): void
433+
private function setAuthAction(): bool
412434
{
413-
// Get identities for action
414-
$identities = $this->getIdentitiesForAction();
435+
if ($this->user === null) {
436+
return false;
437+
}
438+
439+
// First, check ID_TYPE_EMAIL_ACTIVATE identity
440+
$hasAction = $this->setAuthActionEmailActivate();
441+
if ($hasAction) {
442+
return true;
443+
}
444+
445+
// Next, check ID_TYPE_EMAIL_2FA identity
446+
return $this->setAuthActionEmail2FA();
447+
}
415448

416-
// Having an action?
417-
foreach ($identities as $identity) {
449+
/**
450+
* @return bool true if the action is set in the session.
451+
*/
452+
private function setAuthActionEmailActivate(): bool
453+
{
454+
$identity = $this->userIdentityModel->getIdentityByType(
455+
$this->user,
456+
self::ID_TYPE_EMAIL_ACTIVATE
457+
);
458+
459+
if ($identity) {
418460
$actionClass = setting('Auth.actions')[$identity->name];
419461

420462
if ($actionClass) {
@@ -423,9 +465,37 @@ private function setAuthAction(): void
423465
$this->setSessionKey('auth_action', $actionClass);
424466
$this->setSessionKey('auth_action_message', $identity->extra);
425467

426-
return;
468+
return true;
469+
}
470+
}
471+
472+
return false;
473+
}
474+
475+
/**
476+
* @return bool true if the action is set in the session.
477+
*/
478+
private function setAuthActionEmail2FA(): bool
479+
{
480+
$identity = $this->userIdentityModel->getIdentityByType(
481+
$this->user,
482+
self::ID_TYPE_EMAIL_2FA
483+
);
484+
485+
if ($identity) {
486+
$actionClass = setting('Auth.actions')[$identity->name];
487+
488+
if ($actionClass) {
489+
$this->userState = self::STATE_PENDING;
490+
491+
$this->setSessionKey('auth_action', $actionClass);
492+
$this->setSessionKey('auth_action_message', $identity->extra);
493+
494+
return true;
427495
}
428496
}
497+
498+
return false;
429499
}
430500

431501
/**

src/Controllers/LoginController.php

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,14 @@ public function loginView()
2323
return redirect()->to(config('Auth')->loginRedirect());
2424
}
2525

26+
/** @var Session $authenticator */
27+
$authenticator = auth('session')->getAuthenticator();
28+
29+
// If an action has been defined, start it up.
30+
if ($authenticator->hasAction()) {
31+
return redirect()->route('auth-action-show');
32+
}
33+
2634
return view(setting('Auth.views')['login']);
2735
}
2836

@@ -44,20 +52,20 @@ public function loginAction(): RedirectResponse
4452
$credentials['password'] = $this->request->getPost('password');
4553
$remember = (bool) $this->request->getPost('remember');
4654

55+
/** @var Session $authenticator */
56+
$authenticator = auth('session')->getAuthenticator();
57+
4758
// Attempt to login
48-
$result = auth('session')->remember($remember)->attempt($credentials);
59+
$result = $authenticator->remember($remember)->attempt($credentials);
4960
if (! $result->isOK()) {
5061
return redirect()->route('login')->withInput()->with('error', $result->reason());
5162
}
5263

53-
// custom bit of information
54-
$user = $result->extraInfo();
5564
/** @var Session $authenticator */
5665
$authenticator = auth('session')->getAuthenticator();
5766

5867
// If an action has been defined for login, start it up.
59-
$hasAction = $authenticator->startUpAction('login', $user);
60-
if ($hasAction) {
68+
if ($authenticator->hasAction()) {
6169
return redirect()->route('auth-action-show')->withCookies();
6270
}
6371

src/Controllers/RegisterController.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,14 @@ public function registerView()
3939
->with('error', lang('Auth.registerDisabled'));
4040
}
4141

42+
/** @var Session $authenticator */
43+
$authenticator = auth('session')->getAuthenticator();
44+
45+
// If an action has been defined, start it up.
46+
if ($authenticator->hasAction()) {
47+
return redirect()->route('auth-action-show');
48+
}
49+
4250
return view(setting('Auth.views')['register']);
4351
}
4452

tests/Controllers/RegisterTest.php

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,83 @@ public function testRegisterRedirectsToActionIfDefined(): void
147147
]);
148148
}
149149

150+
public function testRegisteredButNotActivatedAndLogin(): void
151+
{
152+
// Ensure our action is defined
153+
$config = config('Auth');
154+
$config->actions['register'] = EmailActivator::class;
155+
Factories::injectMock('config', 'Auth', $config);
156+
157+
$result = $this->post('/register', [
158+
'email' => 'foo@example.com',
159+
'username' => 'foo',
160+
'password' => 'abkdhflkjsdflkjasd;lkjf',
161+
'password_confirm' => 'abkdhflkjsdflkjasd;lkjf',
162+
]);
163+
164+
// Should have been redirected to the action's page.
165+
$result->assertRedirectTo('/auth/a/show');
166+
167+
// Not activated yet, but login again.
168+
$result = $this->withSession()->get('/login');
169+
170+
// Should have been redirected to the action's page.
171+
$result->assertRedirectTo('/auth/a/show');
172+
}
173+
174+
public function testRegisteredButNotActivatedAndRegisterAgain(): void
175+
{
176+
// Ensure our action is defined
177+
$config = config('Auth');
178+
$config->actions['register'] = EmailActivator::class;
179+
Factories::injectMock('config', 'Auth', $config);
180+
181+
$password = 'abkdhflkjsdflkjasd;lkjf';
182+
183+
$result = $this->post('/register', [
184+
'email' => 'foo@example.com',
185+
'username' => 'foo',
186+
'password' => $password,
187+
'password_confirm' => $password,
188+
]);
189+
190+
// Should have been redirected to the action's page.
191+
$result->assertRedirectTo('/auth/a/show');
192+
193+
// Not activated yet, but register again.
194+
$result = $this->withSession()->get('/register');
195+
196+
// Should have been redirected to the action's page.
197+
$result->assertRedirectTo('/auth/a/show');
198+
}
199+
200+
public function testRegisteredAndSessionExpiredAndLogin(): void
201+
{
202+
// Ensure our action is defined
203+
$config = config('Auth');
204+
$config->actions['register'] = EmailActivator::class;
205+
Factories::injectMock('config', 'Auth', $config);
206+
207+
$result = $this->post('/register', [
208+
'email' => 'foo@example.com',
209+
'username' => 'foo',
210+
'password' => 'abkdhflkjsdflkjasd;lkjf',
211+
'password_confirm' => 'abkdhflkjsdflkjasd;lkjf',
212+
]);
213+
214+
// Should have been redirected to the action's page.
215+
$result->assertRedirectTo('/auth/a/show');
216+
217+
// Not activated yet, and do not set Session (= the session is expired) and login again.
218+
$result = $this->post('/login', [
219+
'email' => 'foo@example.com',
220+
'password' => 'abkdhflkjsdflkjasd;lkjf',
221+
]);
222+
223+
// Should have been redirected to the action's page.
224+
$result->assertRedirectTo('/auth/a/show');
225+
}
226+
150227
public function testRegisterRedirectsIfLoggedIn(): void
151228
{
152229
// log them in

0 commit comments

Comments
 (0)