Skip to content

Commit d22158f

Browse files
authored
Merge pull request #176 from kenjis/feat-add-auth-token-logins-table
feat: add `auth_token_logins` table
2 parents 7005aa5 + 0138786 commit d22158f

File tree

6 files changed

+76
-18
lines changed

6 files changed

+76
-18
lines changed

.github/workflows/phpcpd.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,4 @@ jobs:
3333
coverage: none
3434

3535
- name: Detect duplicate code
36-
run: phpcpd src/ tests/
36+
run: phpcpd src/ tests/ --exclude src/Database/Migrations/2020-12-28-223112_create_auth_tables.php

src/Authentication/Authenticators/AccessTokens.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
use CodeIgniter\Shield\Authentication\AuthenticationException;
88
use CodeIgniter\Shield\Authentication\AuthenticatorInterface;
99
use CodeIgniter\Shield\Entities\User;
10-
use CodeIgniter\Shield\Models\LoginModel;
10+
use CodeIgniter\Shield\Models\TokenLoginModel;
1111
use CodeIgniter\Shield\Models\UserIdentityModel;
1212
use CodeIgniter\Shield\Models\UserModel;
1313
use CodeIgniter\Shield\Result;
@@ -21,15 +21,15 @@ class AccessTokens implements AuthenticatorInterface
2121
protected UserModel $provider;
2222

2323
protected ?User $user = null;
24-
protected LoginModel $loginModel;
24+
protected TokenLoginModel $loginModel;
2525

2626
public function __construct(UserModel $provider)
2727
{
2828
helper('session');
2929

3030
$this->provider = $provider;
3131

32-
$this->loginModel = model(LoginModel::class); // @phpstan-ignore-line
32+
$this->loginModel = model(TokenLoginModel::class); // @phpstan-ignore-line
3333
}
3434

