mirror of
				https://github.com/elyby/accounts.git
				synced 2025-05-31 14:11:46 +05:30 
			
		
		
		
	Add support for the legacy refresh tokens, make the new refresh tokens non-expire [skip ci]
This commit is contained in:
		| @@ -45,11 +45,11 @@ class Component extends BaseComponent { | ||||
|             $authServer->enableGrantType($authCodeGrant, $accessTokenTTL); | ||||
|             $authCodeGrant->setScopeRepository($publicScopesRepo); // Change repository after enabling | ||||
|  | ||||
|             // TODO: extends refresh token life time to forever | ||||
|             $refreshTokenGrant = new RefreshTokenGrant($refreshTokensRepo); | ||||
|             $authServer->enableGrantType($refreshTokenGrant, $accessTokenTTL); | ||||
|             $authServer->enableGrantType($refreshTokenGrant); | ||||
|             $refreshTokenGrant->setScopeRepository($publicScopesRepo); // Change repository after enabling | ||||
|  | ||||
|             // TODO: make these access tokens live longer | ||||
|             $clientCredentialsGrant = new Grant\ClientCredentialsGrant(); | ||||
|             $authServer->enableGrantType($clientCredentialsGrant, $accessTokenTTL); | ||||
|             $clientCredentialsGrant->setScopeRepository($internalScopesRepo); // Change repository after enabling | ||||
|   | ||||
| @@ -3,6 +3,8 @@ declare(strict_types=1); | ||||
|  | ||||
| namespace api\components\OAuth2\Entities; | ||||
|  | ||||
| use Carbon\CarbonImmutable; | ||||
| use DateTimeImmutable; | ||||
| use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; | ||||
| use League\OAuth2\Server\Entities\Traits\EntityTrait; | ||||
| use League\OAuth2\Server\Entities\Traits\RefreshTokenTrait; | ||||
| @@ -11,4 +13,17 @@ class RefreshTokenEntity implements RefreshTokenEntityInterface { | ||||
|     use EntityTrait; | ||||
|     use RefreshTokenTrait; | ||||
|  | ||||
|     /** | ||||
|      * We don't rotate refresh tokens, so that to always pass validation in the internal validator | ||||
|      * of the oauth2 server implementation we set the lifetime as far as possible. | ||||
|      * | ||||
|      * In 2038 this may cause problems, but I am sure that by then this code, if it still works, | ||||
|      * will be rewritten several times and the problem will be solved in a completely different way. | ||||
|      * | ||||
|      * @return DateTimeImmutable | ||||
|      */ | ||||
|     public function getExpiryDateTime(): DateTimeImmutable { | ||||
|         return CarbonImmutable::create(2038, 11, 11, 22, 13, 0, 'Europe/Minsk'); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -3,12 +3,36 @@ declare(strict_types=1); | ||||
|  | ||||
| namespace api\components\OAuth2\Grants; | ||||
|  | ||||
| use common\models\OauthSession; | ||||
| use League\OAuth2\Server\Entities\AccessTokenEntityInterface; | ||||
| use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; | ||||
| use League\OAuth2\Server\Exception\OAuthServerException; | ||||
| use League\OAuth2\Server\Grant\RefreshTokenGrant as BaseRefreshTokenGrant; | ||||
| use Psr\Http\Message\ServerRequestInterface; | ||||
| use Yii; | ||||
|  | ||||
| class RefreshTokenGrant extends BaseRefreshTokenGrant { | ||||
|  | ||||
|     /** | ||||
|      * Previously, refresh tokens was stored in Redis. | ||||
|      * If received refresh token is matches the legacy token template, | ||||
|      * restore the information from the legacy storage. | ||||
|      * | ||||
|      * @param ServerRequestInterface $request | ||||
|      * @param string $clientId | ||||
|      * | ||||
|      * @return array | ||||
|      * @throws OAuthServerException | ||||
|      */ | ||||
|     protected function validateOldRefreshToken(ServerRequestInterface $request, $clientId): array { | ||||
|         $refreshToken = $this->getRequestParameter('refresh_token', $request); | ||||
|         if ($refreshToken !== null && mb_strlen($refreshToken) === 40) { | ||||
|             return $this->validateLegacyRefreshToken($refreshToken); | ||||
|         } | ||||
|  | ||||
|         return parent::validateOldRefreshToken($request, $clientId); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Currently we're not rotating refresh tokens. | ||||
|      * So we overriding this method to always return null, which means, | ||||
| @@ -22,4 +46,35 @@ class RefreshTokenGrant extends BaseRefreshTokenGrant { | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     private function validateLegacyRefreshToken(string $refreshToken): array { | ||||
|         $result = Yii::$app->redis->get("oauth:refresh:tokens:{$refreshToken}"); | ||||
|         if ($result === null) { | ||||
|             throw OAuthServerException::invalidRefreshToken('Token has been revoked'); | ||||
|         } | ||||
|  | ||||
|         try { | ||||
|             [ | ||||
|                 'access_token_id' => $accessTokenId, | ||||
|                 'session_id' => $sessionId, | ||||
|             ] = json_decode($result, true, 512, JSON_THROW_ON_ERROR); | ||||
|         } catch (\Exception $e) { | ||||
|             throw OAuthServerException::invalidRefreshToken('Cannot decrypt the refresh token', $e); | ||||
|         } | ||||
|  | ||||
|         /** @var OauthSession|null $relatedSession */ | ||||
|         $relatedSession = OauthSession::findOne(['legacy_id' => $sessionId]); | ||||
|         if ($relatedSession === null) { | ||||
|             throw OAuthServerException::invalidRefreshToken('Token has been revoked'); | ||||
|         } | ||||
|  | ||||
|         return [ | ||||
|             'client_id' => $relatedSession->client_id, | ||||
|             'refresh_token_id' => $refreshToken, | ||||
|             'access_token_id' => $accessTokenId, | ||||
|             'scopes' => $relatedSession->getScopes(), | ||||
|             'user_id' => $relatedSession->account_id, | ||||
|             'expire_time' => null, | ||||
|         ]; | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,63 +0,0 @@ | ||||
| <?php | ||||
| namespace api\components\OAuth2\Repositories; | ||||
|  | ||||
| use api\components\OAuth2\Entities\RefreshTokenEntity; | ||||
| use common\components\Redis\Key; | ||||
| use common\components\Redis\Set; | ||||
| use common\models\OauthSession; | ||||
| use ErrorException; | ||||
| use League\OAuth2\Server\Entity\RefreshTokenEntity as OriginalRefreshTokenEntity; | ||||
| use League\OAuth2\Server\Storage\AbstractStorage; | ||||
| use League\OAuth2\Server\Storage\RefreshTokenInterface; | ||||
| use Yii; | ||||
| use yii\helpers\Json; | ||||
|  | ||||
| class RefreshTokenStorage extends AbstractStorage implements RefreshTokenInterface { | ||||
|  | ||||
|     public $dataTable = 'oauth_refresh_tokens'; | ||||
|  | ||||
|     public function get($token) { | ||||
|         $result = Json::decode((new Key($this->dataTable, $token))->getValue()); | ||||
|         if ($result === null) { | ||||
|             return null; | ||||
|         } | ||||
|  | ||||
|         $entity = new RefreshTokenEntity($this->server); | ||||
|         $entity->setId($result['id']); | ||||
|         $entity->setAccessTokenId($result['access_token_id']); | ||||
|         $entity->setSessionId($result['session_id']); | ||||
|  | ||||
|         return $entity; | ||||
|     } | ||||
|  | ||||
|     public function create($token, $expireTime, $accessToken) { | ||||
|         $sessionId = $this->server->getAccessTokenStorage()->get($accessToken)->getSession()->getId(); | ||||
|         $payload = Json::encode([ | ||||
|             'id' => $token, | ||||
|             'access_token_id' => $accessToken, | ||||
|             'session_id' => $sessionId, | ||||
|         ]); | ||||
|  | ||||
|         $this->key($token)->setValue($payload); | ||||
|         $this->sessionHash($sessionId)->add($token); | ||||
|     } | ||||
|  | ||||
|     public function delete(OriginalRefreshTokenEntity $token) { | ||||
|         if (!$token instanceof RefreshTokenEntity) { | ||||
|             throw new ErrorException('Token must be instance of ' . RefreshTokenEntity::class); | ||||
|         } | ||||
|  | ||||
|         $this->key($token->getId())->delete(); | ||||
|         $this->sessionHash($token->getSessionId())->remove($token->getId()); | ||||
|     } | ||||
|  | ||||
|     public function sessionHash(string $sessionId): Set { | ||||
|         $tableName = Yii::$app->db->getSchema()->getRawTableName(OauthSession::tableName()); | ||||
|         return new Set($tableName, $sessionId, 'refresh_tokens'); | ||||
|     } | ||||
|  | ||||
|     private function key(string $token): Key { | ||||
|         return new Key($this->dataTable, $token); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -57,7 +57,7 @@ class AuthorizationController extends Controller { | ||||
|     } | ||||
|  | ||||
|     private function createOauthProcess(): OauthProcess { | ||||
|         return new OauthProcess(Yii::$app->oauth->authServer); | ||||
|         return new OauthProcess(Yii::$app->oauth->getAuthServer()); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user