|
16 | 16 | use CodeIgniter\Config\Factories; |
17 | 17 | use CodeIgniter\Exceptions\PageNotFoundException; |
18 | 18 | use CodeIgniter\I18n\Time; |
| 19 | +use CodeIgniter\Shield\Authentication\Actions\Email2FA; |
19 | 20 | use CodeIgniter\Shield\Authentication\Actions\EmailActivator; |
20 | 21 | use CodeIgniter\Shield\Authentication\Authenticators\Session; |
21 | 22 | use CodeIgniter\Shield\Entities\User; |
|
24 | 25 | use CodeIgniter\Test\DatabaseTestTrait; |
25 | 26 | use CodeIgniter\Test\FeatureTestTrait; |
26 | 27 | use Config\Services; |
| 28 | +use Tests\Support\AdminEmail2FA; |
27 | 29 | use Tests\Support\AdminEmailActivator; |
28 | 30 | use Tests\Support\FakeUser; |
29 | 31 | use Tests\Support\TestCase; |
@@ -175,6 +177,84 @@ public function testMagicLinkVerifyPendingConditionalRegistrationActivation(): v |
175 | 177 | $this->assertFalse(auth()->loggedIn()); |
176 | 178 | } |
177 | 179 |
|
| 180 | + public function testMagicLinkVerifyStartsLoginAction(): void |
| 181 | + { |
| 182 | + setting('Auth.actions', ['login' => Email2FA::class, 'register' => null]); |
| 183 | + |
| 184 | + /** @var User $user */ |
| 185 | + $user = fake(UserModel::class); |
| 186 | + $user->createEmailIdentity(['email' => 'foo@example.com', 'password' => 'secret123']); |
| 187 | + |
| 188 | + $this->insertMagicLinkIdentity($user, 'validtoken123'); |
| 189 | + |
| 190 | + $result = $this->get(route_to('verify-magic-link') . '?token=validtoken123'); |
| 191 | + |
| 192 | + $result->assertRedirect(); |
| 193 | + $this->assertSame(site_url('/auth/a/show'), $result->getRedirectUrl()); |
| 194 | + $this->assertPendingLoginAction($user, Email2FA::class); |
| 195 | + $this->seeInDatabase(config('Auth')->tables['identities'], [ |
| 196 | + 'user_id' => $user->id, |
| 197 | + 'type' => Session::ID_TYPE_EMAIL_2FA, |
| 198 | + 'name' => 'login', |
| 199 | + ]); |
| 200 | + $result->assertSessionMissing('magicLogin'); |
| 201 | + $this->assertFalse(auth()->loggedIn()); |
| 202 | + |
| 203 | + $identity = model(UserIdentityModel::class)->getIdentityByType($user, Session::ID_TYPE_EMAIL_2FA); |
| 204 | + $this->assertNotNull($identity); |
| 205 | + |
| 206 | + $result = $this->withSession()->post('/auth/a/verify', [ |
| 207 | + 'token' => $identity->secret, |
| 208 | + ]); |
| 209 | + |
| 210 | + $result->assertRedirectTo(config('Auth')->loginRedirect()); |
| 211 | + $result->assertSessionHas('user', ['id' => $user->id]); |
| 212 | + $result->assertSessionHas('magicLogin', true); |
| 213 | + $this->assertTrue(auth()->loggedIn()); |
| 214 | + } |
| 215 | + |
| 216 | + public function testMagicLinkVerifyStartsConditionalLoginActionWhenItApplies(): void |
| 217 | + { |
| 218 | + setting('Auth.actions', ['login' => AdminEmail2FA::class, 'register' => null]); |
| 219 | + |
| 220 | + /** @var User $user */ |
| 221 | + $user = fake(UserModel::class); |
| 222 | + $user->addGroup('admin'); |
| 223 | + $user->createEmailIdentity(['email' => 'foo@example.com', 'password' => 'secret123']); |
| 224 | + |
| 225 | + $this->insertMagicLinkIdentity($user, 'validtoken123'); |
| 226 | + |
| 227 | + $result = $this->get(route_to('verify-magic-link') . '?token=validtoken123'); |
| 228 | + |
| 229 | + $result->assertRedirect(); |
| 230 | + $this->assertSame(site_url('/auth/a/show'), $result->getRedirectUrl()); |
| 231 | + $this->assertPendingLoginAction($user, AdminEmail2FA::class); |
| 232 | + $result->assertSessionMissing('magicLogin'); |
| 233 | + $this->assertFalse(auth()->loggedIn()); |
| 234 | + } |
| 235 | + |
| 236 | + public function testMagicLinkVerifySkipsConditionalLoginActionWhenItDoesNotApply(): void |
| 237 | + { |
| 238 | + setting('Auth.actions', ['login' => AdminEmail2FA::class, 'register' => null]); |
| 239 | + |
| 240 | + /** @var User $user */ |
| 241 | + $user = fake(UserModel::class); |
| 242 | + $user->createEmailIdentity(['email' => 'foo@example.com', 'password' => 'secret123']); |
| 243 | + |
| 244 | + $this->insertMagicLinkIdentity($user, 'validtoken123'); |
| 245 | + |
| 246 | + $result = $this->get(route_to('verify-magic-link') . '?token=validtoken123'); |
| 247 | + |
| 248 | + $result->assertRedirectTo(config('Auth')->loginRedirect()); |
| 249 | + $result->assertSessionHas('user', ['id' => $user->id]); |
| 250 | + $result->assertSessionMissing('auth_action'); |
| 251 | + $this->assertTrue(auth()->loggedIn()); |
| 252 | + $this->dontSeeInDatabase(config('Auth')->tables['identities'], [ |
| 253 | + 'user_id' => $user->id, |
| 254 | + 'type' => Session::ID_TYPE_EMAIL_2FA, |
| 255 | + ]); |
| 256 | + } |
| 257 | + |
178 | 258 | public function testBackToLoginLinkOnPage(): void |
179 | 259 | { |
180 | 260 | $result = $this->get('/login/magic-link'); |
@@ -252,4 +332,26 @@ public function testMagicLinkVerifyReturns404ForRobotUserAgent(): void |
252 | 332 |
|
253 | 333 | $this->get(route_to('verify-magic-link') . '?token=validtoken123'); |
254 | 334 | } |
| 335 | + |
| 336 | + private function insertMagicLinkIdentity(User $user, string $token): void |
| 337 | + { |
| 338 | + model(UserIdentityModel::class)->insert([ |
| 339 | + 'user_id' => $user->id, |
| 340 | + 'type' => Session::ID_TYPE_MAGIC_LINK, |
| 341 | + 'secret' => $token, |
| 342 | + 'expires' => Time::now()->addMinutes(60), |
| 343 | + ]); |
| 344 | + } |
| 345 | + |
| 346 | + /** |
| 347 | + * @param class-string $action |
| 348 | + */ |
| 349 | + private function assertPendingLoginAction(User $user, string $action): void |
| 350 | + { |
| 351 | + $sessionUser = session('user'); |
| 352 | + $this->assertIsArray($sessionUser); |
| 353 | + $this->assertSame($user->id, $sessionUser['id'] ?? null); |
| 354 | + $this->assertSame($action, $sessionUser['auth_action'] ?? null); |
| 355 | + $this->assertSame(lang('Auth.need2FA'), $sessionUser['auth_action_message'] ?? null); |
| 356 | + } |
255 | 357 | } |
0 commit comments