From d3ed61a5877b4db311e8117b60481241d525e67c Mon Sep 17 00:00:00 2001 From: CosDiabos <14842802+CosDiabos@users.noreply.github.com> Date: Sun, 3 Nov 2024 23:33:58 +0100 Subject: [PATCH 01/16] feat: add expiration date to access token & hmac keys/tokens. --- docs/references/authentication/hmac.md | 57 ++++++++++- docs/references/authentication/tokens.md | 52 +++++++++- .../Authenticators/AccessTokens.php | 13 +++ src/Authentication/Traits/HasAccessTokens.php | 76 +++++++++++++- src/Authentication/Traits/HasHmacTokens.php | 77 ++++++++++++++- src/Commands/Hmac.php | 45 ++++++++- src/Models/UserIdentityModel.php | 98 +++++++++++++++++-- tests/Authentication/HasAccessTokensTest.php | 56 ++++++++++- tests/Authentication/HasHmacTokensTest.php | 55 +++++++++++ tests/Commands/HmacTest.php | 21 ++++ 10 files changed, 526 insertions(+), 24 deletions(-) diff --git a/docs/references/authentication/hmac.md b/docs/references/authentication/hmac.md index 737199cbf..b604831d8 100644 --- a/docs/references/authentication/hmac.md +++ b/docs/references/authentication/hmac.md @@ -97,6 +97,58 @@ You can revoke all HMAC Keys with the `revokeAllHmacTokens()` method. $user->revokeAllHmacTokens(); ``` +## Expiring HMAC Keys + +By default, the HMAC keys don't expire unless they meet the HMAC Keys lifetime expiration after their last used date. + +HMAC keys can be set to expire through the `generateHmacToken()` method. This takes the expiration date as the $expiresAt argument. It's also possible to update an existing HMAC key using `setHmacTokenExpirationById($HmacTokenID, $expiresAt)` + +`$expiresAt` Accepts DateTime string formatted as 'Y-m-d h:i:s' or [DateTime relative formats](https://www.php.net/manual/en/datetime.formats.php#datetime.formats.relative) unit symbols (1 day, 2 weeks, 6 months, 1 year) to be added to DateTime 'now' + +```php +// Expiration date = 2024-11-03 12:00:00 +$token = $this->user->generateHmacToken('foo', ['foo:bar'], '2024-11-03 12:00:00'); + +// Expiration date = 2024-11-15 00:00:00 +$token = $user->setHmacTokenExpirationById($token->id, '2024-11-15 00:00:00'); + +// Or Expiration date = now() + 1 month + 15 days +$token = $user->setHmacTokenExpirationById($token->id, '1 month 15 days'); +``` + +The following support methods are also available: + +`hasHmacTokenExpired(AccessToken $HmacToken)` - Checks if the given HMAC key has expired. Returns `true` if the HMAC key has expired, `false` if not, and `null` if the expire date is null. + +```php +$token = $this->user->generateAccessToken('foo', ['foo:bar'], '2024-11-03 12:00:00'); + +$this->user->hasHmacTokenExpired($token); // Returns true +``` + +`getHmacTokenTimeToExpire(AccessToken $accessToken, string $format = "date" | "human")` - Checks if the given HMAC key has expired. Returns `true` if HMAC key has expired, `false` if not, and `null` if the expire date is not set. + +```php +$token = $this->user->generateHmacToken('foo', ['foo:bar']); + +$this->user->getHmacTokenTimeToExpire($token, 'date'); // Returns null + +// Assuming current time is: 2024-11-04 20:00:00 +$token = $this->user->generateHmacToken('foo', ['foo:bar'], '2024-11-03 12:00:00'); + +$this->user->getHmacTokenTimeToExpire($token, 'date'); // 2024-11-03 12:00:00 +$this->user->getHmacTokenTimeToExpire($token, 'human'); // 1 day ago + +$token = $this->user->generateHmacToken('foo', ['foo:bar'], '2026-01-06 12:00:00'); +$this->user->getHmacTokenTimeToExpire($token, 'human'); // in 1 year +``` + +You can also easily set all existing HMAC keys/tokens as expired with the `spark` command: +``` +php spark shield:hmac expireAll +``` +**Careful!** This command 'expires' _all_ keys for _all_ users. + ## Retrieving HMAC Keys The following methods are available to help you retrieve a user's HMAC keys: @@ -217,7 +269,7 @@ Configure **app/Config/AuthToken.php** for your needs. ### HMAC Keys Lifetime -HMAC Keys/Tokens will expire after a specified amount of time has passed since they have been used. +HMAC Keys will expire after a specified amount of time has passed since they have been used. By default, this is set to 1 year. You can change this value by setting the `$unusedTokenLifetime` value. This is in seconds so that you can use the @@ -228,6 +280,9 @@ that CodeIgniter provides. public $unusedTokenLifetime = YEAR; ``` +### HMAC Keys Expiration vs Lifetime +Expiration and Lifetime are different concepts. The lifetime is the maximum time allowed for the HMAC Key to exist since its last use. HMAC Key expiration, on the other hand, is a set date in which the HMAC Key will cease to function. + ### Login Attempt Logging By default, only failed login attempts are recorded in the `auth_token_logins` table. diff --git a/docs/references/authentication/tokens.md b/docs/references/authentication/tokens.md index 65df886d0..daaadb3ba 100644 --- a/docs/references/authentication/tokens.md +++ b/docs/references/authentication/tokens.md @@ -125,7 +125,7 @@ Configure **app/Config/AuthToken.php** for your needs. ### Access Token Lifetime -Tokens will expire after a specified amount of time has passed since they have been used. +Tokens will expire after a specified amount of time has passed since they last have been used. By default, this is set to 1 year. You can change this value by setting the `$unusedTokenLifetime` value. This is @@ -137,6 +137,56 @@ that CodeIgniter provides. public $unusedTokenLifetime = YEAR; ``` + +## Expiring Access Tokens + +By default, the Access Tokens don't expire unless they meet the Access Token lifetime expiration after their last used date. + +Access Tokens can be set to expire through the `generateAccessToken()` method. This takes the expiration date as the $expiresAt argument. It's also possible to update an existing HMAC key using `setAccessTokenById($HmacTokenID, $expiresAt)` + +`$expiresAt` Accepts DateTime string formatted as 'Y-m-d h:i:s' or [DateTime relative formats](https://www.php.net/manual/en/datetime.formats.php#datetime.formats.relative) unit symbols (1 day, 2 weeks, 6 months, 1 year) to be added to DateTime 'now' + +```php +// Expiration date = 2024-11-03 12:00:00 +$token = $this->user->generateAccessToken('foo', ['foo:bar'], '2024-11-03 12:00:00'); + +// Expiration date = 2024-11-15 00:00:00 +$user->setAccessTokenExpirationById($token->id, '2024-11-15 00:00:00'); + +// Or Expiration date = now() + 1 month + 15 days +$user->setAccessTokenExpirationById($token->id, '1 month 15 days'); +``` + +The following support methods are also available: + +`hasAccessTokenExpired(AccessToken $accessToken)` - Checks if the given Access Token has expired. Returns `true` if the Access Token has expired, `false` if not, and `null` if the expire date is not set. + +```php +$token = $this->user->generateAccessToken('foo', ['foo:bar'], '2024-11-03 12:00:00'); + +$this->user->hasAccessTokenExpired($token); // Returns true +``` + +`getAccessTokenTimeToExpire(AccessToken $accessToken, string $format = "date" | "human")` - Checks if the given Access Token has expired. Returns `true` if Access Token has expired, `false` if not, and `null` if the expire date is null. + +```php +$token = $this->user->generateAccessToken('foo', ['foo:bar']); + +$this->user->getAccessTokenTimeToExpire($token, 'date'); // Returns null + +// Assuming current time is: 2024-11-04 20:00:00 +$token = $this->user->generateAccessToken('foo', ['foo:bar'], '2024-11-03 12:00:00'); + +$this->user->getAccessTokenTimeToExpire($token, 'date'); // 2024-11-03 12:00:00 +$this->user->getAccessTokenTimeToExpire($token, 'human'); // 1 day ago + +$token = $this->user->generateAccessToken('foo', ['foo:bar'], '2026-01-06 12:00:00'); +$this->user->getAccessTokenTimeToExpire($token, 'human'); // in 1 year +``` + +### Access Token Expiration vs Lifetime +Expiration and Lifetime are different concepts. The lifetime is the maximum time allowed for the token to exist since its last use. Token expiration, on the other hand, is a set date in which the Access Token will cease to function. + ### Login Attempt Logging By default, only failed login attempts are recorded in the `auth_token_logins` table. diff --git a/src/Authentication/Authenticators/AccessTokens.php b/src/Authentication/Authenticators/AccessTokens.php index ce86155a9..1bce06b9c 100644 --- a/src/Authentication/Authenticators/AccessTokens.php +++ b/src/Authentication/Authenticators/AccessTokens.php @@ -154,6 +154,19 @@ public function check(array $credentials): Result assert($token->last_used_at instanceof Time || $token->last_used_at === null); + // Is expired ? + if ( + $token->expires + && $token->expires->isBefore( + Time::now() + ) + ) { + return new Result([ + 'success' => false, + 'reason' => lang('Auth.oldToken'), + ]); + } + // Hasn't been used in a long time if ( $token->last_used_at diff --git a/src/Authentication/Traits/HasAccessTokens.php b/src/Authentication/Traits/HasAccessTokens.php index bb9d47975..bd2eb5245 100644 --- a/src/Authentication/Traits/HasAccessTokens.php +++ b/src/Authentication/Traits/HasAccessTokens.php @@ -13,8 +13,11 @@ namespace CodeIgniter\Shield\Authentication\Traits; +use CodeIgniter\I18n\Time; +use CodeIgniter\Shield\Authentication\Authenticators\AccessTokens; use CodeIgniter\Shield\Entities\AccessToken; use CodeIgniter\Shield\Models\UserIdentityModel; +use InvalidArgumentException; /** * Trait HasAccessTokens @@ -34,15 +37,18 @@ trait HasAccessTokens /** * Generates a new personal access token for this user. * - * @param string $name Token name - * @param list $scopes Permissions the token grants + * @param string $name Token name + * @param list $scopes Permissions the token grants + * @param string $expiresAt Sets token expiration date. Accepts DateTime string formatted as 'Y-m-d h:i:s' or DateTime relative formats (1 day, 2 weeks, 6 months, 1 year) to be added to DateTime 'now' + * + * @throws InvalidArgumentException */ - public function generateAccessToken(string $name, array $scopes = ['*']): AccessToken + public function generateAccessToken(string $name, array $scopes = ['*'], ?string $expiresAt = null): AccessToken { /** @var UserIdentityModel $identityModel */ $identityModel = model(UserIdentityModel::class); - return $identityModel->generateAccessToken($this, $name, $scopes); + return $identityModel->generateAccessToken($this, $name, $scopes, $expiresAt); } /** @@ -165,4 +171,66 @@ public function setAccessToken(?AccessToken $accessToken): self return $this; } + + /** + * Checks if the provided Access Token has expired. + * + * @return false|true|null Returns true if Access Token has expired, false if not, and null if the expire field is null + */ + public function hasAccessTokenExpired(?AccessToken $accessToken): bool|null + { + if (null === $accessToken->expires) { + return null; + } + + return $accessToken->expires->isBefore(Time::now()); + } + + /** + * Returns formatted date to expiration for provided AccessToken + * + * @param AcessToken $accessToken AccessToken + * @param string $format The return format - "date" or "human". Date is 'Y-m-d h:i:s', human is 'in 2 weeks' + * + * @return false|true|null Returns true if Access Token has expired, false if not and null if the expire field is null + * + * @throws InvalidArgumentException + */ + public function getAccessTokenTimeToExpire(?AccessToken $accessToken, string $format = 'date'): string|null + { + if (null === $accessToken->expires) { + return null; + } + + switch ($format) { + case 'date': + return $accessToken->expires->toLocalizedString(); + + case 'human': + return $accessToken->expires->humanize(); + + default: + throw new InvalidArgumentException('getAccessTokenTimeToExpire(): $format argument is invalid. Expects string with "date" or "human".'); + } + } + + /** + * Sets an expiration for Access Tokens by ID. + * + * @param int $id AccessTokens ID + * @param string $expiresAt Expiration date. Accepts DateTime string formatted as 'Y-m-d h:i:s' or DateTime relative formats (1 day, 2 weeks, 6 months, 1 year) to be added to DateTime 'now' + */ + public function setAccessTokenExpirationById(int $id, string $expiresAt): bool + { + /** @var UserIdentityModel $identityModel */ + $identityModel = model(UserIdentityModel::class); + $result = $identityModel->setIdentityExpirationById($id, $this, $expiresAt, AccessTokens::ID_TYPE_ACCESS_TOKEN); + + if ($result) { + // refresh currentAccessToken with updated data + $this->currentAccessToken = $identityModel->getAccessTokenById($id, $this); + } + + return $result; + } } diff --git a/src/Authentication/Traits/HasHmacTokens.php b/src/Authentication/Traits/HasHmacTokens.php index bfaab7d65..ee2dea92d 100644 --- a/src/Authentication/Traits/HasHmacTokens.php +++ b/src/Authentication/Traits/HasHmacTokens.php @@ -13,8 +13,11 @@ namespace CodeIgniter\Shield\Authentication\Traits; +use CodeIgniter\I18n\Time; +use CodeIgniter\Shield\Authentication\Authenticators\HmacSha256; use CodeIgniter\Shield\Entities\AccessToken; use CodeIgniter\Shield\Models\UserIdentityModel; +use InvalidArgumentException; use ReflectionException; /** @@ -35,17 +38,19 @@ trait HasHmacTokens /** * Generates a new personal HMAC token for this user. * - * @param string $name Token name - * @param list $scopes Permissions the token grants + * @param string $name Token name + * @param list $scopes Permissions the token grants + * @param string $expiresAt Sets token expiration date. Accepts DateTime string formatted as 'Y-m-d h:i:s' or DateTime relative formats (1 day, 2 weeks, 6 months, 1 year) to be added to DateTime 'now' * + * @throws InvalidArgumentException * @throws ReflectionException */ - public function generateHmacToken(string $name, array $scopes = ['*']): AccessToken + public function generateHmacToken(string $name, array $scopes = ['*'], ?string $expiresAt = null): AccessToken { /** @var UserIdentityModel $identityModel */ $identityModel = model(UserIdentityModel::class); - return $identityModel->generateHmacToken($this, $name, $scopes); + return $identityModel->generateHmacToken($this, $name, $scopes, $expiresAt); } /** @@ -156,4 +161,68 @@ public function setHmacToken(?AccessToken $accessToken): self return $this; } + + /** + * Checks if the provided Access Token has expired. + * + * @return false|true|null Returns true if Access Token has expired, false if not, and null if the expire field is null + */ + public function hasHmacTokenExpired(?AccessToken $accessToken): bool|null + { + if (null === $accessToken->expires) { + return null; + } + + return $accessToken->expires->isBefore(Time::now()); + } + + /** + * Returns formatted date to expiration for provided Hmac Key/Token. + * + * @param AcessToken $accessToken AccessToken + * @param string $format The return format - "date" or "human". Date is 'Y-m-d h:i:s', human is 'in 2 weeks' + * + * @return false|true|null Returns true if Access Token has expired, false if not and null if the expire field is null + * + * @throws InvalidArgumentException + */ + public function getHmacTokenTimeToExpire(?AccessToken $accessToken, string $format = 'date'): string|null + { + if (null === $accessToken->expires) { + return null; + } + + switch ($format) { + case 'date': + return $accessToken->expires->toLocalizedString(); + + case 'human': + return $accessToken->expires->humanize(); + + default: + throw new InvalidArgumentException('getHmacTokenTimeToExpire(): $format argument is invalid. Expects string with "date" or "human".'); + } + } + + /** + * Sets an expiration for Hmac Key/Token by ID. + * + * @param int $id AccessTokens ID + * @param string $expiresAt Expiration date. Accepts DateTime string formatted as 'Y-m-d h:i:s' or DateTime relative formats (1 day, 2 weeks, 6 months, 1 year) to be added to DateTime 'now' + * + * @return false|true|null Returns true if token is updated, false if not. + */ + public function setHmacTokenExpirationById(int $id, string $expiresAt): bool + { + /** @var UserIdentityModel $identityModel */ + $identityModel = model(UserIdentityModel::class); + $result = $identityModel->setIdentityExpirationById($id, $this, $expiresAt, HmacSha256::ID_TYPE_HMAC_TOKEN); + + if ($result) { + // refresh currentAccessToken with updated data + $this->currentAccessToken = $identityModel->getHmacTokenById($id, $this); + } + + return $result; + } } diff --git a/src/Commands/Hmac.php b/src/Commands/Hmac.php index d3961b07f..9e290116d 100644 --- a/src/Commands/Hmac.php +++ b/src/Commands/Hmac.php @@ -13,6 +13,7 @@ namespace CodeIgniter\Shield\Commands; +use CodeIgniter\I18n\Time; use CodeIgniter\Shield\Authentication\HMAC\HmacEncrypter; use CodeIgniter\Shield\Commands\Exceptions\BadInputException; use CodeIgniter\Shield\Exceptions\RuntimeException; @@ -46,9 +47,11 @@ class Hmac extends BaseCommand shield:hmac reencrypt shield:hmac encrypt shield:hmac decrypt + shield:hmac invalidateAll The reencrypt command should be used when rotating the encryption keys. The encrypt command should only be run on existing raw secret keys (extremely rare). + The invalidateAll command should only be run if you need to invalidate ALL HMAC Tokens (for everyone). EOL; /** @@ -61,6 +64,7 @@ class Hmac extends BaseCommand reencrypt: Re-encrypts all HMAC Secret Keys on encryption key rotation encrypt: Encrypt all raw HMAC Secret Keys decrypt: Decrypt all encrypted HMAC Secret Keys + invalidateAll: Invalidates all HMAC Keys/Tokens (for everyone) EOL, ]; @@ -87,10 +91,12 @@ public function run(array $params): int try { match ($action) { - 'encrypt' => $this->encrypt(), - 'decrypt' => $this->decrypt(), - 'reencrypt' => $this->reEncrypt(), - default => throw new BadInputException('Unrecognized Command'), + 'encrypt' => $this->encrypt(), + 'decrypt' => $this->decrypt(), + 'reencrypt' => $this->reEncrypt(), + 'invalidateAll' => $this->invalidateAll(), + + default => throw new BadInputException('Unrecognized Command'), }; } catch (Exception $e) { $this->write($e->getMessage(), 'red'); @@ -196,4 +202,33 @@ static function ($identity) use ($uIdModelSub, $encrypter, $that): void { }, ); } -} + + /** + * Invalidates all HMAC Keys/Tokens for every user. + */ + public function invalidateAll(): void + { + $uIdModel = new UserIdentityModel(); + $uIdModelSub = new UserIdentityModel(); + + $that = $this; + + $uIdModel->where('type', 'hmac_sha256')->orderBy('id')->chunk( + 100, + static function ($identity) use ($uIdModelSub, $that): void { + $timeNow = Time::now(); // Current date/time + + if (null !== $identity->expires && $identity->expires->isBefore($timeNow)) { + $that->write('Hmac Key/Token ID: ' . $identity->id . ', already expired, skipped.'); + + return; + } + + $identity->expires = $timeNow; + $uIdModelSub->save($identity); + + $that->write('Hmac Key/Token ID: ' . $identity->id . ', set as expired.'); + }, + ); + } +} \ No newline at end of file diff --git a/src/Models/UserIdentityModel.php b/src/Models/UserIdentityModel.php index 69f9af62a..c0477a821 100644 --- a/src/Models/UserIdentityModel.php +++ b/src/Models/UserIdentityModel.php @@ -24,8 +24,11 @@ use CodeIgniter\Shield\Entities\UserIdentity; use CodeIgniter\Shield\Exceptions\LogicException; use CodeIgniter\Shield\Exceptions\ValidationException; +use DateInterval; +use DateTime; use Exception; use Faker\Generator; +use InvalidArgumentException; use ReflectionException; class UserIdentityModel extends BaseModel @@ -101,6 +104,43 @@ private function checkUserId(User $user): void } } + private function checkExpiresAtFormat(string $expiresAt): string + { + if (! is_string($expiresAt)) { + throw new InvalidArgumentException('$expiresAt should be a string.'); + } + + $expireMatch = []; + + // Check Y-m-d h:i:s format. + preg_match('/\d{4}-\d{2}-\d{2}.\d{2}:\d{2}:\d{2}/', $expiresAt, $expireMatch, PREG_UNMATCHED_AS_NULL); + + // Format Y-m-d h:i:s not found. + if ($expireMatch === []) { + // Looking for relative format like 1 day, 2 weeks and process all + preg_match_all('/[1].(second|minute|hour|day|week|month|year)|[2,3,4,5,6,7,8,9].(seconds|minutes|hours|days|weeks|months|years)/', $expiresAt, $expireMatch, PREG_PATTERN_ORDER); + + if ($expireMatch[0] !== []) { + // Dummy DateTime to add() DateInterval + $dateTime = new DateTime(); + + // Turn the preg_match array into string splitted by ' + ' + // to be fed into DateTime->add() to generate new date + $relativeTime = implode(' + ', $expireMatch[0]); + + // add relative formats + $dateTime->add(DateInterval::createFromDateString($relativeTime)); + + // return Dummy DateTime + return $dateTime->format('Y-m-d h:i:s'); + } + + throw new InvalidArgumentException('$expiresAt should be a DateTime string formatted as "Y-m-d h:i:s" or a DateTime relative formats.'); + } + + return $expiresAt; + } + /** * Create an identity with 6 digits code for auth action * @@ -144,13 +184,20 @@ public function createCodeIdentity( /** * Generates a new personal access token for the user. * - * @param string $name Token name - * @param list $scopes Permissions the token grants + * @param string $name Token name + * @param list $scopes Permissions the token grants + * @param string $expiresAt Sets token expiration date. Accepts DateTime string formatted as 'Y-m-d h:i:s' or DateTime relative formats (1 day, 2 weeks, 6 months, 1 year) to be added to 'Time::now()' + * + * @throws InvalidArgumentException */ - public function generateAccessToken(User $user, string $name, array $scopes = ['*']): AccessToken + public function generateAccessToken(User $user, string $name, array $scopes = ['*'], ?string $expiresAt = null): AccessToken { $this->checkUserId($user); + if ($expiresAt !== null && $expiresAt !== '' && $expiresAt !== '0') { + $expiresAt = $this->checkExpiresAtFormat($expiresAt); + } + helper('text'); $return = $this->insert([ @@ -158,6 +205,7 @@ public function generateAccessToken(User $user, string $name, array $scopes = [' 'user_id' => $user->id, 'name' => $name, 'secret' => hash('sha256', $rawToken = random_string('crypto', 64)), + 'expires' => $expiresAt, 'extra' => serialize($scopes), ]); @@ -224,6 +272,37 @@ public function getAllAccessTokens(User $user): array ->findAll(); } + /** + * Updates or sets expiration date of users' AccessToken or HMAC Token by ID. Returns updated row. + * + * @param string $expiresAt Expiration date. Accepts DateTime string formatted as 'Y-m-d h:i:s' or DateTime relative formats (1 day, 2 weeks, 6 months, 1 year) to be added to 'Time::now()' + * @param mixed $id + */ + public function setIdentityExpirationById($id, User $user, ?string $expiresAt = null, ?string $type_token = null): bool + { + $this->checkUserId($user); + + if (! $expiresAt) { + throw new InvalidArgumentException("setIdentityExpirationById(): expiresAt argument can't be null."); + } + + $expiresAt = $this->checkExpiresAtFormat($expiresAt); + + $currentExpiration = $this->asObject(AccessToken::class)->find($id); + + // d($currentExpiration); + if ($currentExpiration->expires !== $expiresAt) { + // throw new InvalidArgumentException(sprintf("User-id: %d type: %s id: %d expires:%s", $user->id,$type_token,$id,$expiresAt)); + return $this->where('user_id', $user->id) + ->where('type', $type_token) + ->where('id', $id) + ->set(['expires' => $expiresAt]) + ->update(); + } + + throw new InvalidArgumentException('setIdentityExpirationById(): No data to update. ID:' . $id . ' Expires at: ' . $expiresAt . ' curr_expires:' . $currentExpiration->expires . ' Result: ' . ($currentExpiration->expires !== $expiresAt)); + } + // HMAC /** * Find and Retrieve the HMAC AccessToken based on Token alone @@ -242,16 +321,22 @@ public function getHmacTokenByKey(string $key): ?AccessToken /** * Generates a new personal access token for the user. * - * @param string $name Token name - * @param list $scopes Permissions the token grants + * @param string $name Token name + * @param list $scopes Permissions the token grants + * @param string $expiresAt Expiration date. Accepts DateTime string formatted as 'Y-m-d h:i:s' or DateTime relative formats (1 day, 2 weeks, 6 months, 1 year) to be added to 'Time::now()' * * @throws Exception + * @throws InvalidArgumentException * @throws ReflectionException */ - public function generateHmacToken(User $user, string $name, array $scopes = ['*']): AccessToken + public function generateHmacToken(User $user, string $name, array $scopes = ['*'], ?string $expiresAt = null): AccessToken { $this->checkUserId($user); + if ($expiresAt !== null && $expiresAt !== '' && $expiresAt !== '0') { + $expiresAt = $this->checkExpiresAtFormat($expiresAt); + } + $encrypter = new HmacEncrypter(); $rawSecretKey = $encrypter->generateSecretKey(); $secretKey = $encrypter->encrypt($rawSecretKey); @@ -262,6 +347,7 @@ public function generateHmacToken(User $user, string $name, array $scopes = ['*' 'name' => $name, 'secret' => bin2hex(random_bytes(16)), // Key 'secret2' => $secretKey, + 'expires' => $expiresAt, 'extra' => serialize($scopes), ]); diff --git a/tests/Authentication/HasAccessTokensTest.php b/tests/Authentication/HasAccessTokensTest.php index e00154f89..89f6a855c 100644 --- a/tests/Authentication/HasAccessTokensTest.php +++ b/tests/Authentication/HasAccessTokensTest.php @@ -17,6 +17,8 @@ use CodeIgniter\Shield\Entities\User; use CodeIgniter\Shield\Models\UserIdentityModel; use CodeIgniter\Shield\Models\UserModel; +use DateInterval; +use DateTime; use Tests\Support\DatabaseTestCase; /** @@ -152,12 +154,60 @@ public function testTokenCantNoTokenSet(): void $this->assertTrue($this->user->tokenCant('foo')); } - public function testTokenCant(): void + /** + * See https://github.com/codeigniter4/shield/issues/926 + */ + public function testGenerateTokenWithExpiration(): void + { + $token = $this->user->generateAccessToken('foo', ['foo:bar'], '2024-11-03 12:00:00'); + $this->user->setAccessToken($token); + + $this->assertSame('2024-11-03 12:00:00', $this->user->getAccessTokenTimeToExpire($this->user->currentAccessToken(), 'date')); + + $token = $this->user->generateAccessToken('foo', ['foo:bar'], '1 month 1 year'); + $this->user->setAccessToken($token); + + $expireDate = new DateTime(); + $expireDate->add(DateInterval::createFromDateString('1 month + 1 year')); + $this->assertSame($expireDate->format('Y-m-d h:i:s'), $this->user->getAccessTokenTimeToExpire($this->user->currentAccessToken(), 'date')); + } + + /** + * See https://github.com/codeigniter4/shield/issues/926 + */ + public function testSetTokenExpirationById(): void { $token = $this->user->generateAccessToken('foo', ['foo:bar']); + + $this->user->setAccessToken($token); + + $this->assertNull($this->user->currentAccessToken()->expires); + + // true = updated row + $this->assertTrue($this->user->setAccessTokenExpirationById($token->id, '2024-11-03 12:00:00')); + + $this->assertSame('2024-11-03 12:00:00', $this->user->getAccessTokenTimeToExpire($this->user->currentAccessToken(), 'date')); + } + + /** + * See https://github.com/codeigniter4/shield/issues/926 + */ + public function testIsTokenExpired(): void + { + $token = $this->user->generateAccessToken('foo', ['foo:bar'], '2024-11-03 12:00:00'); + $this->user->setAccessToken($token); + + $this->assertTrue($this->user->hasAccessTokenExpired($this->user->currentAccessToken())); + } + + /** + * See https://github.com/codeigniter4/shield/issues/926 + */ + public function testTokenTimeToExpired(): void + { + $token = $this->user->generateAccessToken('foo', ['foo:bar'], '1 year'); $this->user->setAccessToken($token); - $this->assertFalse($this->user->tokenCant('foo:bar')); - $this->assertTrue($this->user->tokenCant('foo:baz')); + $this->assertSame('in 11 months', $this->user->getAccessTokenTimeToExpire($this->user->currentAccessToken(), 'human')); } } diff --git a/tests/Authentication/HasHmacTokensTest.php b/tests/Authentication/HasHmacTokensTest.php index e9d6a3451..5f127e7c3 100644 --- a/tests/Authentication/HasHmacTokensTest.php +++ b/tests/Authentication/HasHmacTokensTest.php @@ -17,6 +17,8 @@ use CodeIgniter\Shield\Entities\User; use CodeIgniter\Shield\Models\UserIdentityModel; use CodeIgniter\Shield\Models\UserModel; +use DateInterval; +use DateTime; use Tests\Support\DatabaseTestCase; /** @@ -155,4 +157,57 @@ public function testHmacTokenCant(): void $this->assertFalse($this->user->hmacTokenCant('foo:bar')); $this->assertTrue($this->user->hmacTokenCant('foo:baz')); } + + /** + * See https://github.com/codeigniter4/shield/issues/926 + */ + public function testGenerateTokenWithExpiration(): void + { + $token = $this->user->generateHmacToken('foo', ['foo:bar'], '2024-11-03 12:00:00'); + + $this->assertSame('2024-11-03 12:00:00', $this->user->getHmacTokenTimeToExpire($token, 'date')); + + $token = $this->user->generateHmacToken('foo', ['foo:bar'], '1 month 1 year'); + + $expireDate = new DateTime(); + $expireDate->add(DateInterval::createFromDateString('1 month 1 year')); + + $this->assertSame($expireDate->format('Y-m-d h:i:s'), $this->user->getHmacTokenTimeToExpire($token, 'date')); + } + + /** + * See https://github.com/codeigniter4/shield/issues/926 + */ + public function testSetTokenExpirationById(): void + { + $token = $this->user->generateHmacToken('foo', ['foo:bar']); + + $this->assertNull($token->expires); + + // true = updated row + $this->assertTrue($this->user->setHmacTokenExpirationById($token->id, '2024-11-03 12:00:00')); + + $found = $this->user->getHmacTokenById($token->id); + $this->assertSame('2024-11-03 12:00:00', $this->user->getHmacTokenTimeToExpire($found, 'date')); + } + + /** + * See https://github.com/codeigniter4/shield/issues/926 + */ + public function testIsHmacTokenExpired(): void + { + $token = $this->user->generateHmacToken('foo', ['foo:bar'], '2024-11-03 12:00:00'); + + $this->assertTrue($this->user->hasHmacTokenExpired($token)); + } + + /** + * See https://github.com/codeigniter4/shield/issues/926 + */ + public function testHmacTokenTimeToExpired(): void + { + $token = $this->user->generateHmacToken('foo', ['foo:bar'], '1 year'); + + $this->assertSame('in 11 months', $this->user->getHmacTokenTimeToExpire($token, 'human')); + } } diff --git a/tests/Commands/HmacTest.php b/tests/Commands/HmacTest.php index 9027599f3..30640a00e 100644 --- a/tests/Commands/HmacTest.php +++ b/tests/Commands/HmacTest.php @@ -141,6 +141,27 @@ public function testBadCommand(): void $this->assertSame('Unrecognized Command', $resultsString); } + /** + * See https://github.com/codeigniter4/shield/issues/926 + */ + public function testExpireAll(): void + { + /** @var User $user */ + $user = fake(UserModel::class); + $user->generateHmacToken('foo', expiresAt: '2024-10-01 12:20:00'); + $user->generateHmacToken('bar'); + + $this->setMockIo([]); + $this->assertNotFalse(command('shield:hmac expireAll')); + + $resultsString = $this->io->getOutputs(); + $results = explode("\n", trim($resultsString)); + + $this->assertCount(2, $results); + $this->assertSame('Hmac Key/Token ID: 1, already expired, skipped.', trim($results[0])); + $this->assertSame('Hmac Key/Token ID: 2, set as expired.', trim($results[1])); + } + /** * Set MockInputOutput and user inputs. * From a55c0f5f1475b12d0639de5abf57136a86b4a316 Mon Sep 17 00:00:00 2001 From: CosDiabos <14842802+CosDiabos@users.noreply.github.com> Date: Thu, 14 Nov 2024 10:40:07 +0100 Subject: [PATCH 02/16] fix test assertion --- tests/Authentication/HasAccessTokensTest.php | 2 +- tests/Authentication/HasHmacTokensTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Authentication/HasAccessTokensTest.php b/tests/Authentication/HasAccessTokensTest.php index 89f6a855c..ab9ef3e3e 100644 --- a/tests/Authentication/HasAccessTokensTest.php +++ b/tests/Authentication/HasAccessTokensTest.php @@ -208,6 +208,6 @@ public function testTokenTimeToExpired(): void $token = $this->user->generateAccessToken('foo', ['foo:bar'], '1 year'); $this->user->setAccessToken($token); - $this->assertSame('in 11 months', $this->user->getAccessTokenTimeToExpire($this->user->currentAccessToken(), 'human')); + $this->assertSame('in 1 year', $this->user->getAccessTokenTimeToExpire($this->user->currentAccessToken(), 'human')); } } diff --git a/tests/Authentication/HasHmacTokensTest.php b/tests/Authentication/HasHmacTokensTest.php index 5f127e7c3..3ecca42fb 100644 --- a/tests/Authentication/HasHmacTokensTest.php +++ b/tests/Authentication/HasHmacTokensTest.php @@ -208,6 +208,6 @@ public function testHmacTokenTimeToExpired(): void { $token = $this->user->generateHmacToken('foo', ['foo:bar'], '1 year'); - $this->assertSame('in 11 months', $this->user->getHmacTokenTimeToExpire($token, 'human')); + $this->assertSame('in 1 year', $this->user->getHmacTokenTimeToExpire($token, 'human')); } } From cb6f968500ad9fd9bb677472858112634c5cdba4 Mon Sep 17 00:00:00 2001 From: CosDiabos <14842802+CosDiabos@users.noreply.github.com> Date: Thu, 14 Nov 2024 15:23:49 +0100 Subject: [PATCH 03/16] fix test for lower PHP version and PHPDoc blocks --- phpstan-baseline.php | 3 +-- .../Authenticators/AccessTokens.php | 2 +- src/Authentication/Traits/HasAccessTokens.php | 16 +++++++--------- src/Authentication/Traits/HasHmacTokens.php | 18 +++++++----------- src/Entities/AccessToken.php | 1 + src/Models/UserIdentityModel.php | 18 +++++------------- tests/Commands/HmacTest.php | 2 +- 7 files changed, 23 insertions(+), 37 deletions(-) diff --git a/phpstan-baseline.php b/phpstan-baseline.php index 575162eb4..508971dc2 100644 --- a/phpstan-baseline.php +++ b/phpstan-baseline.php @@ -258,8 +258,7 @@ ]; $ignoreErrors[] = [ 'message' => '#^Call to function model with CodeIgniter\\\\Shield\\\\Models\\\\UserIdentityModel\\:\\:class is discouraged\\.$#', - 'identifier' => 'codeigniter.factoriesClassConstFetch', - 'count' => 19, + 'count' => 21, 'path' => __DIR__ . '/src/Entities/User.php', ]; $ignoreErrors[] = [ diff --git a/src/Authentication/Authenticators/AccessTokens.php b/src/Authentication/Authenticators/AccessTokens.php index 1bce06b9c..03a2ba37e 100644 --- a/src/Authentication/Authenticators/AccessTokens.php +++ b/src/Authentication/Authenticators/AccessTokens.php @@ -156,7 +156,7 @@ public function check(array $credentials): Result // Is expired ? if ( - $token->expires + $token->expires !== null && $token->expires->isBefore( Time::now() ) diff --git a/src/Authentication/Traits/HasAccessTokens.php b/src/Authentication/Traits/HasAccessTokens.php index bd2eb5245..95eb80ff5 100644 --- a/src/Authentication/Traits/HasAccessTokens.php +++ b/src/Authentication/Traits/HasAccessTokens.php @@ -175,24 +175,20 @@ public function setAccessToken(?AccessToken $accessToken): self /** * Checks if the provided Access Token has expired. * - * @return false|true|null Returns true if Access Token has expired, false if not, and null if the expire field is null + * @return bool|null Returns true if Access Token has expired, false if not, and null if the expire field is null */ public function hasAccessTokenExpired(?AccessToken $accessToken): bool|null { - if (null === $accessToken->expires) { - return null; - } - - return $accessToken->expires->isBefore(Time::now()); + return $accessToken->expires !== null ? $accessToken->expires->isBefore(Time::now()) : null; } /** * Returns formatted date to expiration for provided AccessToken * - * @param AcessToken $accessToken AccessToken - * @param string $format The return format - "date" or "human". Date is 'Y-m-d h:i:s', human is 'in 2 weeks' + * @param AccessToken $accessToken AccessToken + * @param string $format The return format - "date" or "human". Date is 'Y-m-d h:i:s', human is 'in 2 weeks' * - * @return false|true|null Returns true if Access Token has expired, false if not and null if the expire field is null + * @return string|null Returns a formatted expiration date or null if the expire field is not set. * * @throws InvalidArgumentException */ @@ -219,6 +215,8 @@ public function getAccessTokenTimeToExpire(?AccessToken $accessToken, string $fo * * @param int $id AccessTokens ID * @param string $expiresAt Expiration date. Accepts DateTime string formatted as 'Y-m-d h:i:s' or DateTime relative formats (1 day, 2 weeks, 6 months, 1 year) to be added to DateTime 'now' + * + * @return bool Returns true if expiration date is set or updated. */ public function setAccessTokenExpirationById(int $id, string $expiresAt): bool { diff --git a/src/Authentication/Traits/HasHmacTokens.php b/src/Authentication/Traits/HasHmacTokens.php index ee2dea92d..1e1653f6e 100644 --- a/src/Authentication/Traits/HasHmacTokens.php +++ b/src/Authentication/Traits/HasHmacTokens.php @@ -165,24 +165,20 @@ public function setHmacToken(?AccessToken $accessToken): self /** * Checks if the provided Access Token has expired. * - * @return false|true|null Returns true if Access Token has expired, false if not, and null if the expire field is null + * @return bool|null Returns true if Access Token has expired, false if not, and null if the expire field is null */ public function hasHmacTokenExpired(?AccessToken $accessToken): bool|null { - if (null === $accessToken->expires) { - return null; - } - - return $accessToken->expires->isBefore(Time::now()); + return $accessToken->expires !== null ? $accessToken->expires->isBefore(Time::now()) : null; } /** * Returns formatted date to expiration for provided Hmac Key/Token. * - * @param AcessToken $accessToken AccessToken - * @param string $format The return format - "date" or "human". Date is 'Y-m-d h:i:s', human is 'in 2 weeks' + * @param AccessToken $accessToken AccessToken + * @param string $format The return format - "date" or "human". Date is 'Y-m-d h:i:s', human is 'in 2 weeks' * - * @return false|true|null Returns true if Access Token has expired, false if not and null if the expire field is null + * @return string|null Returns a formatted expiration date or null if the expire field is not set. * * @throws InvalidArgumentException */ @@ -207,10 +203,10 @@ public function getHmacTokenTimeToExpire(?AccessToken $accessToken, string $form /** * Sets an expiration for Hmac Key/Token by ID. * - * @param int $id AccessTokens ID + * @param int $id AccessToken ID * @param string $expiresAt Expiration date. Accepts DateTime string formatted as 'Y-m-d h:i:s' or DateTime relative formats (1 day, 2 weeks, 6 months, 1 year) to be added to DateTime 'now' * - * @return false|true|null Returns true if token is updated, false if not. + * @return bool Returns true if expiration date is set or updated. */ public function setHmacTokenExpirationById(int $id, string $expiresAt): bool { diff --git a/src/Entities/AccessToken.php b/src/Entities/AccessToken.php index 406910f21..3cd48e6ff 100644 --- a/src/Entities/AccessToken.php +++ b/src/Entities/AccessToken.php @@ -22,6 +22,7 @@ * Represents a single Personal Access Token, used * for authenticating users for an API. * + * @property string|Time|null $expires * @property string|Time|null $last_used_at */ class AccessToken extends Entity diff --git a/src/Models/UserIdentityModel.php b/src/Models/UserIdentityModel.php index c0477a821..c01275fa0 100644 --- a/src/Models/UserIdentityModel.php +++ b/src/Models/UserIdentityModel.php @@ -106,10 +106,6 @@ private function checkUserId(User $user): void private function checkExpiresAtFormat(string $expiresAt): string { - if (! is_string($expiresAt)) { - throw new InvalidArgumentException('$expiresAt should be a string.'); - } - $expireMatch = []; // Check Y-m-d h:i:s format. @@ -277,22 +273,18 @@ public function getAllAccessTokens(User $user): array * * @param string $expiresAt Expiration date. Accepts DateTime string formatted as 'Y-m-d h:i:s' or DateTime relative formats (1 day, 2 weeks, 6 months, 1 year) to be added to 'Time::now()' * @param mixed $id + * + * @return bool Returns true if expiration date was set or updated. */ public function setIdentityExpirationById($id, User $user, ?string $expiresAt = null, ?string $type_token = null): bool { $this->checkUserId($user); - if (! $expiresAt) { - throw new InvalidArgumentException("setIdentityExpirationById(): expiresAt argument can't be null."); - } - $expiresAt = $this->checkExpiresAtFormat($expiresAt); - $currentExpiration = $this->asObject(AccessToken::class)->find($id); + $currentExpiration = $this->where('user_id', $user->id)->where('id', $id)->asObject(AccessToken::class)->first(); - // d($currentExpiration); - if ($currentExpiration->expires !== $expiresAt) { - // throw new InvalidArgumentException(sprintf("User-id: %d type: %s id: %d expires:%s", $user->id,$type_token,$id,$expiresAt)); + if ($currentExpiration->expires !== null && $currentExpiration->expires !== $expiresAt) { return $this->where('user_id', $user->id) ->where('type', $type_token) ->where('id', $id) @@ -300,7 +292,7 @@ public function setIdentityExpirationById($id, User $user, ?string $expiresAt = ->update(); } - throw new InvalidArgumentException('setIdentityExpirationById(): No data to update. ID:' . $id . ' Expires at: ' . $expiresAt . ' curr_expires:' . $currentExpiration->expires . ' Result: ' . ($currentExpiration->expires !== $expiresAt)); + return false; } // HMAC diff --git a/tests/Commands/HmacTest.php b/tests/Commands/HmacTest.php index 30640a00e..68dc7b358 100644 --- a/tests/Commands/HmacTest.php +++ b/tests/Commands/HmacTest.php @@ -148,7 +148,7 @@ public function testExpireAll(): void { /** @var User $user */ $user = fake(UserModel::class); - $user->generateHmacToken('foo', expiresAt: '2024-10-01 12:20:00'); + $user->generateHmacToken('foo', ['*'], '2024-10-01 12:20:00'); $user->generateHmacToken('bar'); $this->setMockIo([]); From d8e907b15d83bdfeebf218674dd278545a4a2ec6 Mon Sep 17 00:00:00 2001 From: CosDiabos <14842802+CosDiabos@users.noreply.github.com> Date: Thu, 13 Feb 2025 15:52:34 +0100 Subject: [PATCH 04/16] fix token expiration type, tests and docs references. fix invalidateAll hmac cli command. implements CanAccessTokenExpire() and CanHmacTokenExpire() methods. removes hasAccessTokenExpired() and hasHmacTokenExpired() methods. --- docs/references/authentication/hmac.md | 43 ++++++------- docs/references/authentication/tokens.md | 40 ++++++------ phpstan-baseline.php | 1 + .../Authenticators/AccessTokens.php | 2 +- src/Authentication/Traits/HasAccessTokens.php | 54 ++++++---------- src/Authentication/Traits/HasHmacTokens.php | 54 ++++++---------- src/Models/UserIdentityModel.php | 35 +++++------ tests/Authentication/HasAccessTokensTest.php | 49 ++++++++++----- tests/Authentication/HasHmacTokensTest.php | 61 ++++++++++++++----- tests/Commands/HmacTest.php | 7 ++- 10 files changed, 182 insertions(+), 164 deletions(-) diff --git a/docs/references/authentication/hmac.md b/docs/references/authentication/hmac.md index b604831d8..ba79f6be3 100644 --- a/docs/references/authentication/hmac.md +++ b/docs/references/authentication/hmac.md @@ -103,51 +103,52 @@ By default, the HMAC keys don't expire unless they meet the HMAC Keys lifetime e HMAC keys can be set to expire through the `generateHmacToken()` method. This takes the expiration date as the $expiresAt argument. It's also possible to update an existing HMAC key using `setHmacTokenExpirationById($HmacTokenID, $expiresAt)` -`$expiresAt` Accepts DateTime string formatted as 'Y-m-d h:i:s' or [DateTime relative formats](https://www.php.net/manual/en/datetime.formats.php#datetime.formats.relative) unit symbols (1 day, 2 weeks, 6 months, 1 year) to be added to DateTime 'now' +`$expiresAt` [Time](/libraries/time.html) object ```php // Expiration date = 2024-11-03 12:00:00 -$token = $this->user->generateHmacToken('foo', ['foo:bar'], '2024-11-03 12:00:00'); +$expiresAt = Time::parse('2024-11-03 12:00:00'); +$token = $this->user->generateHmacToken('foo', ['foo:bar'], $expiresAt); // Expiration date = 2024-11-15 00:00:00 -$token = $user->setHmacTokenExpirationById($token->id, '2024-11-15 00:00:00'); - -// Or Expiration date = now() + 1 month + 15 days -$token = $user->setHmacTokenExpirationById($token->id, '1 month 15 days'); +$expiresAt = Time::parse('2024-11-15 00:00:00'); +$token = $user->setHmacTokenExpirationById($token->id, $expiresAt); + +// Or Expiration date = 1 month + 15 days into the future +$expiresAt = Time::now(); +$expiresAt = $expiresAt->addMonths(1); +$expiresAt = $expiresAt->addDays(15); +$token = $user->setHmacTokenExpirationById($token->id, $expiresAt); ``` The following support methods are also available: -`hasHmacTokenExpired(AccessToken $HmacToken)` - Checks if the given HMAC key has expired. Returns `true` if the HMAC key has expired, `false` if not, and `null` if the expire date is null. +`hasHmacTokenExpired(AccessToken $HmacToken)` - Checks if the HMAC key has expired. Returns `true` if the HMAC key has expired, `false` if not, and `null` if the expire date is null. ```php -$token = $this->user->generateAccessToken('foo', ['foo:bar'], '2024-11-03 12:00:00'); +$expiresAt = Time::parse('2024-11-03 12:00:00'); +$token = $this->user->generateHmacToken('foo', ['foo:bar'], $expiresAt); $this->user->hasHmacTokenExpired($token); // Returns true ``` -`getHmacTokenTimeToExpire(AccessToken $accessToken, string $format = "date" | "human")` - Checks if the given HMAC key has expired. Returns `true` if HMAC key has expired, `false` if not, and `null` if the expire date is not set. +`canHmacTokenExpire(AccessToken $HmacToken)` - Checks if HMAC key has an expiration set. Returns `true` or `false` accordingly. ```php -$token = $this->user->generateHmacToken('foo', ['foo:bar']); - -$this->user->getHmacTokenTimeToExpire($token, 'date'); // Returns null - -// Assuming current time is: 2024-11-04 20:00:00 -$token = $this->user->generateHmacToken('foo', ['foo:bar'], '2024-11-03 12:00:00'); +$expiresAt = Time::parse('2024-11-03 12:00:00'); -$this->user->getHmacTokenTimeToExpire($token, 'date'); // 2024-11-03 12:00:00 -$this->user->getHmacTokenTimeToExpire($token, 'human'); // 1 day ago +$token = $this->user->generateHmacToken('foo', ['foo:bar'], $expiresAt); +$this->user->canHmacTokenExpire($token); // Returns true -$token = $this->user->generateHmacToken('foo', ['foo:bar'], '2026-01-06 12:00:00'); -$this->user->getHmacTokenTimeToExpire($token, 'human'); // in 1 year +$token2 = $this->user->generateHmacToken('bar'); +$this->user->canHmacTokenExpire($token2); // Returns false ``` You can also easily set all existing HMAC keys/tokens as expired with the `spark` command: ``` -php spark shield:hmac expireAll +php spark shield:hmac invalidateAll ``` -**Careful!** This command 'expires' _all_ keys for _all_ users. +**Careful!** This command invalidates _all_ keys for _all_ users. ## Retrieving HMAC Keys diff --git a/docs/references/authentication/tokens.md b/docs/references/authentication/tokens.md index daaadb3ba..98c415673 100644 --- a/docs/references/authentication/tokens.md +++ b/docs/references/authentication/tokens.md @@ -144,46 +144,50 @@ By default, the Access Tokens don't expire unless they meet the Access Token lif Access Tokens can be set to expire through the `generateAccessToken()` method. This takes the expiration date as the $expiresAt argument. It's also possible to update an existing HMAC key using `setAccessTokenById($HmacTokenID, $expiresAt)` -`$expiresAt` Accepts DateTime string formatted as 'Y-m-d h:i:s' or [DateTime relative formats](https://www.php.net/manual/en/datetime.formats.php#datetime.formats.relative) unit symbols (1 day, 2 weeks, 6 months, 1 year) to be added to DateTime 'now' +`$expiresAt` [Time](/libraries/time.html) object ```php // Expiration date = 2024-11-03 12:00:00 -$token = $this->user->generateAccessToken('foo', ['foo:bar'], '2024-11-03 12:00:00'); +$expiresAt = Time::parse('2024-11-03 12:00:00'); +$token = $this->user->generateAccessToken('foo', ['foo:bar'], $expiresAt); // Expiration date = 2024-11-15 00:00:00 -$user->setAccessTokenExpirationById($token->id, '2024-11-15 00:00:00'); +$expiresAt = Time::parse('2024-11-15 00:00:00'); +$user->setAccessTokenExpirationById($token->id, $expiresAt); -// Or Expiration date = now() + 1 month + 15 days -$user->setAccessTokenExpirationById($token->id, '1 month 15 days'); +// Or Expiration date = 1 month + 15 days into the future +$expiresAt = Time::now(); +$expiresAt = $expiresAt->addMonths(1); +$expiresAt = $expiresAt->addDays(15); + +$user->setAccessTokenExpirationById($token->id, $expiresAt); ``` The following support methods are also available: -`hasAccessTokenExpired(AccessToken $accessToken)` - Checks if the given Access Token has expired. Returns `true` if the Access Token has expired, `false` if not, and `null` if the expire date is not set. +`hasAccessTokenExpired(AccessToken $accessToken)` - Checks if Access Token has expired. Returns `true` if the Access Token has expired, `false` if not, and `null` if the expire date is not set. ```php -$token = $this->user->generateAccessToken('foo', ['foo:bar'], '2024-11-03 12:00:00'); +$expiresAt = Time::parse('2024-11-03 12:00:00'); + +$token = $this->user->generateAccessToken('foo', ['foo:bar'], $expiresAt); $this->user->hasAccessTokenExpired($token); // Returns true ``` -`getAccessTokenTimeToExpire(AccessToken $accessToken, string $format = "date" | "human")` - Checks if the given Access Token has expired. Returns `true` if Access Token has expired, `false` if not, and `null` if the expire date is null. +`canAccessTokenExpire(AccessToken $HmacToken)` - Checks if Access Token has an expiration set. Returns `true` or `false` accordingly. ```php -$token = $this->user->generateAccessToken('foo', ['foo:bar']); - -$this->user->getAccessTokenTimeToExpire($token, 'date'); // Returns null +$expiresAt = Time::parse('2024-11-03 12:00:00'); -// Assuming current time is: 2024-11-04 20:00:00 -$token = $this->user->generateAccessToken('foo', ['foo:bar'], '2024-11-03 12:00:00'); +$token = $this->user->generateAccessToken('foo', ['foo:bar'], $expiresAt); +$this->user->canAccessTokenExpire($token2); // Returns false -$this->user->getAccessTokenTimeToExpire($token, 'date'); // 2024-11-03 12:00:00 -$this->user->getAccessTokenTimeToExpire($token, 'human'); // 1 day ago - -$token = $this->user->generateAccessToken('foo', ['foo:bar'], '2026-01-06 12:00:00'); -$this->user->getAccessTokenTimeToExpire($token, 'human'); // in 1 year +$token2 = $this->user->generateAccessToken('bar'); +$this->user->canAccessTokenExpire($token); // Returns true ``` + ### Access Token Expiration vs Lifetime Expiration and Lifetime are different concepts. The lifetime is the maximum time allowed for the token to exist since its last use. Token expiration, on the other hand, is a set date in which the Access Token will cease to function. diff --git a/phpstan-baseline.php b/phpstan-baseline.php index 508971dc2..e7d630cd2 100644 --- a/phpstan-baseline.php +++ b/phpstan-baseline.php @@ -258,6 +258,7 @@ ]; $ignoreErrors[] = [ 'message' => '#^Call to function model with CodeIgniter\\\\Shield\\\\Models\\\\UserIdentityModel\\:\\:class is discouraged\\.$#', + 'identifier' => 'codeigniter.factoriesClassConstFetch', 'count' => 21, 'path' => __DIR__ . '/src/Entities/User.php', ]; diff --git a/src/Authentication/Authenticators/AccessTokens.php b/src/Authentication/Authenticators/AccessTokens.php index 03a2ba37e..bda2736fd 100644 --- a/src/Authentication/Authenticators/AccessTokens.php +++ b/src/Authentication/Authenticators/AccessTokens.php @@ -158,7 +158,7 @@ public function check(array $credentials): Result if ( $token->expires !== null && $token->expires->isBefore( - Time::now() + Time::now(), ) ) { return new Result([ diff --git a/src/Authentication/Traits/HasAccessTokens.php b/src/Authentication/Traits/HasAccessTokens.php index 95eb80ff5..32cf8c058 100644 --- a/src/Authentication/Traits/HasAccessTokens.php +++ b/src/Authentication/Traits/HasAccessTokens.php @@ -39,11 +39,11 @@ trait HasAccessTokens * * @param string $name Token name * @param list $scopes Permissions the token grants - * @param string $expiresAt Sets token expiration date. Accepts DateTime string formatted as 'Y-m-d h:i:s' or DateTime relative formats (1 day, 2 weeks, 6 months, 1 year) to be added to DateTime 'now' + * @param Time $expiresAt Expiration date * * @throws InvalidArgumentException */ - public function generateAccessToken(string $name, array $scopes = ['*'], ?string $expiresAt = null): AccessToken + public function generateAccessToken(string $name, array $scopes = ['*'], ?Time $expiresAt = null): AccessToken { /** @var UserIdentityModel $identityModel */ $identityModel = model(UserIdentityModel::class); @@ -175,50 +175,22 @@ public function setAccessToken(?AccessToken $accessToken): self /** * Checks if the provided Access Token has expired. * - * @return bool|null Returns true if Access Token has expired, false if not, and null if the expire field is null + * @return bool Returns true if Access Token has expired, false if not */ - public function hasAccessTokenExpired(?AccessToken $accessToken): bool|null + public function hasAccessTokenExpired(?AccessToken $accessToken): bool { - return $accessToken->expires !== null ? $accessToken->expires->isBefore(Time::now()) : null; - } - - /** - * Returns formatted date to expiration for provided AccessToken - * - * @param AccessToken $accessToken AccessToken - * @param string $format The return format - "date" or "human". Date is 'Y-m-d h:i:s', human is 'in 2 weeks' - * - * @return string|null Returns a formatted expiration date or null if the expire field is not set. - * - * @throws InvalidArgumentException - */ - public function getAccessTokenTimeToExpire(?AccessToken $accessToken, string $format = 'date'): string|null - { - if (null === $accessToken->expires) { - return null; - } - - switch ($format) { - case 'date': - return $accessToken->expires->toLocalizedString(); - - case 'human': - return $accessToken->expires->humanize(); - - default: - throw new InvalidArgumentException('getAccessTokenTimeToExpire(): $format argument is invalid. Expects string with "date" or "human".'); - } + return $accessToken->expires !== null && $accessToken->expires->isBefore(Time::now()); } /** * Sets an expiration for Access Tokens by ID. * - * @param int $id AccessTokens ID - * @param string $expiresAt Expiration date. Accepts DateTime string formatted as 'Y-m-d h:i:s' or DateTime relative formats (1 day, 2 weeks, 6 months, 1 year) to be added to DateTime 'now' + * @param int $id AccessTokens ID + * @param Time $expiresAt Expiration date * * @return bool Returns true if expiration date is set or updated. */ - public function setAccessTokenExpirationById(int $id, string $expiresAt): bool + public function setAccessTokenExpirationById(int $id, Time $expiresAt): bool { /** @var UserIdentityModel $identityModel */ $identityModel = model(UserIdentityModel::class); @@ -231,4 +203,14 @@ public function setAccessTokenExpirationById(int $id, string $expiresAt): bool return $result; } + + /** + * Checks if the current Hmac token can expire + * + * @return bool Returns true if AccessToken can expire. + */ + public function canAccessTokenExpire(AccessToken $accessToken): bool + { + return isset($accessToken->expires); + } } diff --git a/src/Authentication/Traits/HasHmacTokens.php b/src/Authentication/Traits/HasHmacTokens.php index 1e1653f6e..343b64583 100644 --- a/src/Authentication/Traits/HasHmacTokens.php +++ b/src/Authentication/Traits/HasHmacTokens.php @@ -40,12 +40,12 @@ trait HasHmacTokens * * @param string $name Token name * @param list $scopes Permissions the token grants - * @param string $expiresAt Sets token expiration date. Accepts DateTime string formatted as 'Y-m-d h:i:s' or DateTime relative formats (1 day, 2 weeks, 6 months, 1 year) to be added to DateTime 'now' + * @param Time $expiresAt Expiration date * * @throws InvalidArgumentException * @throws ReflectionException */ - public function generateHmacToken(string $name, array $scopes = ['*'], ?string $expiresAt = null): AccessToken + public function generateHmacToken(string $name, array $scopes = ['*'], ?Time $expiresAt = null): AccessToken { /** @var UserIdentityModel $identityModel */ $identityModel = model(UserIdentityModel::class); @@ -165,50 +165,22 @@ public function setHmacToken(?AccessToken $accessToken): self /** * Checks if the provided Access Token has expired. * - * @return bool|null Returns true if Access Token has expired, false if not, and null if the expire field is null + * @return bool Returns true if Access Token has expired, false if not */ - public function hasHmacTokenExpired(?AccessToken $accessToken): bool|null + public function hasHmacTokenExpired(?AccessToken $accessToken): bool { - return $accessToken->expires !== null ? $accessToken->expires->isBefore(Time::now()) : null; - } - - /** - * Returns formatted date to expiration for provided Hmac Key/Token. - * - * @param AccessToken $accessToken AccessToken - * @param string $format The return format - "date" or "human". Date is 'Y-m-d h:i:s', human is 'in 2 weeks' - * - * @return string|null Returns a formatted expiration date or null if the expire field is not set. - * - * @throws InvalidArgumentException - */ - public function getHmacTokenTimeToExpire(?AccessToken $accessToken, string $format = 'date'): string|null - { - if (null === $accessToken->expires) { - return null; - } - - switch ($format) { - case 'date': - return $accessToken->expires->toLocalizedString(); - - case 'human': - return $accessToken->expires->humanize(); - - default: - throw new InvalidArgumentException('getHmacTokenTimeToExpire(): $format argument is invalid. Expects string with "date" or "human".'); - } + return $accessToken->expires !== null && $accessToken->expires->isBefore(Time::now()); } /** * Sets an expiration for Hmac Key/Token by ID. * - * @param int $id AccessToken ID - * @param string $expiresAt Expiration date. Accepts DateTime string formatted as 'Y-m-d h:i:s' or DateTime relative formats (1 day, 2 weeks, 6 months, 1 year) to be added to DateTime 'now' + * @param int $id AccessToken ID + * @param Time $expiresAt Expiration date * * @return bool Returns true if expiration date is set or updated. */ - public function setHmacTokenExpirationById(int $id, string $expiresAt): bool + public function setHmacTokenExpirationById(int $id, Time $expiresAt): bool { /** @var UserIdentityModel $identityModel */ $identityModel = model(UserIdentityModel::class); @@ -221,4 +193,14 @@ public function setHmacTokenExpirationById(int $id, string $expiresAt): bool return $result; } + + /** + * Checks if the current Hmac token can expire + * + * @return bool Returns true if Hmac Token can expire. + */ + public function canHmacTokenExpire(AccessToken $accessToken): bool + { + return isset($accessToken->expires); + } } diff --git a/src/Models/UserIdentityModel.php b/src/Models/UserIdentityModel.php index c01275fa0..b3b8cd4f2 100644 --- a/src/Models/UserIdentityModel.php +++ b/src/Models/UserIdentityModel.php @@ -182,17 +182,17 @@ public function createCodeIdentity( * * @param string $name Token name * @param list $scopes Permissions the token grants - * @param string $expiresAt Sets token expiration date. Accepts DateTime string formatted as 'Y-m-d h:i:s' or DateTime relative formats (1 day, 2 weeks, 6 months, 1 year) to be added to 'Time::now()' + * @param Time $expiresAt Expiration date * * @throws InvalidArgumentException */ - public function generateAccessToken(User $user, string $name, array $scopes = ['*'], ?string $expiresAt = null): AccessToken + public function generateAccessToken(User $user, string $name, array $scopes = ['*'], ?Time $expiresAt = null): AccessToken { $this->checkUserId($user); - if ($expiresAt !== null && $expiresAt !== '' && $expiresAt !== '0') { - $expiresAt = $this->checkExpiresAtFormat($expiresAt); - } + // if ($expiresAt !== null && $expiresAt !== '' && $expiresAt !== '0') { + // $expiresAt = $this->checkExpiresAtFormat($expiresAt); + // } helper('text'); @@ -271,25 +271,20 @@ public function getAllAccessTokens(User $user): array /** * Updates or sets expiration date of users' AccessToken or HMAC Token by ID. Returns updated row. * - * @param string $expiresAt Expiration date. Accepts DateTime string formatted as 'Y-m-d h:i:s' or DateTime relative formats (1 day, 2 weeks, 6 months, 1 year) to be added to 'Time::now()' - * @param mixed $id + * @param Time $expiresAt Expiration date + * @param mixed $id * * @return bool Returns true if expiration date was set or updated. */ - public function setIdentityExpirationById($id, User $user, ?string $expiresAt = null, ?string $type_token = null): bool + public function setIdentityExpirationById($id, User $user, ?Time $expiresAt = null, ?string $type_token = null): bool { $this->checkUserId($user); - $expiresAt = $this->checkExpiresAtFormat($expiresAt); - - $currentExpiration = $this->where('user_id', $user->id)->where('id', $id)->asObject(AccessToken::class)->first(); - - if ($currentExpiration->expires !== null && $currentExpiration->expires !== $expiresAt) { + if ($expiresAt !== null) { return $this->where('user_id', $user->id) ->where('type', $type_token) - ->where('id', $id) ->set(['expires' => $expiresAt]) - ->update(); + ->update($id); } return false; @@ -315,19 +310,19 @@ public function getHmacTokenByKey(string $key): ?AccessToken * * @param string $name Token name * @param list $scopes Permissions the token grants - * @param string $expiresAt Expiration date. Accepts DateTime string formatted as 'Y-m-d h:i:s' or DateTime relative formats (1 day, 2 weeks, 6 months, 1 year) to be added to 'Time::now()' + * @param Time $expiresAt Expiration date * * @throws Exception * @throws InvalidArgumentException * @throws ReflectionException */ - public function generateHmacToken(User $user, string $name, array $scopes = ['*'], ?string $expiresAt = null): AccessToken + public function generateHmacToken(User $user, string $name, array $scopes = ['*'], ?Time $expiresAt = null): AccessToken { $this->checkUserId($user); - if ($expiresAt !== null && $expiresAt !== '' && $expiresAt !== '0') { - $expiresAt = $this->checkExpiresAtFormat($expiresAt); - } + // if ($expiresAt !== null && $expiresAt !== '' && $expiresAt !== '0') { + // $expiresAt = $this->checkExpiresAtFormat($expiresAt); + // } $encrypter = new HmacEncrypter(); $rawSecretKey = $encrypter->generateSecretKey(); diff --git a/tests/Authentication/HasAccessTokensTest.php b/tests/Authentication/HasAccessTokensTest.php index ab9ef3e3e..25d68f5ec 100644 --- a/tests/Authentication/HasAccessTokensTest.php +++ b/tests/Authentication/HasAccessTokensTest.php @@ -13,12 +13,11 @@ namespace Tests\Authentication; +use Codeigniter\I18n\Time; use CodeIgniter\Shield\Entities\AccessToken; use CodeIgniter\Shield\Entities\User; use CodeIgniter\Shield\Models\UserIdentityModel; use CodeIgniter\Shield\Models\UserModel; -use DateInterval; -use DateTime; use Tests\Support\DatabaseTestCase; /** @@ -159,17 +158,18 @@ public function testTokenCantNoTokenSet(): void */ public function testGenerateTokenWithExpiration(): void { - $token = $this->user->generateAccessToken('foo', ['foo:bar'], '2024-11-03 12:00:00'); + $tokenExpiration = Time::parse('2024-11-03 12:00:00'); + $token = $this->user->generateAccessToken('foo', ['foo:bar'], $tokenExpiration); $this->user->setAccessToken($token); - $this->assertSame('2024-11-03 12:00:00', $this->user->getAccessTokenTimeToExpire($this->user->currentAccessToken(), 'date')); + $this->assertSame($tokenExpiration->format('Y-m-d h:i:s'), $this->user->currentAccessToken()->expires->format('Y-m-d h:i:s')); - $token = $this->user->generateAccessToken('foo', ['foo:bar'], '1 month 1 year'); + $tokenExpiration = $tokenExpiration->addMonths(1); + $tokenExpiration = $tokenExpiration->addYears(1); + $token = $this->user->generateAccessToken('foo', ['foo:bar'], $tokenExpiration); $this->user->setAccessToken($token); - $expireDate = new DateTime(); - $expireDate->add(DateInterval::createFromDateString('1 month + 1 year')); - $this->assertSame($expireDate->format('Y-m-d h:i:s'), $this->user->getAccessTokenTimeToExpire($this->user->currentAccessToken(), 'date')); + $this->assertSame($tokenExpiration->format('Y-m-d h:i:s'), $this->user->currentAccessToken()->expires->format('Y-m-d h:i:s')); } /** @@ -183,10 +183,10 @@ public function testSetTokenExpirationById(): void $this->assertNull($this->user->currentAccessToken()->expires); - // true = updated row - $this->assertTrue($this->user->setAccessTokenExpirationById($token->id, '2024-11-03 12:00:00')); + $tokenExpiration = Time::parse('2024-11-03 12:00:00'); - $this->assertSame('2024-11-03 12:00:00', $this->user->getAccessTokenTimeToExpire($this->user->currentAccessToken(), 'date')); + $this->assertTrue($this->user->setAccessTokenExpirationById($token->id, $tokenExpiration)); + $this->assertSame($tokenExpiration->format('Y-m-d h:i:s'), $this->user->currentAccessToken()->expires->format('Y-m-d h:i:s')); } /** @@ -194,7 +194,8 @@ public function testSetTokenExpirationById(): void */ public function testIsTokenExpired(): void { - $token = $this->user->generateAccessToken('foo', ['foo:bar'], '2024-11-03 12:00:00'); + $tokenExpiration = Time::parse('2024-11-03 12:00:00'); + $token = $this->user->generateAccessToken('foo', ['foo:bar'], $tokenExpiration); $this->user->setAccessToken($token); $this->assertTrue($this->user->hasAccessTokenExpired($this->user->currentAccessToken())); @@ -205,9 +206,29 @@ public function testIsTokenExpired(): void */ public function testTokenTimeToExpired(): void { - $token = $this->user->generateAccessToken('foo', ['foo:bar'], '1 year'); + $tokenExpiration = Time::now(); + $tokenExpiration = $tokenExpiration->addYears(1); + + $token = $this->user->generateAccessToken('foo', ['foo:bar'], $tokenExpiration); $this->user->setAccessToken($token); - $this->assertSame('in 1 year', $this->user->getAccessTokenTimeToExpire($this->user->currentAccessToken(), 'human')); + $this->assertSame('in 1 year', $this->user->currentAccessToken()->expires->humanize()); + } + + /** + * See https://github.com/codeigniter4/shield/issues/926 + */ + public function testCanHmacTokenExpire(): void + { + $tokenExpiration = Time::now(); + $tokenExpiration = $tokenExpiration->addYears(1); + + $token = $this->user->generateAccessToken('foo', ['foo:bar'], $tokenExpiration); + + $this->assertTrue($this->user->canAccessTokenExpire($token)); + + $token = $this->user->generateAccessToken('foo', ['foo:bar']); + + $this->assertFalse($this->user->canAccessTokenExpire($token)); } } diff --git a/tests/Authentication/HasHmacTokensTest.php b/tests/Authentication/HasHmacTokensTest.php index 3ecca42fb..9c71be405 100644 --- a/tests/Authentication/HasHmacTokensTest.php +++ b/tests/Authentication/HasHmacTokensTest.php @@ -13,12 +13,11 @@ namespace Tests\Authentication; +use Codeigniter\I18n\Time; use CodeIgniter\Shield\Entities\AccessToken; use CodeIgniter\Shield\Entities\User; use CodeIgniter\Shield\Models\UserIdentityModel; use CodeIgniter\Shield\Models\UserModel; -use DateInterval; -use DateTime; use Tests\Support\DatabaseTestCase; /** @@ -163,16 +162,20 @@ public function testHmacTokenCant(): void */ public function testGenerateTokenWithExpiration(): void { - $token = $this->user->generateHmacToken('foo', ['foo:bar'], '2024-11-03 12:00:00'); + $tokenExpiration = Time::parse('2024-11-03 12:00:00'); - $this->assertSame('2024-11-03 12:00:00', $this->user->getHmacTokenTimeToExpire($token, 'date')); + $token = $this->user->generateHmacToken('foo', ['foo:bar'], $tokenExpiration); + $this->user->setHmacToken($token); + + $this->assertSame($tokenExpiration->format('Y-m-d h:i:s'), $this->user->currentHmacToken()->expires->format('Y-m-d h:i:s')); - $token = $this->user->generateHmacToken('foo', ['foo:bar'], '1 month 1 year'); + $tokenExpiration = $tokenExpiration->addMonths(1); + $tokenExpiration = $tokenExpiration->addYears(1); - $expireDate = new DateTime(); - $expireDate->add(DateInterval::createFromDateString('1 month 1 year')); + $token = $this->user->generateHmacToken('foo', ['foo:bar'], $tokenExpiration); + $this->user->setHmacToken($token); - $this->assertSame($expireDate->format('Y-m-d h:i:s'), $this->user->getHmacTokenTimeToExpire($token, 'date')); + $this->assertSame($tokenExpiration->format('Y-m-d h:i:s'), $this->user->currentHmacToken()->expires->format('Y-m-d h:i:s')); } /** @@ -182,13 +185,16 @@ public function testSetTokenExpirationById(): void { $token = $this->user->generateHmacToken('foo', ['foo:bar']); - $this->assertNull($token->expires); + $this->user->setHmacToken($token); - // true = updated row - $this->assertTrue($this->user->setHmacTokenExpirationById($token->id, '2024-11-03 12:00:00')); + $this->assertNull($this->user->currentHmacToken()->expires); - $found = $this->user->getHmacTokenById($token->id); - $this->assertSame('2024-11-03 12:00:00', $this->user->getHmacTokenTimeToExpire($found, 'date')); + $tokenExpiration = Time::parse('2024-11-03 12:00:00'); + + $this->assertTrue($this->user->setHmacTokenExpirationById($token->id, $tokenExpiration)); + + $this->user->setHmacToken($this->user->getHmacTokenById($token->id)); + $this->assertSame($tokenExpiration->format('Y-m-d h:i:s'), $this->user->currentHmacToken()->expires->format('Y-m-d h:i:s')); } /** @@ -196,7 +202,10 @@ public function testSetTokenExpirationById(): void */ public function testIsHmacTokenExpired(): void { - $token = $this->user->generateHmacToken('foo', ['foo:bar'], '2024-11-03 12:00:00'); + $tokenExpiration = Time::parse('2024-11-03 12:00:00'); + + $token = $this->user->generateHmacToken('foo', ['foo:bar'], $tokenExpiration); + $this->user->setHmacToken($token); $this->assertTrue($this->user->hasHmacTokenExpired($token)); } @@ -206,8 +215,28 @@ public function testIsHmacTokenExpired(): void */ public function testHmacTokenTimeToExpired(): void { - $token = $this->user->generateHmacToken('foo', ['foo:bar'], '1 year'); + $tokenExpiration = Time::now(); + $tokenExpiration = $tokenExpiration->addYears(1); + + $token = $this->user->generateHmacToken('foo', ['foo:bar'], $tokenExpiration); + + $this->assertSame('in 1 year', $token->expires->humanize()); + } + + /** + * See https://github.com/codeigniter4/shield/issues/926 + */ + public function testCanHmacTokenExpire(): void + { + $tokenExpiration = Time::now(); + $tokenExpiration = $tokenExpiration->addYears(1); + + $token = $this->user->generateHmacToken('foo', ['foo:bar'], $tokenExpiration); + + $this->assertTrue($this->user->canHmacTokenExpire($token)); + + $token = $this->user->generateHmacToken('foo', ['foo:bar']); - $this->assertSame('in 1 year', $this->user->getHmacTokenTimeToExpire($token, 'human')); + $this->assertFalse($this->user->canHmacTokenExpire($token)); } } diff --git a/tests/Commands/HmacTest.php b/tests/Commands/HmacTest.php index 68dc7b358..6134723ce 100644 --- a/tests/Commands/HmacTest.php +++ b/tests/Commands/HmacTest.php @@ -13,6 +13,7 @@ namespace Tests\Commands; +use CodeIgniter\I18n\Time; use CodeIgniter\Shield\Authentication\HMAC\HmacEncrypter; use CodeIgniter\Shield\Commands\Hmac; use CodeIgniter\Shield\Config\AuthToken; @@ -147,12 +148,14 @@ public function testBadCommand(): void public function testExpireAll(): void { /** @var User $user */ + $tokenExpiration = Time::parse('2024-11-03 12:00:00'); + $user = fake(UserModel::class); - $user->generateHmacToken('foo', ['*'], '2024-10-01 12:20:00'); + $user->generateHmacToken('foo', ['*'], $tokenExpiration); $user->generateHmacToken('bar'); $this->setMockIo([]); - $this->assertNotFalse(command('shield:hmac expireAll')); + $this->assertNotFalse(command('shield:hmac invalidateAll')); $resultsString = $this->io->getOutputs(); $results = explode("\n", trim($resultsString)); From ae4de7ba117e4aebe24f7cd77c722f56b60eea5a Mon Sep 17 00:00:00 2001 From: CosDiabos <14842802+CosDiabos@users.noreply.github.com> Date: Thu, 13 Feb 2025 16:09:23 +0100 Subject: [PATCH 05/16] fix file --- src/Models/UserIdentityModel.php | 43 -------------------------------- 1 file changed, 43 deletions(-) diff --git a/src/Models/UserIdentityModel.php b/src/Models/UserIdentityModel.php index b3b8cd4f2..6aa5acabd 100644 --- a/src/Models/UserIdentityModel.php +++ b/src/Models/UserIdentityModel.php @@ -24,8 +24,6 @@ use CodeIgniter\Shield\Entities\UserIdentity; use CodeIgniter\Shield\Exceptions\LogicException; use CodeIgniter\Shield\Exceptions\ValidationException; -use DateInterval; -use DateTime; use Exception; use Faker\Generator; use InvalidArgumentException; @@ -104,39 +102,6 @@ private function checkUserId(User $user): void } } - private function checkExpiresAtFormat(string $expiresAt): string - { - $expireMatch = []; - - // Check Y-m-d h:i:s format. - preg_match('/\d{4}-\d{2}-\d{2}.\d{2}:\d{2}:\d{2}/', $expiresAt, $expireMatch, PREG_UNMATCHED_AS_NULL); - - // Format Y-m-d h:i:s not found. - if ($expireMatch === []) { - // Looking for relative format like 1 day, 2 weeks and process all - preg_match_all('/[1].(second|minute|hour|day|week|month|year)|[2,3,4,5,6,7,8,9].(seconds|minutes|hours|days|weeks|months|years)/', $expiresAt, $expireMatch, PREG_PATTERN_ORDER); - - if ($expireMatch[0] !== []) { - // Dummy DateTime to add() DateInterval - $dateTime = new DateTime(); - - // Turn the preg_match array into string splitted by ' + ' - // to be fed into DateTime->add() to generate new date - $relativeTime = implode(' + ', $expireMatch[0]); - - // add relative formats - $dateTime->add(DateInterval::createFromDateString($relativeTime)); - - // return Dummy DateTime - return $dateTime->format('Y-m-d h:i:s'); - } - - throw new InvalidArgumentException('$expiresAt should be a DateTime string formatted as "Y-m-d h:i:s" or a DateTime relative formats.'); - } - - return $expiresAt; - } - /** * Create an identity with 6 digits code for auth action * @@ -190,10 +155,6 @@ public function generateAccessToken(User $user, string $name, array $scopes = [' { $this->checkUserId($user); - // if ($expiresAt !== null && $expiresAt !== '' && $expiresAt !== '0') { - // $expiresAt = $this->checkExpiresAtFormat($expiresAt); - // } - helper('text'); $return = $this->insert([ @@ -320,10 +281,6 @@ public function generateHmacToken(User $user, string $name, array $scopes = ['*' { $this->checkUserId($user); - // if ($expiresAt !== null && $expiresAt !== '' && $expiresAt !== '0') { - // $expiresAt = $this->checkExpiresAtFormat($expiresAt); - // } - $encrypter = new HmacEncrypter(); $rawSecretKey = $encrypter->generateSecretKey(); $secretKey = $encrypter->encrypt($rawSecretKey); From 744510465294bf0824b3007576f99ece79d3a957 Mon Sep 17 00:00:00 2001 From: CosDiabos <14842802+CosDiabos@users.noreply.github.com> Date: Fri, 14 Feb 2025 10:13:58 +0100 Subject: [PATCH 06/16] fix namespace case. fix hmac invalidateAll() expires field. --- phpstan-baseline.php | 8 +++++++- tests/Authentication/HasAccessTokensTest.php | 2 +- tests/Authentication/HasHmacTokensTest.php | 2 +- tests/Commands/HmacTest.php | 2 +- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/phpstan-baseline.php b/phpstan-baseline.php index e7d630cd2..3ab25cf31 100644 --- a/phpstan-baseline.php +++ b/phpstan-baseline.php @@ -168,7 +168,13 @@ $ignoreErrors[] = [ 'message' => '#^Cannot access property \\$id on array\\\\|object\\.$#', 'identifier' => 'property.nonObject', - 'count' => 7, + 'count' => 9, + 'path' => __DIR__ . '/src/Commands/Hmac.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Cannot access property \\$expires on array\\\\|object\\.$#', + 'identifier' => 'property.nonObject', + 'count' => 3, 'path' => __DIR__ . '/src/Commands/Hmac.php', ]; $ignoreErrors[] = [ diff --git a/tests/Authentication/HasAccessTokensTest.php b/tests/Authentication/HasAccessTokensTest.php index 25d68f5ec..c33564b4d 100644 --- a/tests/Authentication/HasAccessTokensTest.php +++ b/tests/Authentication/HasAccessTokensTest.php @@ -13,7 +13,7 @@ namespace Tests\Authentication; -use Codeigniter\I18n\Time; +use CodeIgniter\I18n\Time; use CodeIgniter\Shield\Entities\AccessToken; use CodeIgniter\Shield\Entities\User; use CodeIgniter\Shield\Models\UserIdentityModel; diff --git a/tests/Authentication/HasHmacTokensTest.php b/tests/Authentication/HasHmacTokensTest.php index 9c71be405..38015366f 100644 --- a/tests/Authentication/HasHmacTokensTest.php +++ b/tests/Authentication/HasHmacTokensTest.php @@ -13,7 +13,7 @@ namespace Tests\Authentication; -use Codeigniter\I18n\Time; +use CodeIgniter\I18n\Time; use CodeIgniter\Shield\Entities\AccessToken; use CodeIgniter\Shield\Entities\User; use CodeIgniter\Shield\Models\UserIdentityModel; diff --git a/tests/Commands/HmacTest.php b/tests/Commands/HmacTest.php index 6134723ce..9439324f8 100644 --- a/tests/Commands/HmacTest.php +++ b/tests/Commands/HmacTest.php @@ -147,9 +147,9 @@ public function testBadCommand(): void */ public function testExpireAll(): void { - /** @var User $user */ $tokenExpiration = Time::parse('2024-11-03 12:00:00'); + /** @var User $user */ $user = fake(UserModel::class); $user->generateHmacToken('foo', ['*'], $tokenExpiration); $user->generateHmacToken('bar'); From 023b31080ef62e37a19dbaa1173ee96467753b98 Mon Sep 17 00:00:00 2001 From: CosDiabos <14842802+CosDiabos@users.noreply.github.com> Date: Mon, 17 Feb 2025 15:40:00 +0100 Subject: [PATCH 07/16] fix hasAccessTokenExpired()& hasHmacTokenExpired() args. fix docs refinement. fix test. --- docs/references/authentication/hmac.md | 11 ++++---- docs/references/authentication/tokens.md | 12 ++++----- src/Authentication/Traits/HasAccessTokens.php | 4 +-- src/Authentication/Traits/HasHmacTokens.php | 4 +-- src/Commands/Hmac.php | 2 +- tests/Authentication/HasAccessTokensTest.php | 20 +++++++------- tests/Authentication/HasHmacTokensTest.php | 26 +++++++++---------- 7 files changed, 38 insertions(+), 41 deletions(-) diff --git a/docs/references/authentication/hmac.md b/docs/references/authentication/hmac.md index ba79f6be3..404b2fffb 100644 --- a/docs/references/authentication/hmac.md +++ b/docs/references/authentication/hmac.md @@ -115,19 +115,17 @@ $expiresAt = Time::parse('2024-11-15 00:00:00'); $token = $user->setHmacTokenExpirationById($token->id, $expiresAt); // Or Expiration date = 1 month + 15 days into the future -$expiresAt = Time::now(); -$expiresAt = $expiresAt->addMonths(1); -$expiresAt = $expiresAt->addDays(15); +$expiresAt = Time::now()->addMonths(1)->addDays(15); $token = $user->setHmacTokenExpirationById($token->id, $expiresAt); ``` The following support methods are also available: -`hasHmacTokenExpired(AccessToken $HmacToken)` - Checks if the HMAC key has expired. Returns `true` if the HMAC key has expired, `false` if not, and `null` if the expire date is null. +`hasHmacTokenExpired(AccessToken $HmacToken)` - Checks if the HMAC key has expired. Returns `true` if the HMAC key has expired, `false` if not. ```php $expiresAt = Time::parse('2024-11-03 12:00:00'); -$token = $this->user->generateHmacToken('foo', ['foo:bar'], $expiresAt); +$token = $this->user->generateHmacToken('foo', ['foo.bar'], $expiresAt); $this->user->hasHmacTokenExpired($token); // Returns true ``` @@ -137,7 +135,7 @@ $this->user->hasHmacTokenExpired($token); // Returns true ```php $expiresAt = Time::parse('2024-11-03 12:00:00'); -$token = $this->user->generateHmacToken('foo', ['foo:bar'], $expiresAt); +$token = $this->user->generateHmacToken('foo', ['foo.bar'], $expiresAt); $this->user->canHmacTokenExpire($token); // Returns true $token2 = $this->user->generateHmacToken('bar'); @@ -282,6 +280,7 @@ public $unusedTokenLifetime = YEAR; ``` ### HMAC Keys Expiration vs Lifetime + Expiration and Lifetime are different concepts. The lifetime is the maximum time allowed for the HMAC Key to exist since its last use. HMAC Key expiration, on the other hand, is a set date in which the HMAC Key will cease to function. ### Login Attempt Logging diff --git a/docs/references/authentication/tokens.md b/docs/references/authentication/tokens.md index 98c415673..f5b9b78f3 100644 --- a/docs/references/authentication/tokens.md +++ b/docs/references/authentication/tokens.md @@ -149,28 +149,26 @@ Access Tokens can be set to expire through the `generateAccessToken()` method. T ```php // Expiration date = 2024-11-03 12:00:00 $expiresAt = Time::parse('2024-11-03 12:00:00'); -$token = $this->user->generateAccessToken('foo', ['foo:bar'], $expiresAt); +$token = $this->user->generateAccessToken('foo', ['foo.bar'], $expiresAt); // Expiration date = 2024-11-15 00:00:00 $expiresAt = Time::parse('2024-11-15 00:00:00'); $user->setAccessTokenExpirationById($token->id, $expiresAt); // Or Expiration date = 1 month + 15 days into the future -$expiresAt = Time::now(); -$expiresAt = $expiresAt->addMonths(1); -$expiresAt = $expiresAt->addDays(15); +$expiresAt = Time::now()->addMonths(1)->addDays(15); $user->setAccessTokenExpirationById($token->id, $expiresAt); ``` The following support methods are also available: -`hasAccessTokenExpired(AccessToken $accessToken)` - Checks if Access Token has expired. Returns `true` if the Access Token has expired, `false` if not, and `null` if the expire date is not set. +`hasAccessTokenExpired(AccessToken $accessToken)` - Checks if Access Token has expired. Returns `true` if the Access Token has expired, `false` if not. ```php $expiresAt = Time::parse('2024-11-03 12:00:00'); -$token = $this->user->generateAccessToken('foo', ['foo:bar'], $expiresAt); +$token = $this->user->generateAccessToken('foo', ['foo.bar'], $expiresAt); $this->user->hasAccessTokenExpired($token); // Returns true ``` @@ -180,7 +178,7 @@ $this->user->hasAccessTokenExpired($token); // Returns true ```php $expiresAt = Time::parse('2024-11-03 12:00:00'); -$token = $this->user->generateAccessToken('foo', ['foo:bar'], $expiresAt); +$token = $this->user->generateAccessToken('foo', ['foo.bar'], $expiresAt); $this->user->canAccessTokenExpire($token2); // Returns false $token2 = $this->user->generateAccessToken('bar'); diff --git a/src/Authentication/Traits/HasAccessTokens.php b/src/Authentication/Traits/HasAccessTokens.php index 32cf8c058..169bf1051 100644 --- a/src/Authentication/Traits/HasAccessTokens.php +++ b/src/Authentication/Traits/HasAccessTokens.php @@ -177,7 +177,7 @@ public function setAccessToken(?AccessToken $accessToken): self * * @return bool Returns true if Access Token has expired, false if not */ - public function hasAccessTokenExpired(?AccessToken $accessToken): bool + public function hasAccessTokenExpired(AccessToken $accessToken): bool { return $accessToken->expires !== null && $accessToken->expires->isBefore(Time::now()); } @@ -211,6 +211,6 @@ public function setAccessTokenExpirationById(int $id, Time $expiresAt): bool */ public function canAccessTokenExpire(AccessToken $accessToken): bool { - return isset($accessToken->expires); + return $accessToken->expires !== null; } } diff --git a/src/Authentication/Traits/HasHmacTokens.php b/src/Authentication/Traits/HasHmacTokens.php index 343b64583..ec0a51975 100644 --- a/src/Authentication/Traits/HasHmacTokens.php +++ b/src/Authentication/Traits/HasHmacTokens.php @@ -167,7 +167,7 @@ public function setHmacToken(?AccessToken $accessToken): self * * @return bool Returns true if Access Token has expired, false if not */ - public function hasHmacTokenExpired(?AccessToken $accessToken): bool + public function hasHmacTokenExpired(AccessToken $accessToken): bool { return $accessToken->expires !== null && $accessToken->expires->isBefore(Time::now()); } @@ -201,6 +201,6 @@ public function setHmacTokenExpirationById(int $id, Time $expiresAt): bool */ public function canHmacTokenExpire(AccessToken $accessToken): bool { - return isset($accessToken->expires); + return $accessToken->expires !== null; } } diff --git a/src/Commands/Hmac.php b/src/Commands/Hmac.php index 9e290116d..ba1c2fa10 100644 --- a/src/Commands/Hmac.php +++ b/src/Commands/Hmac.php @@ -231,4 +231,4 @@ static function ($identity) use ($uIdModelSub, $that): void { }, ); } -} \ No newline at end of file +} diff --git a/tests/Authentication/HasAccessTokensTest.php b/tests/Authentication/HasAccessTokensTest.php index c33564b4d..a7a18f776 100644 --- a/tests/Authentication/HasAccessTokensTest.php +++ b/tests/Authentication/HasAccessTokensTest.php @@ -141,11 +141,11 @@ public function testTokenCanNoTokenSet(): void public function testTokenCanBasics(): void { - $token = $this->user->generateAccessToken('foo', ['foo:bar']); + $token = $this->user->generateAccessToken('foo', ['foo.bar']); $this->user->setAccessToken($token); - $this->assertTrue($this->user->tokenCan('foo:bar')); - $this->assertFalse($this->user->tokenCan('foo:baz')); + $this->assertTrue($this->user->tokenCan('foo.bar')); + $this->assertFalse($this->user->tokenCan('foo.baz')); } public function testTokenCantNoTokenSet(): void @@ -159,14 +159,14 @@ public function testTokenCantNoTokenSet(): void public function testGenerateTokenWithExpiration(): void { $tokenExpiration = Time::parse('2024-11-03 12:00:00'); - $token = $this->user->generateAccessToken('foo', ['foo:bar'], $tokenExpiration); + $token = $this->user->generateAccessToken('foo', ['foo.bar'], $tokenExpiration); $this->user->setAccessToken($token); $this->assertSame($tokenExpiration->format('Y-m-d h:i:s'), $this->user->currentAccessToken()->expires->format('Y-m-d h:i:s')); $tokenExpiration = $tokenExpiration->addMonths(1); $tokenExpiration = $tokenExpiration->addYears(1); - $token = $this->user->generateAccessToken('foo', ['foo:bar'], $tokenExpiration); + $token = $this->user->generateAccessToken('foo', ['foo.bar'], $tokenExpiration); $this->user->setAccessToken($token); $this->assertSame($tokenExpiration->format('Y-m-d h:i:s'), $this->user->currentAccessToken()->expires->format('Y-m-d h:i:s')); @@ -177,7 +177,7 @@ public function testGenerateTokenWithExpiration(): void */ public function testSetTokenExpirationById(): void { - $token = $this->user->generateAccessToken('foo', ['foo:bar']); + $token = $this->user->generateAccessToken('foo', ['foo.bar']); $this->user->setAccessToken($token); @@ -195,7 +195,7 @@ public function testSetTokenExpirationById(): void public function testIsTokenExpired(): void { $tokenExpiration = Time::parse('2024-11-03 12:00:00'); - $token = $this->user->generateAccessToken('foo', ['foo:bar'], $tokenExpiration); + $token = $this->user->generateAccessToken('foo', ['foo.bar'], $tokenExpiration); $this->user->setAccessToken($token); $this->assertTrue($this->user->hasAccessTokenExpired($this->user->currentAccessToken())); @@ -209,7 +209,7 @@ public function testTokenTimeToExpired(): void $tokenExpiration = Time::now(); $tokenExpiration = $tokenExpiration->addYears(1); - $token = $this->user->generateAccessToken('foo', ['foo:bar'], $tokenExpiration); + $token = $this->user->generateAccessToken('foo', ['foo.bar'], $tokenExpiration); $this->user->setAccessToken($token); $this->assertSame('in 1 year', $this->user->currentAccessToken()->expires->humanize()); @@ -223,11 +223,11 @@ public function testCanHmacTokenExpire(): void $tokenExpiration = Time::now(); $tokenExpiration = $tokenExpiration->addYears(1); - $token = $this->user->generateAccessToken('foo', ['foo:bar'], $tokenExpiration); + $token = $this->user->generateAccessToken('foo', ['foo.bar'], $tokenExpiration); $this->assertTrue($this->user->canAccessTokenExpire($token)); - $token = $this->user->generateAccessToken('foo', ['foo:bar']); + $token = $this->user->generateAccessToken('foo', ['foo.bar']); $this->assertFalse($this->user->canAccessTokenExpire($token)); } diff --git a/tests/Authentication/HasHmacTokensTest.php b/tests/Authentication/HasHmacTokensTest.php index 38015366f..f08b08318 100644 --- a/tests/Authentication/HasHmacTokensTest.php +++ b/tests/Authentication/HasHmacTokensTest.php @@ -136,11 +136,11 @@ public function testHmacTokenCanNoTokenSet(): void public function testHmacTokenCanBasics(): void { - $token = $this->user->generateHmacToken('foo', ['foo:bar']); + $token = $this->user->generateHmacToken('foo', ['foo.bar']); $this->user->setHmacToken($token); - $this->assertTrue($this->user->hmacTokenCan('foo:bar')); - $this->assertFalse($this->user->hmacTokenCan('foo:baz')); + $this->assertTrue($this->user->hmacTokenCan('foo.bar')); + $this->assertFalse($this->user->hmacTokenCan('foo.baz')); } public function testHmacTokenCantNoTokenSet(): void @@ -150,11 +150,11 @@ public function testHmacTokenCantNoTokenSet(): void public function testHmacTokenCant(): void { - $token = $this->user->generateHmacToken('foo', ['foo:bar']); + $token = $this->user->generateHmacToken('foo', ['foo.bar']); $this->user->setHmacToken($token); - $this->assertFalse($this->user->hmacTokenCant('foo:bar')); - $this->assertTrue($this->user->hmacTokenCant('foo:baz')); + $this->assertFalse($this->user->hmacTokenCant('foo.bar')); + $this->assertTrue($this->user->hmacTokenCant('foo.baz')); } /** @@ -164,7 +164,7 @@ public function testGenerateTokenWithExpiration(): void { $tokenExpiration = Time::parse('2024-11-03 12:00:00'); - $token = $this->user->generateHmacToken('foo', ['foo:bar'], $tokenExpiration); + $token = $this->user->generateHmacToken('foo', ['foo.bar'], $tokenExpiration); $this->user->setHmacToken($token); $this->assertSame($tokenExpiration->format('Y-m-d h:i:s'), $this->user->currentHmacToken()->expires->format('Y-m-d h:i:s')); @@ -172,7 +172,7 @@ public function testGenerateTokenWithExpiration(): void $tokenExpiration = $tokenExpiration->addMonths(1); $tokenExpiration = $tokenExpiration->addYears(1); - $token = $this->user->generateHmacToken('foo', ['foo:bar'], $tokenExpiration); + $token = $this->user->generateHmacToken('foo', ['foo.bar'], $tokenExpiration); $this->user->setHmacToken($token); $this->assertSame($tokenExpiration->format('Y-m-d h:i:s'), $this->user->currentHmacToken()->expires->format('Y-m-d h:i:s')); @@ -183,7 +183,7 @@ public function testGenerateTokenWithExpiration(): void */ public function testSetTokenExpirationById(): void { - $token = $this->user->generateHmacToken('foo', ['foo:bar']); + $token = $this->user->generateHmacToken('foo', ['foo.bar']); $this->user->setHmacToken($token); @@ -204,7 +204,7 @@ public function testIsHmacTokenExpired(): void { $tokenExpiration = Time::parse('2024-11-03 12:00:00'); - $token = $this->user->generateHmacToken('foo', ['foo:bar'], $tokenExpiration); + $token = $this->user->generateHmacToken('foo', ['foo.bar'], $tokenExpiration); $this->user->setHmacToken($token); $this->assertTrue($this->user->hasHmacTokenExpired($token)); @@ -218,7 +218,7 @@ public function testHmacTokenTimeToExpired(): void $tokenExpiration = Time::now(); $tokenExpiration = $tokenExpiration->addYears(1); - $token = $this->user->generateHmacToken('foo', ['foo:bar'], $tokenExpiration); + $token = $this->user->generateHmacToken('foo', ['foo.bar'], $tokenExpiration); $this->assertSame('in 1 year', $token->expires->humanize()); } @@ -231,11 +231,11 @@ public function testCanHmacTokenExpire(): void $tokenExpiration = Time::now(); $tokenExpiration = $tokenExpiration->addYears(1); - $token = $this->user->generateHmacToken('foo', ['foo:bar'], $tokenExpiration); + $token = $this->user->generateHmacToken('foo', ['foo.bar'], $tokenExpiration); $this->assertTrue($this->user->canHmacTokenExpire($token)); - $token = $this->user->generateHmacToken('foo', ['foo:bar']); + $token = $this->user->generateHmacToken('foo', ['foo.bar']); $this->assertFalse($this->user->canHmacTokenExpire($token)); } From b6b6f2789f65c9d598ff7d9c71a82abe3e1c0c1d Mon Sep 17 00:00:00 2001 From: CosDiabos <14842802+CosDiabos@users.noreply.github.com> Date: Tue, 18 Feb 2025 10:42:06 +0100 Subject: [PATCH 08/16] fix token expiration args, support remove expiration. fix tokens / hmac docs. fix psalm xml attribute: ensureOverrideAttribute. --- docs/references/authentication/hmac.md | 7 +++++-- docs/references/authentication/tokens.md | 7 +++++-- psalm.xml | 1 + src/Authentication/Traits/HasAccessTokens.php | 2 +- src/Authentication/Traits/HasHmacTokens.php | 2 +- src/Models/UserIdentityModel.php | 12 ++++-------- tests/Authentication/HasAccessTokensTest.php | 17 +++++++++++++++++ tests/Authentication/HasHmacTokensTest.php | 17 +++++++++++++++++ 8 files changed, 51 insertions(+), 14 deletions(-) diff --git a/docs/references/authentication/hmac.md b/docs/references/authentication/hmac.md index 404b2fffb..5277ad053 100644 --- a/docs/references/authentication/hmac.md +++ b/docs/references/authentication/hmac.md @@ -101,7 +101,7 @@ $user->revokeAllHmacTokens(); By default, the HMAC keys don't expire unless they meet the HMAC Keys lifetime expiration after their last used date. -HMAC keys can be set to expire through the `generateHmacToken()` method. This takes the expiration date as the $expiresAt argument. It's also possible to update an existing HMAC key using `setHmacTokenExpirationById($HmacTokenID, $expiresAt)` +HMAC keys can be set to expire through the `generateHmacToken()` method. This takes the expiration date as the $expiresAt argument. To update or remove an existing HMAC key expiration date use `setHmacTokenExpirationById($HmacTokenID, $expiresAt)` `$expiresAt` [Time](/libraries/time.html) object @@ -114,9 +114,12 @@ $token = $this->user->generateHmacToken('foo', ['foo:bar'], $expiresAt); $expiresAt = Time::parse('2024-11-15 00:00:00'); $token = $user->setHmacTokenExpirationById($token->id, $expiresAt); -// Or Expiration date = 1 month + 15 days into the future +// Expiration date = 1 month + 15 days into the future $expiresAt = Time::now()->addMonths(1)->addDays(15); $token = $user->setHmacTokenExpirationById($token->id, $expiresAt); + +// Remove the expiration date +$token = $user->setHmacTokenExpirationById($token->id, null); ``` The following support methods are also available: diff --git a/docs/references/authentication/tokens.md b/docs/references/authentication/tokens.md index f5b9b78f3..723421226 100644 --- a/docs/references/authentication/tokens.md +++ b/docs/references/authentication/tokens.md @@ -142,7 +142,7 @@ public $unusedTokenLifetime = YEAR; By default, the Access Tokens don't expire unless they meet the Access Token lifetime expiration after their last used date. -Access Tokens can be set to expire through the `generateAccessToken()` method. This takes the expiration date as the $expiresAt argument. It's also possible to update an existing HMAC key using `setAccessTokenById($HmacTokenID, $expiresAt)` +Access Tokens can be set to expire through the `generateAccessToken()` method. This takes the expiration date as the $expiresAt argument. To update or remove an existing HMAC key expiration date use `setAccessTokenById($HmacTokenID, $expiresAt)` `$expiresAt` [Time](/libraries/time.html) object @@ -157,8 +157,10 @@ $user->setAccessTokenExpirationById($token->id, $expiresAt); // Or Expiration date = 1 month + 15 days into the future $expiresAt = Time::now()->addMonths(1)->addDays(15); - $user->setAccessTokenExpirationById($token->id, $expiresAt); + +// Remove the expiration date +$token = $user->setAccessTokenExpirationById($token->id, null); ``` The following support methods are also available: @@ -187,6 +189,7 @@ $this->user->canAccessTokenExpire($token); // Returns true ### Access Token Expiration vs Lifetime + Expiration and Lifetime are different concepts. The lifetime is the maximum time allowed for the token to exist since its last use. Token expiration, on the other hand, is a set date in which the Access Token will cease to function. ### Login Attempt Logging diff --git a/psalm.xml b/psalm.xml index 5b3a32593..61677a083 100644 --- a/psalm.xml +++ b/psalm.xml @@ -11,6 +11,7 @@ errorBaseline="psalm-baseline.xml" findUnusedBaselineEntry="false" findUnusedCode="false" + ensureOverrideAttribute="false" > diff --git a/src/Authentication/Traits/HasAccessTokens.php b/src/Authentication/Traits/HasAccessTokens.php index 169bf1051..9dbc51fd8 100644 --- a/src/Authentication/Traits/HasAccessTokens.php +++ b/src/Authentication/Traits/HasAccessTokens.php @@ -190,7 +190,7 @@ public function hasAccessTokenExpired(AccessToken $accessToken): bool * * @return bool Returns true if expiration date is set or updated. */ - public function setAccessTokenExpirationById(int $id, Time $expiresAt): bool + public function setAccessTokenExpirationById(int $id, ?Time $expiresAt): bool { /** @var UserIdentityModel $identityModel */ $identityModel = model(UserIdentityModel::class); diff --git a/src/Authentication/Traits/HasHmacTokens.php b/src/Authentication/Traits/HasHmacTokens.php index ec0a51975..ad1a8e043 100644 --- a/src/Authentication/Traits/HasHmacTokens.php +++ b/src/Authentication/Traits/HasHmacTokens.php @@ -180,7 +180,7 @@ public function hasHmacTokenExpired(AccessToken $accessToken): bool * * @return bool Returns true if expiration date is set or updated. */ - public function setHmacTokenExpirationById(int $id, Time $expiresAt): bool + public function setHmacTokenExpirationById(int $id, ?Time $expiresAt): bool { /** @var UserIdentityModel $identityModel */ $identityModel = model(UserIdentityModel::class); diff --git a/src/Models/UserIdentityModel.php b/src/Models/UserIdentityModel.php index 6aa5acabd..8a16bdb35 100644 --- a/src/Models/UserIdentityModel.php +++ b/src/Models/UserIdentityModel.php @@ -241,14 +241,10 @@ public function setIdentityExpirationById($id, User $user, ?Time $expiresAt = nu { $this->checkUserId($user); - if ($expiresAt !== null) { - return $this->where('user_id', $user->id) - ->where('type', $type_token) - ->set(['expires' => $expiresAt]) - ->update($id); - } - - return false; + return $this->where('user_id', $user->id) + ->where('type', $type_token) + ->set(['expires' => $expiresAt]) + ->update($id); } // HMAC diff --git a/tests/Authentication/HasAccessTokensTest.php b/tests/Authentication/HasAccessTokensTest.php index a7a18f776..fae520c14 100644 --- a/tests/Authentication/HasAccessTokensTest.php +++ b/tests/Authentication/HasAccessTokensTest.php @@ -231,4 +231,21 @@ public function testCanHmacTokenExpire(): void $this->assertFalse($this->user->canAccessTokenExpire($token)); } + + /** + * See https://github.com/codeigniter4/shield/issues/926 + */ + public function testAccessTokenRemoveExpiration(): void + { + $tokenExpiration = Time::now(); + $tokenExpiration = $tokenExpiration->addYears(1); + + $token = $this->user->generateAccessToken('foo', ['foo.bar'], $tokenExpiration); + + $this->assertTrue($this->user->canAccessTokenExpire($token)); + + $this->user->setAccessTokenExpirationById($token->id, null); + + $this->assertFalse($this->user->canAccessTokenExpire($this->user->currentAccessToken())); + } } diff --git a/tests/Authentication/HasHmacTokensTest.php b/tests/Authentication/HasHmacTokensTest.php index f08b08318..a4585af7e 100644 --- a/tests/Authentication/HasHmacTokensTest.php +++ b/tests/Authentication/HasHmacTokensTest.php @@ -239,4 +239,21 @@ public function testCanHmacTokenExpire(): void $this->assertFalse($this->user->canHmacTokenExpire($token)); } + + /** + * See https://github.com/codeigniter4/shield/issues/926 + */ + public function testHmacTokenRemoveExpiration(): void + { + $tokenExpiration = Time::now(); + $tokenExpiration = $tokenExpiration->addYears(1); + + $token = $this->user->generateHmacToken('foo', ['foo.bar'], $tokenExpiration); + + $this->assertTrue($this->user->canAccessTokenExpire($token)); + + $this->user->setHmacTokenExpirationById($token->id, null); + + $this->assertFalse($this->user->canHmacTokenExpire($this->user->currentAccessToken())); + } } From 9682431f0839b3978a292915f04adb777c219a40 Mon Sep 17 00:00:00 2001 From: CosDiabos <14842802+CosDiabos@users.noreply.github.com> Date: Wed, 19 Feb 2025 13:12:07 +0100 Subject: [PATCH 09/16] fix tokens expiration method names. fix docs and tests. --- docs/references/authentication/hmac.md | 22 ++++++------ docs/references/authentication/tokens.md | 14 ++++---- phpstan-baseline.php | 2 +- src/Authentication/Traits/HasAccessTokens.php | 35 ++++++++++++++---- src/Authentication/Traits/HasHmacTokens.php | 36 ++++++++++++++----- src/Models/UserIdentityModel.php | 8 ++--- tests/Authentication/HasAccessTokensTest.php | 30 ++++++++-------- tests/Authentication/HasHmacTokensTest.php | 26 +++++++------- 8 files changed, 107 insertions(+), 66 deletions(-) diff --git a/docs/references/authentication/hmac.md b/docs/references/authentication/hmac.md index 5277ad053..12e8543a3 100644 --- a/docs/references/authentication/hmac.md +++ b/docs/references/authentication/hmac.md @@ -101,30 +101,30 @@ $user->revokeAllHmacTokens(); By default, the HMAC keys don't expire unless they meet the HMAC Keys lifetime expiration after their last used date. -HMAC keys can be set to expire through the `generateHmacToken()` method. This takes the expiration date as the $expiresAt argument. To update or remove an existing HMAC key expiration date use `setHmacTokenExpirationById($HmacTokenID, $expiresAt)` +HMAC keys can be set to expire through the `generateHmacToken()` method. This takes the expiration date as the `$expiresAt` argument. To update an existing HMAC key expiration date use `updateHmacTokenExpiration($hmacTokenID, $expiresAt)` and to remove `removeHmacTokenExpiration($hmacTokenID)`. -`$expiresAt` [Time](/libraries/time.html) object +`$expiresAt` [Time](https://codeigniter.com/user_guide/libraries/time.html) object ```php // Expiration date = 2024-11-03 12:00:00 $expiresAt = Time::parse('2024-11-03 12:00:00'); -$token = $this->user->generateHmacToken('foo', ['foo:bar'], $expiresAt); +$token = $this->user->generateHmacToken('foo', ['foo.bar'], $expiresAt); // Expiration date = 2024-11-15 00:00:00 $expiresAt = Time::parse('2024-11-15 00:00:00'); -$token = $user->setHmacTokenExpirationById($token->id, $expiresAt); +$user->updateHmacTokenExpiration($token->id, $expiresAt); // Expiration date = 1 month + 15 days into the future $expiresAt = Time::now()->addMonths(1)->addDays(15); -$token = $user->setHmacTokenExpirationById($token->id, $expiresAt); +$user->updateHmacTokenExpiration($token->id, $expiresAt); // Remove the expiration date -$token = $user->setHmacTokenExpirationById($token->id, null); +$user->removeHmacTokenExpiration($token->id); ``` The following support methods are also available: -`hasHmacTokenExpired(AccessToken $HmacToken)` - Checks if the HMAC key has expired. Returns `true` if the HMAC key has expired, `false` if not. +`isHmacTokenExpired(AccessToken $HmacToken)` - Checks if the HMAC key is expired. Returns `true` if the HMAC key is expired, `false` if not. ```php $expiresAt = Time::parse('2024-11-03 12:00:00'); @@ -133,7 +133,7 @@ $token = $this->user->generateHmacToken('foo', ['foo.bar'], $expiresAt); $this->user->hasHmacTokenExpired($token); // Returns true ``` -`canHmacTokenExpire(AccessToken $HmacToken)` - Checks if HMAC key has an expiration set. Returns `true` or `false` accordingly. +`hasHmacTokenExpiry(AccessToken $HmacToken)` - Checks if HMAC key has an expiration set. Returns `true` or `false` accordingly. ```php $expiresAt = Time::parse('2024-11-03 12:00:00'); @@ -146,10 +146,12 @@ $this->user->canHmacTokenExpire($token2); // Returns false ``` You can also easily set all existing HMAC keys/tokens as expired with the `spark` command: -``` +```console php spark shield:hmac invalidateAll ``` -**Careful!** This command invalidates _all_ keys for _all_ users. + +!!! warning + This command invalidates _all_ keys for _all_ users. ## Retrieving HMAC Keys diff --git a/docs/references/authentication/tokens.md b/docs/references/authentication/tokens.md index 723421226..36032783a 100644 --- a/docs/references/authentication/tokens.md +++ b/docs/references/authentication/tokens.md @@ -142,9 +142,9 @@ public $unusedTokenLifetime = YEAR; By default, the Access Tokens don't expire unless they meet the Access Token lifetime expiration after their last used date. -Access Tokens can be set to expire through the `generateAccessToken()` method. This takes the expiration date as the $expiresAt argument. To update or remove an existing HMAC key expiration date use `setAccessTokenById($HmacTokenID, $expiresAt)` +Access Tokens can be set to expire through the `generateAccessToken()` method. This takes the expiration date as the `$expiresAt` argument. To update an existing HMAC key expiration date use `updateAcessTokenExpiration($hmacTokenID, $expiresAt)` and to remove `removeAccessTokenExpiration($hmacTokenID)`. -`$expiresAt` [Time](/libraries/time.html) object +`$expiresAt` [Time](https://codeigniter.com/user_guide/libraries/time.html) object ```php // Expiration date = 2024-11-03 12:00:00 @@ -153,19 +153,19 @@ $token = $this->user->generateAccessToken('foo', ['foo.bar'], $expiresAt); // Expiration date = 2024-11-15 00:00:00 $expiresAt = Time::parse('2024-11-15 00:00:00'); -$user->setAccessTokenExpirationById($token->id, $expiresAt); +$user->updateAcessTokenExpiration($token->id, $expiresAt); // Or Expiration date = 1 month + 15 days into the future $expiresAt = Time::now()->addMonths(1)->addDays(15); -$user->setAccessTokenExpirationById($token->id, $expiresAt); +$user->updateAcessTokenExpiration($token->id, $expiresAt); // Remove the expiration date -$token = $user->setAccessTokenExpirationById($token->id, null); +$user->removeAccessTokenExpiration($token->id); ``` The following support methods are also available: -`hasAccessTokenExpired(AccessToken $accessToken)` - Checks if Access Token has expired. Returns `true` if the Access Token has expired, `false` if not. +`isAccessTokenExpired(AccessToken $accessToken)` - Checks if Access Token is expired. Returns `true` if the Access Token is expired, `false` if not. ```php $expiresAt = Time::parse('2024-11-03 12:00:00'); @@ -175,7 +175,7 @@ $token = $this->user->generateAccessToken('foo', ['foo.bar'], $expiresAt); $this->user->hasAccessTokenExpired($token); // Returns true ``` -`canAccessTokenExpire(AccessToken $HmacToken)` - Checks if Access Token has an expiration set. Returns `true` or `false` accordingly. +`hasAccessTokenExpiry(AccessToken $HmacToken)` - Checks if Access Token has an expiration set. Returns `true` or `false` accordingly. ```php $expiresAt = Time::parse('2024-11-03 12:00:00'); diff --git a/phpstan-baseline.php b/phpstan-baseline.php index 3ab25cf31..59cc29d60 100644 --- a/phpstan-baseline.php +++ b/phpstan-baseline.php @@ -265,7 +265,7 @@ $ignoreErrors[] = [ 'message' => '#^Call to function model with CodeIgniter\\\\Shield\\\\Models\\\\UserIdentityModel\\:\\:class is discouraged\\.$#', 'identifier' => 'codeigniter.factoriesClassConstFetch', - 'count' => 21, + 'count' => 23, 'path' => __DIR__ . '/src/Entities/User.php', ]; $ignoreErrors[] = [ diff --git a/src/Authentication/Traits/HasAccessTokens.php b/src/Authentication/Traits/HasAccessTokens.php index 9dbc51fd8..9cb535703 100644 --- a/src/Authentication/Traits/HasAccessTokens.php +++ b/src/Authentication/Traits/HasAccessTokens.php @@ -39,7 +39,7 @@ trait HasAccessTokens * * @param string $name Token name * @param list $scopes Permissions the token grants - * @param Time $expiresAt Expiration date + * @param Time|null $expiresAt Expiration date * * @throws InvalidArgumentException */ @@ -173,11 +173,11 @@ public function setAccessToken(?AccessToken $accessToken): self } /** - * Checks if the provided Access Token has expired. + * Checks if the provided Access Token is expired. * - * @return bool Returns true if Access Token has expired, false if not + * @return bool Returns true if Access Token is expired, false if not */ - public function hasAccessTokenExpired(AccessToken $accessToken): bool + public function isAccessTokenExpired(AccessToken $accessToken): bool { return $accessToken->expires !== null && $accessToken->expires->isBefore(Time::now()); } @@ -190,11 +190,32 @@ public function hasAccessTokenExpired(AccessToken $accessToken): bool * * @return bool Returns true if expiration date is set or updated. */ - public function setAccessTokenExpirationById(int $id, ?Time $expiresAt): bool + public function updateAccessTokenExpiration(int $id, Time $expiresAt): bool { /** @var UserIdentityModel $identityModel */ $identityModel = model(UserIdentityModel::class); - $result = $identityModel->setIdentityExpirationById($id, $this, $expiresAt, AccessTokens::ID_TYPE_ACCESS_TOKEN); + $result = $identityModel->setIdentityExpirationById($id, $this, $expiresAt); + + if ($result) { + // refresh currentAccessToken with updated data + $this->currentAccessToken = $identityModel->getAccessTokenById($id, $this); + } + + return $result; + } + + /** + * Removes the expiration date for Access Tokens by ID. + * + * @param int $id AccessTokens ID + * + * @return bool Returns true if expiration date is set or updated. + */ + public function removeAccessTokenExpiration(int $id): bool + { + /** @var UserIdentityModel $identityModel */ + $identityModel = model(UserIdentityModel::class); + $result = $identityModel->setIdentityExpirationById($id, $this); if ($result) { // refresh currentAccessToken with updated data @@ -209,7 +230,7 @@ public function setAccessTokenExpirationById(int $id, ?Time $expiresAt): bool * * @return bool Returns true if AccessToken can expire. */ - public function canAccessTokenExpire(AccessToken $accessToken): bool + public function hasAccessTokenExpiry(AccessToken $accessToken): bool { return $accessToken->expires !== null; } diff --git a/src/Authentication/Traits/HasHmacTokens.php b/src/Authentication/Traits/HasHmacTokens.php index ad1a8e043..95759a695 100644 --- a/src/Authentication/Traits/HasHmacTokens.php +++ b/src/Authentication/Traits/HasHmacTokens.php @@ -14,7 +14,6 @@ namespace CodeIgniter\Shield\Authentication\Traits; use CodeIgniter\I18n\Time; -use CodeIgniter\Shield\Authentication\Authenticators\HmacSha256; use CodeIgniter\Shield\Entities\AccessToken; use CodeIgniter\Shield\Models\UserIdentityModel; use InvalidArgumentException; @@ -40,7 +39,7 @@ trait HasHmacTokens * * @param string $name Token name * @param list $scopes Permissions the token grants - * @param Time $expiresAt Expiration date + * @param Time|null $expiresAt Expiration date * * @throws InvalidArgumentException * @throws ReflectionException @@ -163,11 +162,11 @@ public function setHmacToken(?AccessToken $accessToken): self } /** - * Checks if the provided Access Token has expired. + * Checks if the provided Access Token is expired. * - * @return bool Returns true if Access Token has expired, false if not + * @return bool Returns true if Access Token is expired, false if not */ - public function hasHmacTokenExpired(AccessToken $accessToken): bool + public function isHmacTokenExpired(AccessToken $accessToken): bool { return $accessToken->expires !== null && $accessToken->expires->isBefore(Time::now()); } @@ -180,11 +179,11 @@ public function hasHmacTokenExpired(AccessToken $accessToken): bool * * @return bool Returns true if expiration date is set or updated. */ - public function setHmacTokenExpirationById(int $id, ?Time $expiresAt): bool + public function updateHmacTokenExpiration(int $id, Time $expiresAt): bool { /** @var UserIdentityModel $identityModel */ $identityModel = model(UserIdentityModel::class); - $result = $identityModel->setIdentityExpirationById($id, $this, $expiresAt, HmacSha256::ID_TYPE_HMAC_TOKEN); + $result = $identityModel->setIdentityExpirationById($id, $this, $expiresAt); if ($result) { // refresh currentAccessToken with updated data @@ -194,12 +193,33 @@ public function setHmacTokenExpirationById(int $id, ?Time $expiresAt): bool return $result; } + /** + * Removes the expiration date for Hmac Key/Token by ID. + * + * @param int $id AccessToken ID + * + * @return bool Returns true if expiration date is removed. + */ + public function removeHmacTokenExpiration(int $id): bool + { + /** @var UserIdentityModel $identityModel */ + $identityModel = model(UserIdentityModel::class); + $result = $identityModel->setIdentityExpirationById($id, $this); + + if ($result) { + // refresh currentAccessToken with updated data + $this->currentHmacToken = $identityModel->getHmacTokenById($id, $this); + } + + return $result; + } + /** * Checks if the current Hmac token can expire * * @return bool Returns true if Hmac Token can expire. */ - public function canHmacTokenExpire(AccessToken $accessToken): bool + public function hasHmacTokenExpiry(AccessToken $accessToken): bool { return $accessToken->expires !== null; } diff --git a/src/Models/UserIdentityModel.php b/src/Models/UserIdentityModel.php index 8a16bdb35..c3f6b42e5 100644 --- a/src/Models/UserIdentityModel.php +++ b/src/Models/UserIdentityModel.php @@ -230,21 +230,21 @@ public function getAllAccessTokens(User $user): array } /** - * Updates or sets expiration date of users' AccessToken or HMAC Token by ID. Returns updated row. + * Updates or sets expiration date of users' AccessToken or HMAC Token by ID. * * @param Time $expiresAt Expiration date * @param mixed $id * * @return bool Returns true if expiration date was set or updated. */ - public function setIdentityExpirationById($id, User $user, ?Time $expiresAt = null, ?string $type_token = null): bool + public function setIdentityExpirationById($id, User $user, ?Time $expiresAt = null): bool { $this->checkUserId($user); return $this->where('user_id', $user->id) - ->where('type', $type_token) + ->where('id', $id) ->set(['expires' => $expiresAt]) - ->update($id); + ->update(); } // HMAC diff --git a/tests/Authentication/HasAccessTokensTest.php b/tests/Authentication/HasAccessTokensTest.php index fae520c14..808c93f60 100644 --- a/tests/Authentication/HasAccessTokensTest.php +++ b/tests/Authentication/HasAccessTokensTest.php @@ -164,8 +164,7 @@ public function testGenerateTokenWithExpiration(): void $this->assertSame($tokenExpiration->format('Y-m-d h:i:s'), $this->user->currentAccessToken()->expires->format('Y-m-d h:i:s')); - $tokenExpiration = $tokenExpiration->addMonths(1); - $tokenExpiration = $tokenExpiration->addYears(1); + $tokenExpiration = $tokenExpiration->addMonths(1)->addYears(1); $token = $this->user->generateAccessToken('foo', ['foo.bar'], $tokenExpiration); $this->user->setAccessToken($token); @@ -185,7 +184,7 @@ public function testSetTokenExpirationById(): void $tokenExpiration = Time::parse('2024-11-03 12:00:00'); - $this->assertTrue($this->user->setAccessTokenExpirationById($token->id, $tokenExpiration)); + $this->assertTrue($this->user->updateAccessTokenExpiration($token->id, $tokenExpiration)); $this->assertSame($tokenExpiration->format('Y-m-d h:i:s'), $this->user->currentAccessToken()->expires->format('Y-m-d h:i:s')); } @@ -198,7 +197,7 @@ public function testIsTokenExpired(): void $token = $this->user->generateAccessToken('foo', ['foo.bar'], $tokenExpiration); $this->user->setAccessToken($token); - $this->assertTrue($this->user->hasAccessTokenExpired($this->user->currentAccessToken())); + $this->assertTrue($this->user->isAccessTokenExpired($this->user->currentAccessToken())); } /** @@ -206,8 +205,7 @@ public function testIsTokenExpired(): void */ public function testTokenTimeToExpired(): void { - $tokenExpiration = Time::now(); - $tokenExpiration = $tokenExpiration->addYears(1); + $tokenExpiration = Time::now()->addYears(1); $token = $this->user->generateAccessToken('foo', ['foo.bar'], $tokenExpiration); $this->user->setAccessToken($token); @@ -218,18 +216,17 @@ public function testTokenTimeToExpired(): void /** * See https://github.com/codeigniter4/shield/issues/926 */ - public function testCanHmacTokenExpire(): void + public function testHasHmacTokenExpiry(): void { - $tokenExpiration = Time::now(); - $tokenExpiration = $tokenExpiration->addYears(1); + $tokenExpiration = Time::now()->addYears(1); $token = $this->user->generateAccessToken('foo', ['foo.bar'], $tokenExpiration); - $this->assertTrue($this->user->canAccessTokenExpire($token)); + $this->assertTrue($this->user->hasAccessTokenExpiry($token)); $token = $this->user->generateAccessToken('foo', ['foo.bar']); - $this->assertFalse($this->user->canAccessTokenExpire($token)); + $this->assertFalse($this->user->hasAccessTokenExpiry($token)); } /** @@ -237,15 +234,16 @@ public function testCanHmacTokenExpire(): void */ public function testAccessTokenRemoveExpiration(): void { - $tokenExpiration = Time::now(); - $tokenExpiration = $tokenExpiration->addYears(1); + $tokenExpiration = Time::now()->addYears(1); $token = $this->user->generateAccessToken('foo', ['foo.bar'], $tokenExpiration); - $this->assertTrue($this->user->canAccessTokenExpire($token)); + $this->user->setAccessToken($token); + + $this->assertTrue($this->user->hasAccessTokenExpiry($token)); - $this->user->setAccessTokenExpirationById($token->id, null); + $this->assertTrue($this->user->removeAccessTokenExpiration($token->id)); - $this->assertFalse($this->user->canAccessTokenExpire($this->user->currentAccessToken())); + $this->assertFalse($this->user->hasAccessTokenExpiry($this->user->currentAccessToken())); } } diff --git a/tests/Authentication/HasHmacTokensTest.php b/tests/Authentication/HasHmacTokensTest.php index a4585af7e..8cbcf42d5 100644 --- a/tests/Authentication/HasHmacTokensTest.php +++ b/tests/Authentication/HasHmacTokensTest.php @@ -169,8 +169,7 @@ public function testGenerateTokenWithExpiration(): void $this->assertSame($tokenExpiration->format('Y-m-d h:i:s'), $this->user->currentHmacToken()->expires->format('Y-m-d h:i:s')); - $tokenExpiration = $tokenExpiration->addMonths(1); - $tokenExpiration = $tokenExpiration->addYears(1); + $tokenExpiration = $tokenExpiration->addMonths(1)->addYears(1); $token = $this->user->generateHmacToken('foo', ['foo.bar'], $tokenExpiration); $this->user->setHmacToken($token); @@ -191,7 +190,7 @@ public function testSetTokenExpirationById(): void $tokenExpiration = Time::parse('2024-11-03 12:00:00'); - $this->assertTrue($this->user->setHmacTokenExpirationById($token->id, $tokenExpiration)); + $this->assertTrue($this->user->updateHmacTokenExpiration($token->id, $tokenExpiration)); $this->user->setHmacToken($this->user->getHmacTokenById($token->id)); $this->assertSame($tokenExpiration->format('Y-m-d h:i:s'), $this->user->currentHmacToken()->expires->format('Y-m-d h:i:s')); @@ -207,7 +206,7 @@ public function testIsHmacTokenExpired(): void $token = $this->user->generateHmacToken('foo', ['foo.bar'], $tokenExpiration); $this->user->setHmacToken($token); - $this->assertTrue($this->user->hasHmacTokenExpired($token)); + $this->assertTrue($this->user->isHmacTokenExpired($token)); } /** @@ -226,18 +225,18 @@ public function testHmacTokenTimeToExpired(): void /** * See https://github.com/codeigniter4/shield/issues/926 */ - public function testCanHmacTokenExpire(): void + public function testHasHmacTokenExpiry(): void { $tokenExpiration = Time::now(); $tokenExpiration = $tokenExpiration->addYears(1); $token = $this->user->generateHmacToken('foo', ['foo.bar'], $tokenExpiration); - $this->assertTrue($this->user->canHmacTokenExpire($token)); + $this->assertTrue($this->user->hasHmacTokenExpiry($token)); $token = $this->user->generateHmacToken('foo', ['foo.bar']); - $this->assertFalse($this->user->canHmacTokenExpire($token)); + $this->assertFalse($this->user->hasHmacTokenExpiry($token)); } /** @@ -245,15 +244,16 @@ public function testCanHmacTokenExpire(): void */ public function testHmacTokenRemoveExpiration(): void { - $tokenExpiration = Time::now(); - $tokenExpiration = $tokenExpiration->addYears(1); + $tokenExpiration = Time::now()->addYears(1); - $token = $this->user->generateHmacToken('foo', ['foo.bar'], $tokenExpiration); + $token = $this->user->generateHmacToken('hmac', ['foo.bar'], $tokenExpiration); + + $this->user->setHmacToken($token); - $this->assertTrue($this->user->canAccessTokenExpire($token)); + $this->assertTrue($this->user->hasHmacTokenExpiry($token)); - $this->user->setHmacTokenExpirationById($token->id, null); + $this->assertTrue($this->user->removeHmacTokenExpiration($token->id)); - $this->assertFalse($this->user->canHmacTokenExpire($this->user->currentAccessToken())); + $this->assertFalse($this->user->hasHmacTokenExpiry($this->user->currentHmacToken())); } } From 44e91c956a8345e9c410dc53b9aceceb15e1c140 Mon Sep 17 00:00:00 2001 From: CosDiabos <14842802+CosDiabos@users.noreply.github.com> Date: Wed, 19 Feb 2025 15:58:11 +0100 Subject: [PATCH 10/16] fix hmac docs warning section --- docs/references/authentication/hmac.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/references/authentication/hmac.md b/docs/references/authentication/hmac.md index 12e8543a3..c749b196d 100644 --- a/docs/references/authentication/hmac.md +++ b/docs/references/authentication/hmac.md @@ -151,6 +151,7 @@ php spark shield:hmac invalidateAll ``` !!! warning + This command invalidates _all_ keys for _all_ users. ## Retrieving HMAC Keys From 8b7a2bd9b20ef5a98eb6b2f5df59c3e9a111aff7 Mon Sep 17 00:00:00 2001 From: CosDiabos <14842802+CosDiabos@users.noreply.github.com> Date: Wed, 19 Feb 2025 16:20:32 +0100 Subject: [PATCH 11/16] fix hmac expiration docs sample --- docs/references/authentication/hmac.md | 6 +++--- docs/references/authentication/tokens.md | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/references/authentication/hmac.md b/docs/references/authentication/hmac.md index c749b196d..82f31dc52 100644 --- a/docs/references/authentication/hmac.md +++ b/docs/references/authentication/hmac.md @@ -130,7 +130,7 @@ The following support methods are also available: $expiresAt = Time::parse('2024-11-03 12:00:00'); $token = $this->user->generateHmacToken('foo', ['foo.bar'], $expiresAt); -$this->user->hasHmacTokenExpired($token); // Returns true +$this->user->isHmacTokenExpired($token); // Returns true ``` `hasHmacTokenExpiry(AccessToken $HmacToken)` - Checks if HMAC key has an expiration set. Returns `true` or `false` accordingly. @@ -139,10 +139,10 @@ $this->user->hasHmacTokenExpired($token); // Returns true $expiresAt = Time::parse('2024-11-03 12:00:00'); $token = $this->user->generateHmacToken('foo', ['foo.bar'], $expiresAt); -$this->user->canHmacTokenExpire($token); // Returns true +$this->user->hasHmacTokenExpiry($token); // Returns true $token2 = $this->user->generateHmacToken('bar'); -$this->user->canHmacTokenExpire($token2); // Returns false +$this->user->hasHmacTokenExpiry($token2); // Returns false ``` You can also easily set all existing HMAC keys/tokens as expired with the `spark` command: diff --git a/docs/references/authentication/tokens.md b/docs/references/authentication/tokens.md index 36032783a..37bdf3d9c 100644 --- a/docs/references/authentication/tokens.md +++ b/docs/references/authentication/tokens.md @@ -172,7 +172,7 @@ $expiresAt = Time::parse('2024-11-03 12:00:00'); $token = $this->user->generateAccessToken('foo', ['foo.bar'], $expiresAt); -$this->user->hasAccessTokenExpired($token); // Returns true +$this->user->isAccessTokenExpired($token); // Returns true ``` `hasAccessTokenExpiry(AccessToken $HmacToken)` - Checks if Access Token has an expiration set. Returns `true` or `false` accordingly. @@ -181,10 +181,10 @@ $this->user->hasAccessTokenExpired($token); // Returns true $expiresAt = Time::parse('2024-11-03 12:00:00'); $token = $this->user->generateAccessToken('foo', ['foo.bar'], $expiresAt); -$this->user->canAccessTokenExpire($token2); // Returns false +$this->user->hasAccessTokenExpiry($token2); // Returns false $token2 = $this->user->generateAccessToken('bar'); -$this->user->canAccessTokenExpire($token); // Returns true +$this->user->hasAccessTokenExpiry($token); // Returns true ``` From e12406d2157d5cee1fdeb3c6d5c27df97c43a4f9 Mon Sep 17 00:00:00 2001 From: CosDiabos <14842802+CosDiabos@users.noreply.github.com> Date: Thu, 20 Feb 2025 16:39:41 +0100 Subject: [PATCH 12/16] fix comments and docs --- docs/references/authentication/tokens.md | 2 +- src/Authentication/Traits/HasAccessTokens.php | 6 +----- src/Authentication/Traits/HasHmacTokens.php | 12 ++++-------- src/Commands/Hmac.php | 10 ++++------ 4 files changed, 10 insertions(+), 20 deletions(-) diff --git a/docs/references/authentication/tokens.md b/docs/references/authentication/tokens.md index 37bdf3d9c..38977b99d 100644 --- a/docs/references/authentication/tokens.md +++ b/docs/references/authentication/tokens.md @@ -125,7 +125,7 @@ Configure **app/Config/AuthToken.php** for your needs. ### Access Token Lifetime -Tokens will expire after a specified amount of time has passed since they last have been used. +Tokens will expire after a specified amount of time has passed since they have last been used. By default, this is set to 1 year. You can change this value by setting the `$unusedTokenLifetime` value. This is diff --git a/src/Authentication/Traits/HasAccessTokens.php b/src/Authentication/Traits/HasAccessTokens.php index 9cb535703..5630d0d0d 100644 --- a/src/Authentication/Traits/HasAccessTokens.php +++ b/src/Authentication/Traits/HasAccessTokens.php @@ -174,8 +174,6 @@ public function setAccessToken(?AccessToken $accessToken): self /** * Checks if the provided Access Token is expired. - * - * @return bool Returns true if Access Token is expired, false if not */ public function isAccessTokenExpired(AccessToken $accessToken): bool { @@ -226,9 +224,7 @@ public function removeAccessTokenExpiration(int $id): bool } /** - * Checks if the current Hmac token can expire - * - * @return bool Returns true if AccessToken can expire. + * Checks if the access token has a set expiration date */ public function hasAccessTokenExpiry(AccessToken $accessToken): bool { diff --git a/src/Authentication/Traits/HasHmacTokens.php b/src/Authentication/Traits/HasHmacTokens.php index 95759a695..01f043567 100644 --- a/src/Authentication/Traits/HasHmacTokens.php +++ b/src/Authentication/Traits/HasHmacTokens.php @@ -163,8 +163,6 @@ public function setHmacToken(?AccessToken $accessToken): self /** * Checks if the provided Access Token is expired. - * - * @return bool Returns true if Access Token is expired, false if not */ public function isHmacTokenExpired(AccessToken $accessToken): bool { @@ -172,7 +170,7 @@ public function isHmacTokenExpired(AccessToken $accessToken): bool } /** - * Sets an expiration for Hmac Key/Token by ID. + * Sets an expiration for HMAC token by ID. * * @param int $id AccessToken ID * @param Time $expiresAt Expiration date @@ -194,11 +192,11 @@ public function updateHmacTokenExpiration(int $id, Time $expiresAt): bool } /** - * Removes the expiration date for Hmac Key/Token by ID. + * Removes the expiration date for HMAC token by ID. * * @param int $id AccessToken ID * - * @return bool Returns true if expiration date is removed. + * @return bool Returns true if expiration date is removed */ public function removeHmacTokenExpiration(int $id): bool { @@ -215,9 +213,7 @@ public function removeHmacTokenExpiration(int $id): bool } /** - * Checks if the current Hmac token can expire - * - * @return bool Returns true if Hmac Token can expire. + * Checks if the current HMAC token has a set expiration date */ public function hasHmacTokenExpiry(AccessToken $accessToken): bool { diff --git a/src/Commands/Hmac.php b/src/Commands/Hmac.php index ba1c2fa10..38421cb67 100644 --- a/src/Commands/Hmac.php +++ b/src/Commands/Hmac.php @@ -211,15 +211,13 @@ public function invalidateAll(): void $uIdModel = new UserIdentityModel(); $uIdModelSub = new UserIdentityModel(); - $that = $this; - $uIdModel->where('type', 'hmac_sha256')->orderBy('id')->chunk( 100, - static function ($identity) use ($uIdModelSub, $that): void { - $timeNow = Time::now(); // Current date/time + function ($identity) use ($uIdModelSub): void { + $timeNow = Time::now(); if (null !== $identity->expires && $identity->expires->isBefore($timeNow)) { - $that->write('Hmac Key/Token ID: ' . $identity->id . ', already expired, skipped.'); + $this->write('HMAC Token ID: ' . $identity->id . ', already expired, skipped.'); return; } @@ -227,7 +225,7 @@ static function ($identity) use ($uIdModelSub, $that): void { $identity->expires = $timeNow; $uIdModelSub->save($identity); - $that->write('Hmac Key/Token ID: ' . $identity->id . ', set as expired.'); + $this->write('HMAC Token ID: ' . $identity->id . ', set as expired.'); }, ); } From 97615b537eb79461b5e1110d3dc86c50fc9d8fc2 Mon Sep 17 00:00:00 2001 From: CosDiabos <14842802+CosDiabos@users.noreply.github.com> Date: Thu, 20 Feb 2025 16:50:38 +0100 Subject: [PATCH 13/16] fix test assertion --- tests/Commands/HmacTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Commands/HmacTest.php b/tests/Commands/HmacTest.php index 9439324f8..270aa6f5e 100644 --- a/tests/Commands/HmacTest.php +++ b/tests/Commands/HmacTest.php @@ -161,8 +161,8 @@ public function testExpireAll(): void $results = explode("\n", trim($resultsString)); $this->assertCount(2, $results); - $this->assertSame('Hmac Key/Token ID: 1, already expired, skipped.', trim($results[0])); - $this->assertSame('Hmac Key/Token ID: 2, set as expired.', trim($results[1])); + $this->assertSame('HMAC Token ID: 1, already expired, skipped.', trim($results[0])); + $this->assertSame('HMAC Token ID: 2, set as expired.', trim($results[1])); } /** From 267f9ffb4b21ec7031759b77b0a0975c58313654 Mon Sep 17 00:00:00 2001 From: CosDiabos <14842802+CosDiabos@users.noreply.github.com> Date: Sat, 22 Feb 2025 11:33:50 +0100 Subject: [PATCH 14/16] fix docs and comments. fix updateHmacTokenExpiration() token refresh. --- docs/references/authentication/hmac.md | 10 +++++----- docs/references/authentication/tokens.md | 10 +++++----- src/Authentication/Traits/HasHmacTokens.php | 20 ++++++++++---------- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/docs/references/authentication/hmac.md b/docs/references/authentication/hmac.md index 82f31dc52..03d179abf 100644 --- a/docs/references/authentication/hmac.md +++ b/docs/references/authentication/hmac.md @@ -99,9 +99,9 @@ $user->revokeAllHmacTokens(); ## Expiring HMAC Keys -By default, the HMAC keys don't expire unless they meet the HMAC Keys lifetime expiration after their last used date. +By default, the HMAC keys don't expire unless they reach the HMAC keys' lifetime expiration after their last use date. -HMAC keys can be set to expire through the `generateHmacToken()` method. This takes the expiration date as the `$expiresAt` argument. To update an existing HMAC key expiration date use `updateHmacTokenExpiration($hmacTokenID, $expiresAt)` and to remove `removeHmacTokenExpiration($hmacTokenID)`. +HMAC keys can be set to expire through the `generateHmacToken()` method. This takes the expiration date as the `$expiresAt` argument. To update an existing HMAC key expiration date, use `updateHmacTokenExpiration($hmacTokenID, $expiresAt)`. To remove it, use `removeHmacTokenExpiration($hmacTokenID)`. `$expiresAt` [Time](https://codeigniter.com/user_guide/libraries/time.html) object @@ -124,7 +124,7 @@ $user->removeHmacTokenExpiration($token->id); The following support methods are also available: -`isHmacTokenExpired(AccessToken $HmacToken)` - Checks if the HMAC key is expired. Returns `true` if the HMAC key is expired, `false` if not. +`isHmacTokenExpired(AccessToken $hmacToken)` - Checks if the HMAC key is expired. Returns `true` if the HMAC key is expired; otherwise, returns `false`. ```php $expiresAt = Time::parse('2024-11-03 12:00:00'); @@ -133,7 +133,7 @@ $token = $this->user->generateHmacToken('foo', ['foo.bar'], $expiresAt); $this->user->isHmacTokenExpired($token); // Returns true ``` -`hasHmacTokenExpiry(AccessToken $HmacToken)` - Checks if HMAC key has an expiration set. Returns `true` or `false` accordingly. +`hasHmacTokenExpiry(AccessToken $hmacToken)` - Checks if HMAC key has an expiration set. Returns `true` if the HMAC key is expired; otherwise, returns `false`. ```php $expiresAt = Time::parse('2024-11-03 12:00:00'); @@ -287,7 +287,7 @@ public $unusedTokenLifetime = YEAR; ### HMAC Keys Expiration vs Lifetime -Expiration and Lifetime are different concepts. The lifetime is the maximum time allowed for the HMAC Key to exist since its last use. HMAC Key expiration, on the other hand, is a set date in which the HMAC Key will cease to function. +Expiration and lifetime are two different concepts. The lifetime is the maximum time allowed for the HMAC Key to exist since its last use. HMAC Key expiration, on the other hand, is a set date in which the HMAC Key will cease to function. ### Login Attempt Logging diff --git a/docs/references/authentication/tokens.md b/docs/references/authentication/tokens.md index 38977b99d..7289331d7 100644 --- a/docs/references/authentication/tokens.md +++ b/docs/references/authentication/tokens.md @@ -140,9 +140,9 @@ public $unusedTokenLifetime = YEAR; ## Expiring Access Tokens -By default, the Access Tokens don't expire unless they meet the Access Token lifetime expiration after their last used date. +By default, the Access Tokens don't expire unless they reach the Access Token's lifetime expiration after their last use date. -Access Tokens can be set to expire through the `generateAccessToken()` method. This takes the expiration date as the `$expiresAt` argument. To update an existing HMAC key expiration date use `updateAcessTokenExpiration($hmacTokenID, $expiresAt)` and to remove `removeAccessTokenExpiration($hmacTokenID)`. +Access Tokens can be set to expire through the `generateAccessToken()` method. This takes the expiration date as the `$expiresAt` argument. To update an existing HMAC key expiration date, use `updateAcessTokenExpiration($accessTokenID, $expiresAt)`. To remove it, use `removeAccessTokenExpiration($accessTokenID)`. `$expiresAt` [Time](https://codeigniter.com/user_guide/libraries/time.html) object @@ -165,7 +165,7 @@ $user->removeAccessTokenExpiration($token->id); The following support methods are also available: -`isAccessTokenExpired(AccessToken $accessToken)` - Checks if Access Token is expired. Returns `true` if the Access Token is expired, `false` if not. +`isAccessTokenExpired(AccessToken $accessToken)` - Checks if Access Token is expired. Returns `true` if the Access Token is expired; otherwise, returns `false`. ```php $expiresAt = Time::parse('2024-11-03 12:00:00'); @@ -175,7 +175,7 @@ $token = $this->user->generateAccessToken('foo', ['foo.bar'], $expiresAt); $this->user->isAccessTokenExpired($token); // Returns true ``` -`hasAccessTokenExpiry(AccessToken $HmacToken)` - Checks if Access Token has an expiration set. Returns `true` or `false` accordingly. +`hasAccessTokenExpiry(AccessToken $accessToken)` - Returns `true` if the Access Token has a set expiration date; otherwise, returns `false`. ```php $expiresAt = Time::parse('2024-11-03 12:00:00'); @@ -190,7 +190,7 @@ $this->user->hasAccessTokenExpiry($token); // Returns true ### Access Token Expiration vs Lifetime -Expiration and Lifetime are different concepts. The lifetime is the maximum time allowed for the token to exist since its last use. Token expiration, on the other hand, is a set date in which the Access Token will cease to function. +Expiration and lifetime are two different concepts. The lifetime is the maximum time allowed for the token to exist since its last use. Token expiration, on the other hand, is a set date in which the Access Token will cease to function. ### Login Attempt Logging diff --git a/src/Authentication/Traits/HasHmacTokens.php b/src/Authentication/Traits/HasHmacTokens.php index 01f043567..70a869379 100644 --- a/src/Authentication/Traits/HasHmacTokens.php +++ b/src/Authentication/Traits/HasHmacTokens.php @@ -162,17 +162,17 @@ public function setHmacToken(?AccessToken $accessToken): self } /** - * Checks if the provided Access Token is expired. + * Checks if the provided HMAC Token is expired. */ - public function isHmacTokenExpired(AccessToken $accessToken): bool + public function isHmacTokenExpired(AccessToken $hmacToken): bool { - return $accessToken->expires !== null && $accessToken->expires->isBefore(Time::now()); + return $hmacToken->expires !== null && $hmacToken->expires->isBefore(Time::now()); } /** * Sets an expiration for HMAC token by ID. * - * @param int $id AccessToken ID + * @param int $id HMAC Token ID * @param Time $expiresAt Expiration date * * @return bool Returns true if expiration date is set or updated. @@ -184,8 +184,8 @@ public function updateHmacTokenExpiration(int $id, Time $expiresAt): bool $result = $identityModel->setIdentityExpirationById($id, $this, $expiresAt); if ($result) { - // refresh currentAccessToken with updated data - $this->currentAccessToken = $identityModel->getHmacTokenById($id, $this); + // refresh currentHmacToken with updated data + $this->currentHmacToken = $identityModel->getHmacTokenById($id, $this); } return $result; @@ -194,7 +194,7 @@ public function updateHmacTokenExpiration(int $id, Time $expiresAt): bool /** * Removes the expiration date for HMAC token by ID. * - * @param int $id AccessToken ID + * @param int $id HMAC Token ID * * @return bool Returns true if expiration date is removed */ @@ -205,7 +205,7 @@ public function removeHmacTokenExpiration(int $id): bool $result = $identityModel->setIdentityExpirationById($id, $this); if ($result) { - // refresh currentAccessToken with updated data + // refresh currentHmacToken with updated data $this->currentHmacToken = $identityModel->getHmacTokenById($id, $this); } @@ -215,8 +215,8 @@ public function removeHmacTokenExpiration(int $id): bool /** * Checks if the current HMAC token has a set expiration date */ - public function hasHmacTokenExpiry(AccessToken $accessToken): bool + public function hasHmacTokenExpiry(AccessToken $hmacToken): bool { - return $accessToken->expires !== null; + return $hmacToken->expires !== null; } } From 32d4f575057ff8ec64f28caf40e232e1fb58f176 Mon Sep 17 00:00:00 2001 From: CosDiabos <14842802+CosDiabos@users.noreply.github.com> Date: Fri, 28 Feb 2025 11:37:32 +0100 Subject: [PATCH 15/16] fix tokens expire methods names. fix check() expires type check. updated docs & tests. --- docs/references/authentication/hmac.md | 6 +++--- docs/references/authentication/tokens.md | 6 +++--- src/Authentication/Authenticators/AccessTokens.php | 2 +- src/Authentication/Traits/HasAccessTokens.php | 4 ++-- src/Authentication/Traits/HasHmacTokens.php | 4 ++-- tests/Authentication/HasAccessTokensTest.php | 10 +++++----- tests/Authentication/HasHmacTokensTest.php | 10 +++++----- 7 files changed, 21 insertions(+), 21 deletions(-) diff --git a/docs/references/authentication/hmac.md b/docs/references/authentication/hmac.md index 03d179abf..74695d266 100644 --- a/docs/references/authentication/hmac.md +++ b/docs/references/authentication/hmac.md @@ -133,16 +133,16 @@ $token = $this->user->generateHmacToken('foo', ['foo.bar'], $expiresAt); $this->user->isHmacTokenExpired($token); // Returns true ``` -`hasHmacTokenExpiry(AccessToken $hmacToken)` - Checks if HMAC key has an expiration set. Returns `true` if the HMAC key is expired; otherwise, returns `false`. +`CanHmacTokenExpire(AccessToken $hmacToken)` - Checks if HMAC key has an expiration set. Returns `true` if the HMAC key is expired; otherwise, returns `false`. ```php $expiresAt = Time::parse('2024-11-03 12:00:00'); $token = $this->user->generateHmacToken('foo', ['foo.bar'], $expiresAt); -$this->user->hasHmacTokenExpiry($token); // Returns true +$this->user->CanHmacTokenExpire($token); // Returns true $token2 = $this->user->generateHmacToken('bar'); -$this->user->hasHmacTokenExpiry($token2); // Returns false +$this->user->CanHmacTokenExpire($token2); // Returns false ``` You can also easily set all existing HMAC keys/tokens as expired with the `spark` command: diff --git a/docs/references/authentication/tokens.md b/docs/references/authentication/tokens.md index 7289331d7..ebc385a1a 100644 --- a/docs/references/authentication/tokens.md +++ b/docs/references/authentication/tokens.md @@ -175,16 +175,16 @@ $token = $this->user->generateAccessToken('foo', ['foo.bar'], $expiresAt); $this->user->isAccessTokenExpired($token); // Returns true ``` -`hasAccessTokenExpiry(AccessToken $accessToken)` - Returns `true` if the Access Token has a set expiration date; otherwise, returns `false`. +`CanAccessTokenExpire(AccessToken $accessToken)` - Returns `true` if the Access Token has a set expiration date; otherwise, returns `false`. ```php $expiresAt = Time::parse('2024-11-03 12:00:00'); $token = $this->user->generateAccessToken('foo', ['foo.bar'], $expiresAt); -$this->user->hasAccessTokenExpiry($token2); // Returns false +$this->user->CanAccessTokenExpire($token2); // Returns false $token2 = $this->user->generateAccessToken('bar'); -$this->user->hasAccessTokenExpiry($token); // Returns true +$this->user->CanAccessTokenExpire($token); // Returns true ``` diff --git a/src/Authentication/Authenticators/AccessTokens.php b/src/Authentication/Authenticators/AccessTokens.php index bda2736fd..4b471ada4 100644 --- a/src/Authentication/Authenticators/AccessTokens.php +++ b/src/Authentication/Authenticators/AccessTokens.php @@ -156,7 +156,7 @@ public function check(array $credentials): Result // Is expired ? if ( - $token->expires !== null + $token->expires instanceof Time && $token->expires->isBefore( Time::now(), ) diff --git a/src/Authentication/Traits/HasAccessTokens.php b/src/Authentication/Traits/HasAccessTokens.php index 5630d0d0d..f3546e1ab 100644 --- a/src/Authentication/Traits/HasAccessTokens.php +++ b/src/Authentication/Traits/HasAccessTokens.php @@ -177,7 +177,7 @@ public function setAccessToken(?AccessToken $accessToken): self */ public function isAccessTokenExpired(AccessToken $accessToken): bool { - return $accessToken->expires !== null && $accessToken->expires->isBefore(Time::now()); + return $accessToken->expires instanceof Time && $accessToken->expires->isBefore(Time::now()); } /** @@ -226,7 +226,7 @@ public function removeAccessTokenExpiration(int $id): bool /** * Checks if the access token has a set expiration date */ - public function hasAccessTokenExpiry(AccessToken $accessToken): bool + public function CanAccessTokenExpire(AccessToken $accessToken): bool { return $accessToken->expires !== null; } diff --git a/src/Authentication/Traits/HasHmacTokens.php b/src/Authentication/Traits/HasHmacTokens.php index 70a869379..1b90da7de 100644 --- a/src/Authentication/Traits/HasHmacTokens.php +++ b/src/Authentication/Traits/HasHmacTokens.php @@ -166,7 +166,7 @@ public function setHmacToken(?AccessToken $accessToken): self */ public function isHmacTokenExpired(AccessToken $hmacToken): bool { - return $hmacToken->expires !== null && $hmacToken->expires->isBefore(Time::now()); + return $hmacToken->expires instanceof Time && $hmacToken->expires->isBefore(Time::now()); } /** @@ -215,7 +215,7 @@ public function removeHmacTokenExpiration(int $id): bool /** * Checks if the current HMAC token has a set expiration date */ - public function hasHmacTokenExpiry(AccessToken $hmacToken): bool + public function CanHmacTokenExpire(AccessToken $hmacToken): bool { return $hmacToken->expires !== null; } diff --git a/tests/Authentication/HasAccessTokensTest.php b/tests/Authentication/HasAccessTokensTest.php index 808c93f60..8ac69d5db 100644 --- a/tests/Authentication/HasAccessTokensTest.php +++ b/tests/Authentication/HasAccessTokensTest.php @@ -216,17 +216,17 @@ public function testTokenTimeToExpired(): void /** * See https://github.com/codeigniter4/shield/issues/926 */ - public function testHasHmacTokenExpiry(): void + public function testCanHmacTokenExpire(): void { $tokenExpiration = Time::now()->addYears(1); $token = $this->user->generateAccessToken('foo', ['foo.bar'], $tokenExpiration); - $this->assertTrue($this->user->hasAccessTokenExpiry($token)); + $this->assertTrue($this->user->CanAccessTokenExpire($token)); $token = $this->user->generateAccessToken('foo', ['foo.bar']); - $this->assertFalse($this->user->hasAccessTokenExpiry($token)); + $this->assertFalse($this->user->CanAccessTokenExpire($token)); } /** @@ -240,10 +240,10 @@ public function testAccessTokenRemoveExpiration(): void $this->user->setAccessToken($token); - $this->assertTrue($this->user->hasAccessTokenExpiry($token)); + $this->assertTrue($this->user->CanAccessTokenExpire($token)); $this->assertTrue($this->user->removeAccessTokenExpiration($token->id)); - $this->assertFalse($this->user->hasAccessTokenExpiry($this->user->currentAccessToken())); + $this->assertFalse($this->user->CanAccessTokenExpire($this->user->currentAccessToken())); } } diff --git a/tests/Authentication/HasHmacTokensTest.php b/tests/Authentication/HasHmacTokensTest.php index 8cbcf42d5..dd380d12f 100644 --- a/tests/Authentication/HasHmacTokensTest.php +++ b/tests/Authentication/HasHmacTokensTest.php @@ -225,18 +225,18 @@ public function testHmacTokenTimeToExpired(): void /** * See https://github.com/codeigniter4/shield/issues/926 */ - public function testHasHmacTokenExpiry(): void + public function testCanHmacTokenExpire(): void { $tokenExpiration = Time::now(); $tokenExpiration = $tokenExpiration->addYears(1); $token = $this->user->generateHmacToken('foo', ['foo.bar'], $tokenExpiration); - $this->assertTrue($this->user->hasHmacTokenExpiry($token)); + $this->assertTrue($this->user->CanHmacTokenExpire($token)); $token = $this->user->generateHmacToken('foo', ['foo.bar']); - $this->assertFalse($this->user->hasHmacTokenExpiry($token)); + $this->assertFalse($this->user->CanHmacTokenExpire($token)); } /** @@ -250,10 +250,10 @@ public function testHmacTokenRemoveExpiration(): void $this->user->setHmacToken($token); - $this->assertTrue($this->user->hasHmacTokenExpiry($token)); + $this->assertTrue($this->user->CanHmacTokenExpire($token)); $this->assertTrue($this->user->removeHmacTokenExpiration($token->id)); - $this->assertFalse($this->user->hasHmacTokenExpiry($this->user->currentHmacToken())); + $this->assertFalse($this->user->CanHmacTokenExpire($this->user->currentHmacToken())); } } From 97708e350be7b64664c6cb820ba4c5c881aefabc Mon Sep 17 00:00:00 2001 From: CosDiabos <14842802+CosDiabos@users.noreply.github.com> Date: Fri, 28 Feb 2025 14:25:13 +0100 Subject: [PATCH 16/16] fix name case --- docs/references/authentication/hmac.md | 6 +++--- docs/references/authentication/tokens.md | 6 +++--- src/Authentication/Traits/HasAccessTokens.php | 2 +- src/Authentication/Traits/HasHmacTokens.php | 2 +- tests/Authentication/HasAccessTokensTest.php | 8 ++++---- tests/Authentication/HasHmacTokensTest.php | 10 +++++----- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/docs/references/authentication/hmac.md b/docs/references/authentication/hmac.md index 74695d266..7a7b37d3e 100644 --- a/docs/references/authentication/hmac.md +++ b/docs/references/authentication/hmac.md @@ -133,16 +133,16 @@ $token = $this->user->generateHmacToken('foo', ['foo.bar'], $expiresAt); $this->user->isHmacTokenExpired($token); // Returns true ``` -`CanHmacTokenExpire(AccessToken $hmacToken)` - Checks if HMAC key has an expiration set. Returns `true` if the HMAC key is expired; otherwise, returns `false`. +`canHmacTokenExpire(AccessToken $hmacToken)` - Checks if HMAC key has an expiration set. Returns `true` if the HMAC key is expired; otherwise, returns `false`. ```php $expiresAt = Time::parse('2024-11-03 12:00:00'); $token = $this->user->generateHmacToken('foo', ['foo.bar'], $expiresAt); -$this->user->CanHmacTokenExpire($token); // Returns true +$this->user->canHmacTokenExpire($token); // Returns true $token2 = $this->user->generateHmacToken('bar'); -$this->user->CanHmacTokenExpire($token2); // Returns false +$this->user->canHmacTokenExpire($token2); // Returns false ``` You can also easily set all existing HMAC keys/tokens as expired with the `spark` command: diff --git a/docs/references/authentication/tokens.md b/docs/references/authentication/tokens.md index ebc385a1a..4f1b9e1c9 100644 --- a/docs/references/authentication/tokens.md +++ b/docs/references/authentication/tokens.md @@ -175,16 +175,16 @@ $token = $this->user->generateAccessToken('foo', ['foo.bar'], $expiresAt); $this->user->isAccessTokenExpired($token); // Returns true ``` -`CanAccessTokenExpire(AccessToken $accessToken)` - Returns `true` if the Access Token has a set expiration date; otherwise, returns `false`. +`canAccessTokenExpire(AccessToken $accessToken)` - Returns `true` if the Access Token has a set expiration date; otherwise, returns `false`. ```php $expiresAt = Time::parse('2024-11-03 12:00:00'); $token = $this->user->generateAccessToken('foo', ['foo.bar'], $expiresAt); -$this->user->CanAccessTokenExpire($token2); // Returns false +$this->user->canAccessTokenExpire($token2); // Returns false $token2 = $this->user->generateAccessToken('bar'); -$this->user->CanAccessTokenExpire($token); // Returns true +$this->user->canAccessTokenExpire($token); // Returns true ``` diff --git a/src/Authentication/Traits/HasAccessTokens.php b/src/Authentication/Traits/HasAccessTokens.php index f3546e1ab..b4fa52cbb 100644 --- a/src/Authentication/Traits/HasAccessTokens.php +++ b/src/Authentication/Traits/HasAccessTokens.php @@ -226,7 +226,7 @@ public function removeAccessTokenExpiration(int $id): bool /** * Checks if the access token has a set expiration date */ - public function CanAccessTokenExpire(AccessToken $accessToken): bool + public function canAccessTokenExpire(AccessToken $accessToken): bool { return $accessToken->expires !== null; } diff --git a/src/Authentication/Traits/HasHmacTokens.php b/src/Authentication/Traits/HasHmacTokens.php index 1b90da7de..137cea9a3 100644 --- a/src/Authentication/Traits/HasHmacTokens.php +++ b/src/Authentication/Traits/HasHmacTokens.php @@ -215,7 +215,7 @@ public function removeHmacTokenExpiration(int $id): bool /** * Checks if the current HMAC token has a set expiration date */ - public function CanHmacTokenExpire(AccessToken $hmacToken): bool + public function canHmacTokenExpire(AccessToken $hmacToken): bool { return $hmacToken->expires !== null; } diff --git a/tests/Authentication/HasAccessTokensTest.php b/tests/Authentication/HasAccessTokensTest.php index 8ac69d5db..d864860c0 100644 --- a/tests/Authentication/HasAccessTokensTest.php +++ b/tests/Authentication/HasAccessTokensTest.php @@ -222,11 +222,11 @@ public function testCanHmacTokenExpire(): void $token = $this->user->generateAccessToken('foo', ['foo.bar'], $tokenExpiration); - $this->assertTrue($this->user->CanAccessTokenExpire($token)); + $this->assertTrue($this->user->canAccessTokenExpire($token)); $token = $this->user->generateAccessToken('foo', ['foo.bar']); - $this->assertFalse($this->user->CanAccessTokenExpire($token)); + $this->assertFalse($this->user->canAccessTokenExpire($token)); } /** @@ -240,10 +240,10 @@ public function testAccessTokenRemoveExpiration(): void $this->user->setAccessToken($token); - $this->assertTrue($this->user->CanAccessTokenExpire($token)); + $this->assertTrue($this->user->canAccessTokenExpire($token)); $this->assertTrue($this->user->removeAccessTokenExpiration($token->id)); - $this->assertFalse($this->user->CanAccessTokenExpire($this->user->currentAccessToken())); + $this->assertFalse($this->user->canAccessTokenExpire($this->user->currentAccessToken())); } } diff --git a/tests/Authentication/HasHmacTokensTest.php b/tests/Authentication/HasHmacTokensTest.php index dd380d12f..33909c1de 100644 --- a/tests/Authentication/HasHmacTokensTest.php +++ b/tests/Authentication/HasHmacTokensTest.php @@ -225,18 +225,18 @@ public function testHmacTokenTimeToExpired(): void /** * See https://github.com/codeigniter4/shield/issues/926 */ - public function testCanHmacTokenExpire(): void + public function testcanHmacTokenExpire(): void { $tokenExpiration = Time::now(); $tokenExpiration = $tokenExpiration->addYears(1); $token = $this->user->generateHmacToken('foo', ['foo.bar'], $tokenExpiration); - $this->assertTrue($this->user->CanHmacTokenExpire($token)); + $this->assertTrue($this->user->canHmacTokenExpire($token)); $token = $this->user->generateHmacToken('foo', ['foo.bar']); - $this->assertFalse($this->user->CanHmacTokenExpire($token)); + $this->assertFalse($this->user->canHmacTokenExpire($token)); } /** @@ -250,10 +250,10 @@ public function testHmacTokenRemoveExpiration(): void $this->user->setHmacToken($token); - $this->assertTrue($this->user->CanHmacTokenExpire($token)); + $this->assertTrue($this->user->canHmacTokenExpire($token)); $this->assertTrue($this->user->removeHmacTokenExpiration($token->id)); - $this->assertFalse($this->user->CanHmacTokenExpire($this->user->currentHmacToken())); + $this->assertFalse($this->user->canHmacTokenExpire($this->user->currentHmacToken())); } }