3535
/**

src/Database/Migrations/2020-12-28-223112_create_auth_tables.php

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,7 @@ public function up(): void
2626

2727
/*
2828
* Auth Identities Table
29-
* Used for storage of passwords, reset hashes
30-
* social login identities, etc.
29+
* Used for storage of passwords, access tokens, social login identities, etc.
3130
*/
3231
$this->forge->addField([
3332
'id' => ['type' => 'int', 'constraint' => 11, 'unsigned' => true, 'auto_increment' => true],
@@ -49,22 +48,45 @@ public function up(): void
4948
$this->forge->addForeignKey('user_id', 'users', 'id', '', 'CASCADE');
5049
$this->forge->createTable('auth_identities', true);
5150

52-
// Auth Login Attempts Table
51+
/**
52+
* Auth Login Attempts Table
53+
* Records login attempts. A login means users think it is a login.
54+
* To login, users do action(s) like posting a form.
55+
*/
5356
$this->forge->addField([
5457
'id' => ['type' => 'int', 'constraint' => 11, 'unsigned' => true, 'auto_increment' => true],
55-
'ip_address' => ['type' => 'varchar', 'constraint' => 255, 'null' => true],
58+
'ip_address' => ['type' => 'varchar', 'constraint' => 255],
5659
'user_agent' => ['type' => 'varchar', 'constraint' => 255, 'null' => true],
57-
'identifier' => ['type' => 'varchar', 'constraint' => 255, 'null' => true],
60+
'identifier' => ['type' => 'varchar', 'constraint' => 255],
5861
'user_id' => ['type' => 'int', 'constraint' => 11, 'unsigned' => true, 'null' => true], // Only for successful logins
5962
'date' => ['type' => 'datetime'],
6063
'success' => ['type' => 'tinyint', 'constraint' => 1],
6164
]);
6265
$this->forge->addPrimaryKey('id');
6366
$this->forge->addKey('identifier');
6467
$this->forge->addKey('user_id');
65-
// NOTE: Do NOT delete the user_id or email when the user is deleted for security audits
68+
// NOTE: Do NOT delete the user_id or identifier when the user is deleted for security audits
6669
$this->forge->createTable('auth_logins', true);
6770

71+
/*
72+
* Auth Token Login Attempts Table
73+
* Records Bearer Token type login attempts.
74+
*/
75+
$this->forge->addField([
76+
'id' => ['type' => 'int', 'constraint' => 11, 'unsigned' => true, 'auto_increment' => true],
77+
'ip_address' => ['type' => 'varchar', 'constraint' => 255],
78+
'user_agent' => ['type' => 'varchar', 'constraint' => 255, 'null' => true],
79+
'identifier' => ['type' => 'varchar', 'constraint' => 255],
80+
'user_id' => ['type' => 'int', 'constraint' => 11, 'unsigned' => true, 'null' => true], // Only for successful logins
81+
'date' => ['type' => 'datetime'],
82+
'success' => ['type' => 'tinyint', 'constraint' => 1],
83+
]);
84+
$this->forge->addPrimaryKey('id');
85+
$this->forge->addKey('identifier');
86+
$this->forge->addKey('user_id');
87+
// NOTE: Do NOT delete the user_id or identifier when the user is deleted for security audits
88+
$this->forge->createTable('auth_token_logins', true);
89+
6890
/*
6991
* Auth Remember Tokens (remember-me) Table
7092
* @see https://paragonie.com/blog/2015/04/secure-authentication-php-with-long-term-persistence
@@ -114,6 +136,7 @@ public function down(): void
114136

115137
$this->forge->dropTable('users', true);
116138
$this->forge->dropTable('auth_logins', true);
139+
$this->forge->dropTable('auth_token_logins', true);
117140
$this->forge->dropTable('auth_remember_tokens', true);
118141
$this->forge->dropTable('auth_access_tokens', true);
119142
$this->forge->dropTable('auth_identities', true);

src/Models/LoginModel.php

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
namespace CodeIgniter\Shield\Models;
44

5-
use CodeIgniter\Database\BaseResult;
65
use CodeIgniter\I18n\Time;
76
use CodeIgniter\Model;
87
use CodeIgniter\Shield\Entities\Login;
@@ -11,6 +10,8 @@
1110

1211
class LoginModel extends Model
1312
{
13+
use CheckQueryReturnTrait;
14+
1415
protected $table = 'auth_logins';
1516
protected $primaryKey = 'id';
1617
protected $returnType = Login::class;
@@ -36,19 +37,24 @@ class LoginModel extends Model
3637

3738
/**
3839
* @param int|string|null $userId
39-
*
40-
* @return BaseResult|false|int|object|string
4140
*/
42-
public function recordLoginAttempt(string $identifier, bool $success, ?string $ipAddress = null, ?string $userAgent = null, $userId = null)
43-
{
44-
return $this->insert([
41+
public function recordLoginAttempt(
42+
string $identifier,
43+
bool $success,
44+
?string $ipAddress = null,
45+
?string $userAgent = null,
46+
$userId = null
47+
): void {
48+
$return = $this->insert([
4549
'ip_address' => $ipAddress,
4650
'user_agent' => $userAgent,
4751
'identifier' => $identifier,
4852
'user_id' => $userId,
4953
'date' => date('Y-m-d H:i:s'),
5054
'success' => (int) $success,
5155
]);
56+
57+
$this->checkQueryReturn($return);
5258
}
5359

5460
/**

src/Models/TokenLoginModel.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace CodeIgniter\Shield\Models;
4+
5+
use CodeIgniter\I18n\Time;
6+
use CodeIgniter\Shield\Entities\Login;
7+
use Exception;
8+
use Faker\Generator;
9+
10+
class TokenLoginModel extends LoginModel
11+
{
12+
protected $table = 'auth_token_logins';
13+
14+
/**
15+
* Generate a fake login for testing
16+
*
17+
* @throws Exception
18+
*/
19+
public function fake(Generator &$faker): Login
20+
{
21+
return new Login([
22+
'ip_address' => $faker->ipv4,
23+
'identifier' => 'token: ' . random_string('crypto', 64),
24+
'user_id' => fake(UserModel::class)->id,
25+
'date' => Time::parse('-1 day')->toDateTimeString(),
26+
'success' => true,
27+
]);
28+
}
29+
}

tests/Authentication/AccessTokenAuthenticatorTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ public function testAttemptCannotFindUser()
168168
$this->assertSame(lang('Auth.badToken'), $result->reason());
169169

170170
// A login attempt should have always been recorded
171-
$this->seeInDatabase('auth_logins', [
171+
$this->seeInDatabase('auth_token_logins', [
172172
'identifier' => 'token: abc123',
173173
'success' => 0,
174174
]);
@@ -195,7 +195,7 @@ public function testAttemptSuccess()
195195
$this->assertSame($token->token, $foundUser->currentAccessToken()->token);
196196

197197
// A login attempt should have been recorded
198-
$this->seeInDatabase('auth_logins', [
198+
$this->seeInDatabase('auth_token_logins', [
199199
'identifier' => 'token: ' . $token->raw_token,
200200
'success' => 1,
201201
]);

0 commit comments

Comments
 (0)