Make authentication more reliable for list syncing
This commit is contained in:
parent
7373cf93b7
commit
ee18d407a2
@ -21,7 +21,6 @@ use const Aviat\AnimeClient\USER_AGENT;
|
||||
use function Amp\Promise\wait;
|
||||
use function Aviat\AnimeClient\getResponse;
|
||||
|
||||
use Amp;
|
||||
use Amp\Http\Client\Request;
|
||||
use Amp\Http\Client\Body\FormBody;
|
||||
use Aviat\Ion\Json;
|
||||
|
@ -54,7 +54,7 @@ class AnimeListTransformer extends AbstractTransformer {
|
||||
'reconsuming' => $reconsuming,
|
||||
'status' => $reconsuming
|
||||
? KitsuStatus::WATCHING
|
||||
:AnimeWatchingStatus::ANILIST_TO_KITSU[$item['status']],
|
||||
: AnimeWatchingStatus::ANILIST_TO_KITSU[$item['status']],
|
||||
'updatedAt' => (new DateTime())
|
||||
->setTimestamp($item['updatedAt'])
|
||||
->format(DateTime::W3C)
|
||||
|
@ -17,6 +17,7 @@
|
||||
namespace Aviat\AnimeClient\API\Anilist\Transformer;
|
||||
|
||||
use Aviat\AnimeClient\API\Enum\MangaReadingStatus\Anilist as AnilistStatus;
|
||||
use Aviat\AnimeClient\API\Enum\MangaReadingStatus\Kitsu as KitsuStatus;
|
||||
use Aviat\AnimeClient\API\Mapping\MangaReadingStatus;
|
||||
use Aviat\AnimeClient\Types\MangaListItem;
|
||||
use Aviat\AnimeClient\Types\FormItem;
|
||||
@ -40,6 +41,8 @@ class MangaListTransformer extends AbstractTransformer {
|
||||
*/
|
||||
public function untransform(array $item): FormItem
|
||||
{
|
||||
$reconsuming = $item['status'] === AnilistStatus::REPEATING;
|
||||
|
||||
return FormItem::from([
|
||||
'id' => $item['id'],
|
||||
'mal_id' => $item['media']['idMal'],
|
||||
@ -49,8 +52,10 @@ class MangaListTransformer extends AbstractTransformer {
|
||||
'progress' => $item['progress'],
|
||||
'rating' => $item['score'],
|
||||
'reconsumeCount' => $item['repeat'],
|
||||
'reconsuming' => $item['status'] === AnilistStatus::REPEATING,
|
||||
'status' => MangaReadingStatus::ANILIST_TO_KITSU[$item['status']],
|
||||
'reconsuming' => $reconsuming,
|
||||
'status' => $reconsuming
|
||||
? KitsuStatus::READING
|
||||
: MangaReadingStatus::ANILIST_TO_KITSU[$item['status']],
|
||||
'updatedAt' => (new DateTime())
|
||||
->setTimestamp($item['updatedAt'])
|
||||
->format(DateTime::W3C),
|
||||
|
@ -29,6 +29,7 @@ use Aviat\Ion\Di\{ContainerAware, ContainerInterface};
|
||||
use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException};
|
||||
|
||||
use Throwable;
|
||||
use const PHP_SAPI;
|
||||
|
||||
/**
|
||||
* Kitsu API Authentication
|
||||
@ -83,35 +84,67 @@ final class Auth {
|
||||
|
||||
$auth = $this->model->authenticate($username, $password);
|
||||
|
||||
if (FALSE !== $auth)
|
||||
{
|
||||
// Set the token in the cache for command line operations
|
||||
$cacheItem = $this->cache->getItem(K::AUTH_TOKEN_CACHE_KEY);
|
||||
$cacheItem->set($auth['access_token']);
|
||||
$cacheItem->save();
|
||||
|
||||
// Set the token expiration in the cache
|
||||
$expireTime = $auth['created_at'] + $auth['expires_in'];
|
||||
$cacheItem = $this->cache->getItem(K::AUTH_TOKEN_EXP_CACHE_KEY);
|
||||
$cacheItem->set($expireTime);
|
||||
$cacheItem->save();
|
||||
|
||||
// Set the refresh token in the cache
|
||||
$cacheItem = $this->cache->getItem(K::AUTH_TOKEN_REFRESH_CACHE_KEY);
|
||||
$cacheItem->set($auth['refresh_token']);
|
||||
$cacheItem->save();
|
||||
|
||||
// Set the session values
|
||||
$this->segment->set('auth_token', $auth['access_token']);
|
||||
$this->segment->set('auth_token_expires', $expireTime);
|
||||
$this->segment->set('refresh_token', $auth['refresh_token']);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
return $this->storeAuth($auth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the current user is authenticated
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isAuthenticated(): bool
|
||||
{
|
||||
return ($this->getAuthToken() !== NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear authentication values
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function logout(): void
|
||||
{
|
||||
$this->segment->clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the authentication token from the session
|
||||
*
|
||||
* @return string|false
|
||||
*/
|
||||
private function getAuthToken(): ?string
|
||||
{
|
||||
$now = time();
|
||||
|
||||
if (PHP_SAPI === 'cli')
|
||||
{
|
||||
$token = $this->cacheGet(K::AUTH_TOKEN_CACHE_KEY, NULL);
|
||||
$refreshToken = $this->cacheGet(K::AUTH_TOKEN_REFRESH_CACHE_KEY, NULL);
|
||||
$expireTime = $this->cacheGet(K::AUTH_TOKEN_EXP_CACHE_KEY);
|
||||
$isExpired = $now > $expireTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
$token = $this->segment->get('auth_token', NULL);
|
||||
$refreshToken = $this->segment->get('refresh_token', NULL);
|
||||
$isExpired = $now > $this->segment->get('auth_token_expires', $now + 5000);
|
||||
}
|
||||
|
||||
// Attempt to re-authenticate with refresh token
|
||||
/* if ($isExpired === TRUE && $refreshToken !== NULL)
|
||||
{
|
||||
if ($this->reAuthenticate($refreshToken) !== NULL)
|
||||
{
|
||||
return (PHP_SAPI === 'cli')
|
||||
? $this->cacheGet(K::AUTH_TOKEN_CACHE_KEY, NULL)
|
||||
: $this->segment->get('auth_token', NULL);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}*/
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the call to re-authenticate with the existing refresh token
|
||||
@ -121,10 +154,15 @@ final class Auth {
|
||||
* @throws InvalidArgumentException
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function reAuthenticate(string $token): bool
|
||||
private function reAuthenticate(string $token): bool
|
||||
{
|
||||
$auth = $this->model->reAuthenticate($token);
|
||||
|
||||
return $this->storeAuth($auth);
|
||||
}
|
||||
|
||||
private function storeAuth($auth): bool
|
||||
{
|
||||
if (FALSE !== $auth)
|
||||
{
|
||||
// Set the token in the cache for command line operations
|
||||
@ -153,52 +191,15 @@ final class Auth {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check whether the current user is authenticated
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isAuthenticated(): bool
|
||||
private function cacheGet(string $key, $default = NULL)
|
||||
{
|
||||
return ($this->get_auth_token() !== FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear authentication values
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function logout(): void
|
||||
{
|
||||
$this->segment->clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the authentication token from the session
|
||||
*
|
||||
* @return string|false
|
||||
*/
|
||||
public function get_auth_token()
|
||||
{
|
||||
$now = time();
|
||||
|
||||
$token = $this->segment->get('auth_token', FALSE);
|
||||
$refreshToken = $this->segment->get('refresh_token', FALSE);
|
||||
$isExpired = time() > $this->segment->get('auth_token_expires', $now + 5000);
|
||||
|
||||
// Attempt to re-authenticate with refresh token
|
||||
/* if ($isExpired && $refreshToken)
|
||||
$cacheItem = $this->cache->getItem($key);
|
||||
if ( ! $cacheItem->isHit())
|
||||
{
|
||||
if ($this->reAuthenticate($refreshToken))
|
||||
{
|
||||
return $this->segment->get('auth_token', FALSE);
|
||||
}
|
||||
return $default;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
} */
|
||||
|
||||
return $token;
|
||||
return $cacheItem->get();
|
||||
}
|
||||
}
|
||||
// End of KitsuAuth.php
|
@ -16,6 +16,7 @@
|
||||
|
||||
namespace Aviat\AnimeClient\API\Kitsu;
|
||||
|
||||
use const PHP_SAPI;
|
||||
use const Aviat\AnimeClient\SESSION_SEGMENT;
|
||||
|
||||
use function Amp\Promise\wait;
|
||||
@ -69,11 +70,14 @@ trait KitsuTrait {
|
||||
->getSegment(SESSION_SEGMENT);
|
||||
|
||||
$cache = $this->getContainer()->get('cache');
|
||||
$cacheItem = $cache->getItem('kitsu-auth-token');
|
||||
$cacheItem = $cache->getItem(K::AUTH_TOKEN_CACHE_KEY);
|
||||
$token = null;
|
||||
|
||||
|
||||
if ($sessionSegment->get('auth_token') !== NULL && $url !== K::AUTH_URL)
|
||||
if (PHP_SAPI === 'cli' && $cacheItem->isHit())
|
||||
{
|
||||
$token = $cacheItem->get();
|
||||
}
|
||||
else if ($url !== K::AUTH_URL && $sessionSegment->get('auth_token') !== NULL)
|
||||
{
|
||||
$token = $sessionSegment->get('auth_token');
|
||||
if ( ! $cacheItem->isHit())
|
||||
@ -82,12 +86,8 @@ trait KitsuTrait {
|
||||
$cacheItem->save();
|
||||
}
|
||||
}
|
||||
else if ($sessionSegment->get('auth_token') === NULL && $cacheItem->isHit())
|
||||
{
|
||||
$token = $cacheItem->get();
|
||||
}
|
||||
|
||||
if (NULL !== $token)
|
||||
if ($token !== NULL)
|
||||
{
|
||||
$request = $request->setAuth('bearer', $token);
|
||||
}
|
||||
|
@ -26,8 +26,8 @@ use Aviat\AnimeClient\API\{Anilist, CacheTrait, Kitsu};
|
||||
use Aviat\AnimeClient\API\Kitsu\KitsuRequestBuilder;
|
||||
use Aviat\Banker\Pool;
|
||||
use Aviat\Ion\Config;
|
||||
use Aviat\Ion\Di\{Container, ContainerAware};
|
||||
use ConsoleKit\{Command, ConsoleException};
|
||||
use Aviat\Ion\Di\{Container, ContainerInterface, ContainerAware};
|
||||
use ConsoleKit\{Colors, Command, ConsoleException};
|
||||
use ConsoleKit\Widgets\Box;
|
||||
use Laminas\Diactoros\{Response, ServerRequestFactory};
|
||||
use Monolog\Handler\RotatingFileHandler;
|
||||
@ -44,15 +44,30 @@ abstract class BaseCommand extends Command {
|
||||
* Echo text in a box
|
||||
*
|
||||
* @param string $message
|
||||
* @param string|int|null $fgColor
|
||||
* @param string|int|null $bgColor
|
||||
* @return void
|
||||
*/
|
||||
protected function echoBox($message): void
|
||||
public function echoBox(string $message, $fgColor = NULL, $bgColor = NULL): void
|
||||
{
|
||||
try
|
||||
{
|
||||
echo "\n";
|
||||
$len = strlen($message);
|
||||
|
||||
// color message
|
||||
$message = Colors::colorize($message, $fgColor, $bgColor);
|
||||
$colorLen = strlen($message);
|
||||
|
||||
// create the box
|
||||
$box = new Box($this->getConsole(), $message);
|
||||
|
||||
if ($len !== $colorLen)
|
||||
{
|
||||
$box->setPadding((($colorLen - $len) / 2) + 2);
|
||||
}
|
||||
|
||||
$box->write();
|
||||
|
||||
echo "\n";
|
||||
}
|
||||
catch (ConsoleException $e)
|
||||
@ -61,12 +76,42 @@ abstract class BaseCommand extends Command {
|
||||
}
|
||||
}
|
||||
|
||||
public function echo(string $message): void
|
||||
{
|
||||
$this->_line($message);
|
||||
}
|
||||
|
||||
public function echoSuccess(string $message): void
|
||||
{
|
||||
$this->_line($message, Colors::GREEN | Colors::BOLD, Colors::BLACK);
|
||||
}
|
||||
|
||||
public function echoWarning(string $message): void
|
||||
{
|
||||
$this->_line($message, Colors::YELLOW | Colors::BOLD, Colors::BLACK);
|
||||
}
|
||||
|
||||
public function echoWarningBox(string $message): void
|
||||
{
|
||||
$this->echoBox($message, Colors::YELLOW | Colors::BOLD, Colors::BLACK);
|
||||
}
|
||||
|
||||
public function echoError(string $message): void
|
||||
{
|
||||
$this->_line($message, Colors::RED | Colors::BOLD, Colors::BLACK);
|
||||
}
|
||||
|
||||
public function echoErrorBox(string $message): void
|
||||
{
|
||||
$this->echoBox($message, Colors::RED | Colors::BOLD, Colors::BLACK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the Di container
|
||||
*
|
||||
* @return Container
|
||||
* @return Containerinterface
|
||||
*/
|
||||
protected function setupContainer(): Container
|
||||
public function setupContainer(): ContainerInterface
|
||||
{
|
||||
$APP_DIR = realpath(__DIR__ . '/../../../app');
|
||||
$APPCONF_DIR = realpath("{$APP_DIR}/appConf/");
|
||||
@ -82,7 +127,7 @@ abstract class BaseCommand extends Command {
|
||||
|
||||
$configArray = array_replace_recursive($baseConfig, $config, $overrideConfig);
|
||||
|
||||
$di = static function ($configArray) use ($APP_DIR): Container {
|
||||
$di = static function (array $configArray) use ($APP_DIR): Container {
|
||||
$container = new Container();
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
@ -103,9 +148,7 @@ abstract class BaseCommand extends Command {
|
||||
$container->setLogger($kitsu_request_logger, 'kitsu-request');
|
||||
|
||||
// Create Config Object
|
||||
$container->set('config', static function() use ($configArray): Config {
|
||||
return new Config($configArray);
|
||||
});
|
||||
$container->set('config', fn () => new Config($configArray));
|
||||
|
||||
// Create Cache Object
|
||||
$container->set('cache', static function($container) {
|
||||
@ -115,28 +158,20 @@ abstract class BaseCommand extends Command {
|
||||
});
|
||||
|
||||
// Create Aura Router Object
|
||||
$container->set('aura-router', static function() {
|
||||
return new RouterContainer;
|
||||
});
|
||||
$container->set('aura-router', fn () => new RouterContainer);
|
||||
|
||||
// Create Request/Response Objects
|
||||
$container->set('request', static function() {
|
||||
return ServerRequestFactory::fromGlobals(
|
||||
$_SERVER,
|
||||
$_GET,
|
||||
$_POST,
|
||||
$_COOKIE,
|
||||
$_FILES
|
||||
);
|
||||
});
|
||||
$container->set('response', static function(): Response {
|
||||
return new Response;
|
||||
});
|
||||
$container->set('request', fn () => ServerRequestFactory::fromGlobals(
|
||||
$_SERVER,
|
||||
$_GET,
|
||||
$_POST,
|
||||
$_COOKIE,
|
||||
$_FILES
|
||||
));
|
||||
$container->set('response', fn () => new Response);
|
||||
|
||||
// Create session Object
|
||||
$container->set('session', static function() {
|
||||
return (new SessionFactory())->newInstance($_COOKIE);
|
||||
});
|
||||
$container->set('session', fn () => (new SessionFactory())->newInstance($_COOKIE));
|
||||
|
||||
// Models
|
||||
$container->set('kitsu-model', static function($container): Kitsu\Model {
|
||||
@ -175,21 +210,21 @@ abstract class BaseCommand extends Command {
|
||||
return $model;
|
||||
});
|
||||
|
||||
$container->set('auth', static function($container): Kitsu\Auth {
|
||||
return new Kitsu\Auth($container);
|
||||
});
|
||||
$container->set('auth', fn ($container) => new Kitsu\Auth($container));
|
||||
|
||||
$container->set('url-generator', static function($container): UrlGenerator {
|
||||
return new UrlGenerator($container);
|
||||
});
|
||||
$container->set('url-generator', fn ($container) => new UrlGenerator($container));
|
||||
|
||||
$container->set('util', static function($container): Util {
|
||||
return new Util($container);
|
||||
});
|
||||
$container->set('util', fn ($container) => new Util($container));
|
||||
|
||||
return $container;
|
||||
};
|
||||
|
||||
return $di($configArray);
|
||||
}
|
||||
|
||||
private function _line(string $message, $fgColor = NULL, $bgColor = NULL): void
|
||||
{
|
||||
$message = Colors::colorize($message, $fgColor, $bgColor);
|
||||
$this->getConsole()->writeln($message);
|
||||
}
|
||||
}
|
28
src/AnimeClient/Enum/ListType.php
Normal file
28
src/AnimeClient/Enum/ListType.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* Hummingbird Anime List Client
|
||||
*
|
||||
* An API client for Kitsu to manage anime and manga watch lists
|
||||
*
|
||||
* PHP version 7.4
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2020 Timothy J. Warren
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
* @version 5
|
||||
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||
*/
|
||||
|
||||
namespace Aviat\AnimeClient\Enum;
|
||||
|
||||
use Aviat\Ion\Enum as BaseEnum;
|
||||
|
||||
/**
|
||||
* Types of lists
|
||||
*/
|
||||
final class ListType extends BaseEnum {
|
||||
public const ANIME = 'anime';
|
||||
public const DRAMA = 'drama';
|
||||
public const MANGA = 'manga';
|
||||
}
|
28
src/AnimeClient/Enum/SyncAction.php
Normal file
28
src/AnimeClient/Enum/SyncAction.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* Hummingbird Anime List Client
|
||||
*
|
||||
* An API client for Kitsu to manage anime and manga watch lists
|
||||
*
|
||||
* PHP version 7.4
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2020 Timothy J. Warren
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
* @version 5
|
||||
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||
*/
|
||||
|
||||
namespace Aviat\AnimeClient\Enum;
|
||||
|
||||
use Aviat\Ion\Enum as BaseEnum;
|
||||
|
||||
/**
|
||||
* Types of actions when syncing lists from different APIs
|
||||
*/
|
||||
final class SyncAction extends BaseEnum {
|
||||
public const CREATE = 'create';
|
||||
public const UPDATE = 'update';
|
||||
public const DELETE = 'delete';
|
||||
}
|
Loading…
Reference in New Issue
Block a user