From 0f814c3f2ad9f37bcf01dd6a2f7ae3247b448203 Mon Sep 17 00:00:00 2001 From: JVT038 <47184046+JVT038@users.noreply.github.com> Date: Wed, 28 Feb 2024 21:32:58 +0100 Subject: [PATCH 1/8] Added endpoint to retrieve movie data. Furthermore, I have added HTTP tests and the openapi docs have been updated. Signed-off-by: JVT038 <47184046+JVT038@users.noreply.github.com> --- docs/openapi.json | 222 ++++++++++++++++++ settings/routes.php | 3 +- src/HttpController/Api/MovieController.php | 91 +++++++ .../Api/MovieSearchController.php | 47 ---- tests/rest/api/movie-search.http | 7 - tests/rest/api/movie.http | 45 ++++ 6 files changed, 360 insertions(+), 55 deletions(-) create mode 100644 src/HttpController/Api/MovieController.php delete mode 100644 src/HttpController/Api/MovieSearchController.php delete mode 100644 tests/rest/api/movie-search.http create mode 100644 tests/rest/api/movie.http diff --git a/docs/openapi.json b/docs/openapi.json index 0ba8eb00c..416d77507 100644 --- a/docs/openapi.json +++ b/docs/openapi.json @@ -985,6 +985,222 @@ ] } }, + "/movies/{id}": { + "get": { + "tags": [ + "Movies" + ], + "summary": "Get movie data", + "description": "Get the data of a specific movie based on their Movary ID", + "parameters": [ + { + "$ref": "#/components/schemas/id" + } + ], + "responses": { + "200": { + "description": "Movie ID is valid and the movie was found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "movie": { + "description": "Movie object", + "type": "object", + "properties": { + "id": { + "$ref": "#/components/schemas/id" + }, + "tmdbId": { + "$ref": "#/components/schemas/idNullable" + }, + "imdbId": { + "type": "string", + "nullable": true, + "example": "tt12345678" + }, + "title": { + "$ref": "#/components/schemas/title" + }, + "releaseDate": { + "$ref":"#/components/schemas/releaseDateNullable" + }, + "posterPath": { + "$ref": "#/components/schemas/posterPath" + }, + "tagline": { + "$ref": "#/components/schemas/movie/properties/tagline" + }, + "overview": { + "$ref": "#/components/schemas/overview" + }, + "runtime": { + "$ref": "#/components/schemas/movie/properties/runtime" + }, + "imdbUrl": { + "type": "string", + "description": "URL of the movie in IMDb", + "nullable": true + }, + "imdbRatingAverage": { + "type": "string", + "description": "Average rating of movie on IMDb", + "nullable": true + }, + "imdbRatingVoteCount": { + "type": "string", + "description": "Amount of ratings of movie in IMDb", + "nullable": true + }, + "tmdbUrl": { + "type": "string", + "description": "The URL of the movie on TMDB", + "nullable": true + }, + "tmdbRatingAverage": { + "type": "number", + "description": "Average rating of movie on TMDB", + "nullable": true + }, + "tmdbRatingVotecount": { + "type": "string", + "description": "Amount of ratings of movie in TMDB", + "nullable": true + }, + "originalLanguage": { + "type": "string", + "description": "Original language of movie", + "nullable": true, + "example": "English" + } + } + }, + "movieGenres": { + "type": "array", + "properties": { + "genre": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Name of the genre" + } + } + } + }, + "example": [ + { + "name": "Genre 1" + }, + { + "name": "Genre 2" + } + ] + }, + "castMembers": { + "type": "array", + "properties": { + "castMember": { + "type": "object", + "id": { + "$ref": "#/components/schemas/id" + }, + "name": { + "type": "string", + "description": "Name of the cast member" + }, + "posterPath": { + "$ref": "#/components/schemas/posterPath" + }, + "characterName": { + "type": "string", + "description": "Name of the character this cast member played in the movie" + } + } + }, + "example": [ + { + "id": 1, + "name": "Name 1", + "posterPath": "/path/to/poster1.jpg", + "characterName": "Character name 1" + }, + { + "id": 2, + "name": "Name 2", + "posterPath": "/path/to/poster2.jpg", + "characterName": "Character name 2" + } + ] + }, + "directors": { + "type": "array", + "properties": { + "director": { + "type": "object", + "description": "The director", + "properties": { + "id": { + "$ref": "#/components/schemas/id" + }, + "name": { + "type": "string", + "description": "Name of the director" + }, + "posterPath": { + "$ref": "#/components/schemas/posterPath" + } + } + } + } + }, + "totalPlays": { + "$ref": "#/components/schemas/playsOptional" + }, + "watchDates": { + "type": "array", + "properties": { + "date": { + "$ref": "#/components/schemas/dateNullable" + } + } + }, + "isOnWatchList": { + "type": "boolean", + "description": "True if on watchlist, false if not, and null if no token has been sent in the request header..", + "nullable": true + }, + "countries": { + "type": "object", + "properties": { + "ISOCode": { + "type": "object", + "description": "2-Letter ISO code of country, with as value their full name", + "example": { + "US": "United States" + } + } + }, + "example": { + "AF": "Afghanistan", + "AL": "Albania" + } + }, + "displayCharacterNames": { + "type": "boolean" + } + } + } + } + } + }, + "404": { + "description": "Movie was not found" + } + } + } + }, "/webhook/plex/{uuid}": { "post": { "tags": [ @@ -1396,6 +1612,12 @@ "example": 1, "nullable": false }, + "posterPath": { + "type": "string", + "description": "The path to the poster", + "nullable": true, + "example": "/storage/images/itemtype/1.jpg" + }, "positionOptional": { "type": "number", "example": 1, diff --git a/settings/routes.php b/settings/routes.php index 9f0d58aa4..ebbd501fa 100644 --- a/settings/routes.php +++ b/settings/routes.php @@ -221,7 +221,8 @@ function addApiRoutes(RouterService $routerService, FastRoute\RouteCollector $ro $routes->add('DELETE', $routeUserPlayed, [Api\PlayedController::class, 'deleteFromPlayed'], [Api\Middleware\IsAuthorizedToWriteUserData::class]); $routes->add('PUT', $routeUserPlayed, [Api\PlayedController::class, 'updatePlayed'], [Api\Middleware\IsAuthorizedToWriteUserData::class]); - $routes->add('GET', '/movies/search', [Api\MovieSearchController::class, 'search'], [Api\Middleware\IsAuthenticated::class]); + $routes->add('GET', '/movies/search', [Api\MovieController::class, 'search'], [Api\Middleware\IsAuthenticated::class]); + $routes->add('GET', '/movies/{id:\d}', [Api\MovieController::class, 'getMovie']); $routes->add('POST', '/webhook/plex/{id:.+}', [Api\PlexController::class, 'handlePlexWebhook']); $routes->add('POST', '/webhook/jellyfin/{id:.+}', [Api\JellyfinController::class, 'handleJellyfinWebhook']); diff --git a/src/HttpController/Api/MovieController.php b/src/HttpController/Api/MovieController.php new file mode 100644 index 000000000..2a2ce8cde --- /dev/null +++ b/src/HttpController/Api/MovieController.php @@ -0,0 +1,91 @@ +searchRequestMapper->mapRequest($request); + + $tmdbResponse = $this->tmdbApi->searchMovie( + $requestData->getSearchTerm(), + $requestData->getYear(), + $requestData->getPage(), + ); + + $paginationElements = $this->paginationElementsCalculator->createPaginationElements( + $tmdbResponse['total_results'], + (int)floor($tmdbResponse['total_results'] / $tmdbResponse['total_pages']), + $requestData->getPage(), + ); + + return Response::createJson( + Json::encode([ + 'results' => $this->historyResponseMapper->mapMovieSearchResults($tmdbResponse), + 'currentPage' => $paginationElements->getCurrentPage(), + 'maxPage' => $paginationElements->getMaxPage(), + ]), + ); + } + + public function getMovie(Request $request) : Response + { + $requestedMovieId = (int)$request->getRouteParameters()['id']; + $movie = $this->movieApi->findByIdFormatted($requestedMovieId); + if($movie === null) { + return Response::createNotFound(); + } + $userId = $this->authenticationService->getUserIdByApiToken($request); + if($userId === null) { + $movieTotalPlays = null; + $movieWatchDates = null; + $isOnWatchlist = null; + $displayCharacterNames = true; + } else { + $movieTotalPlays = $this->movieApi->fetchHistoryMovieTotalPlays($requestedMovieId, $userId); + $movieWatchDates = $this->movieApi->fetchHistoryByMovieId($requestedMovieId, $userId); + $isOnWatchlist = $this->movieWatchlistApi->hasMovieInWatchlist($userId, $requestedMovieId); + $displayCharacterNames = $this->userApi->findUserById($userId)?->getDisplayCharacterNames() ?? true; + } + return Response::createJson( + Json::encode([ + 'movie' => $movie, + 'movieGenres' => $this->movieApi->findGenresByMovieId($requestedMovieId), + 'castMembers' => $this->movieApi->findCastByMovieId($requestedMovieId), + 'directors' => $this->movieApi->findDirectorsByMovieId($requestedMovieId), + 'totalPlays' => $movieTotalPlays, + 'watchDates' => $movieWatchDates, + 'isOnWatchlist' => $isOnWatchlist, + 'countries' => $this->tmdbIsoCountryCache->fetchAll(), + 'displayCharacterNames' => $displayCharacterNames + ]) + ); + } +} diff --git a/src/HttpController/Api/MovieSearchController.php b/src/HttpController/Api/MovieSearchController.php deleted file mode 100644 index 43a341361..000000000 --- a/src/HttpController/Api/MovieSearchController.php +++ /dev/null @@ -1,47 +0,0 @@ -searchRequestMapper->mapRequest($request); - - $tmdbResponse = $this->tmdbApi->searchMovie( - $requestData->getSearchTerm(), - $requestData->getYear(), - $requestData->getPage(), - ); - - $paginationElements = $this->paginationElementsCalculator->createPaginationElements( - $tmdbResponse['total_results'], - (int)floor($tmdbResponse['total_results'] / $tmdbResponse['total_pages']), - $requestData->getPage(), - ); - - return Response::createJson( - Json::encode([ - 'results' => $this->historyResponseMapper->mapMovieSearchResults($tmdbResponse), - 'currentPage' => $paginationElements->getCurrentPage(), - 'maxPage' => $paginationElements->getMaxPage(), - ]), - ); - } -} diff --git a/tests/rest/api/movie-search.http b/tests/rest/api/movie-search.http deleted file mode 100644 index 5eac78441..000000000 --- a/tests/rest/api/movie-search.http +++ /dev/null @@ -1,7 +0,0 @@ -GET http://127.0.0.1/api/movies/search?search=Matrix&page=1&releaseYear=2012 -Accept: */* -Cache-Control: no-cache -Content-Type: application/json -X-Movary-Token: {{xMovaryToken}} - -#### diff --git a/tests/rest/api/movie.http b/tests/rest/api/movie.http new file mode 100644 index 000000000..5cfd5dd7e --- /dev/null +++ b/tests/rest/api/movie.http @@ -0,0 +1,45 @@ +#@name Search movie +GET http://127.0.0.1/api/movies/search?search=Matrix&page=1&releaseYear=2012 +Accept: */* +Cache-Control: no-cache +Content-Type: application/json +X-Movary-Token: {{xAuthToken}} + +#### +#@name Get movie data +GET http://127.0.0.1/api/movies/1 +Accept: */* +Cache-Control: no-cache +Content-Type: application/json + +> {% +client.test('Has correct status code', () => { + let expectedStatusCode = 200; + client.assert(response.status === expectedStatusCode, 'Response did not have status code ' + expectedStatusCode); +}); +client.test('Has the right properties', () => { + client.assert(response.body.hasOwnProperty('movie') === true, 'Response did not have the \'movie\' property.'); + client.assert(response.body.hasOwnProperty('movieGenres') === true, 'Response did not have the \'movieGenres\' property.'); + client.assert(response.body.hasOwnProperty('castMembers') === true, 'Response did not have the \'castMembers\' property.'); + client.assert(response.body.hasOwnProperty('directors') === true, 'Response did not have the \'directors\' property.'); + client.assert(response.body.hasOwnProperty('totalPlays') === true, 'Response did not have the \'totalPlays\' property.'); + client.assert(response.body.hasOwnProperty('watchDates') === true, 'Response did not have the \'watchDates\' property.'); + client.assert(response.body.hasOwnProperty('isOnWatchlist') === true, 'Response did not have the \'isOnWatchlist\' property.'); + client.assert(response.body.hasOwnProperty('countries') === true, 'Response did not have the \'countries\' property.'); + client.assert(response.body.hasOwnProperty('displayCharacterNames') === true, 'Response did not have the \'displayCharacterNames\' property.'); +}); +%} + +#### +#@name Trigger Not Found +GET http://127.0.0.1/api/movies/-1 +Accept: */* +Cache-Control: no-cache +Content-Type: application/json + +> {% + client.test('Has correct status code', () => { + let expectedStatusCode = 404; + client.assert(response.status === expectedStatusCode, 'Response did not have status code ' + expectedStatusCode); + }); +%} From 0db37622c5fe97573bb1504f5110809d46efead2 Mon Sep 17 00:00:00 2001 From: JVT038 <47184046+JVT038@users.noreply.github.com> Date: Sat, 2 Mar 2024 12:55:58 +0100 Subject: [PATCH 2/8] Centralize authentication process Signed-off-by: JVT038 <47184046+JVT038@users.noreply.github.com> --- src/Domain/User/Service/Authentication.php | 96 +++++++++++++------ .../Api/AuthenticationController.php | 2 +- .../Api/ValueObject/AuthenticationObject.php | 46 +++++++++ 3 files changed, 116 insertions(+), 28 deletions(-) create mode 100644 src/HttpController/Api/ValueObject/AuthenticationObject.php diff --git a/src/Domain/User/Service/Authentication.php b/src/Domain/User/Service/Authentication.php index e23d86661..2ca1a211a 100644 --- a/src/Domain/User/Service/Authentication.php +++ b/src/Domain/User/Service/Authentication.php @@ -9,6 +9,7 @@ use Movary\Domain\User\UserApi; use Movary\Domain\User\UserEntity; use Movary\Domain\User\UserRepository; +use Movary\HttpController\Api\ValueObject\AuthenticationObject; use Movary\HttpController\Web\CreateUserController; use Movary\Util\SessionWrapper; use Movary\ValueObject\DateTime; @@ -29,6 +30,52 @@ public function __construct( ) { } + public function createAuthenticationObjectFromCookie() : ?AuthenticationObject + { + $token = filter_input(INPUT_COOKIE, self::AUTHENTICATION_COOKIE_NAME); + $authenticationMethod = AuthenticationObject::COOKIE_AUTHENTICATION; + if (empty($token) === true || $this->isValidToken((string)$token) === true) { + unset($_COOKIE[self::AUTHENTICATION_COOKIE_NAME]); + setcookie(self::AUTHENTICATION_COOKIE_NAME, '', -1); + return null; + } + $user = $this->userApi->findByToken($token); + if(empty($user) === true) { + return null; + } + return AuthenticationObject::createAuthenticationObject($token, $authenticationMethod, $user); + } + + public function createAuthenticationObjectDynamically(Request $request) : ?AuthenticationObject + { + $token = $request->getHeaders()['X-Movary-Token'] ?? filter_input(INPUT_COOKIE, self::AUTHENTICATION_COOKIE_NAME); + $authenticationMethod = AuthenticationObject::COOKIE_AUTHENTICATION; + if (empty($token) === true || $this->isValidToken($token) === false) { + unset($_COOKIE[self::AUTHENTICATION_COOKIE_NAME]); + setcookie(self::AUTHENTICATION_COOKIE_NAME, '', -1); + $authenticationMethod = AuthenticationObject::HEADER_AUTHENTICATION; + } + $user = $this->userApi->findByToken($token); + if(empty($user) === true) { + return null; + } + return AuthenticationObject::createAuthenticationObject($token, $authenticationMethod, $user); + } + + public function createAuthenticationObjectFromHeader(Request $request) : ?AuthenticationObject + { + $token = $request->getHeaders()['X-Movary-Token'] ?? null; + $authenticationMethod = AuthenticationObject::HEADER_AUTHENTICATION; + if (empty($token) === true || $this->isValidToken((string)$token) === false) { + return null; + } + $user = $this->userApi->findByToken($token); + if(empty($user) === true) { + return null; + } + return AuthenticationObject::createAuthenticationObject($token, $authenticationMethod, $user); + } + public function createExpirationDate(int $days = 1) : DateTime { $timestamp = strtotime('+' . $days . ' day'); @@ -84,28 +131,27 @@ public function getCurrentUser() : UserEntity public function getCurrentUserId() : int { $userId = $this->sessionWrapper->find('userId'); - $token = filter_input(INPUT_COOKIE, self::AUTHENTICATION_COOKIE_NAME); - if ($userId === null && $token !== null) { - $userId = $this->repository->findUserIdByAuthToken((string)$token); - $this->sessionWrapper->set('userId', $userId); + if ($userId === null) { + + $authenticationObject = $this->createAuthenticationObjectFromCookie(); + if($authenticationObject === null) { + throw new RuntimeException('Could not find a current user'); + } + $this->sessionWrapper->set('userId', $authenticationObject->getUser()->getId()); } if ($userId === null) { throw new RuntimeException('Could not find a current user'); } - return $userId; } public function getToken(Request $request) : ?string { - $tokenInCookie = filter_input(INPUT_COOKIE, self::AUTHENTICATION_COOKIE_NAME); - if ($tokenInCookie !== false && $tokenInCookie !== null) { - return $tokenInCookie; - } + $authenticationObject = $this->createAuthenticationObjectDynamically($request); - return $request->getHeaders()['X-Movary-Token'] ?? null; + return $authenticationObject?->getToken(); } public function getUserIdByApiToken(Request $request) : ?int @@ -115,7 +161,7 @@ public function getUserIdByApiToken(Request $request) : ?int return null; } - if ($this->isValidAuthToken($apiToken) === false) { + if ($this->isValidToken($apiToken) === false) { return null; } @@ -124,18 +170,11 @@ public function getUserIdByApiToken(Request $request) : ?int public function isUserAuthenticatedWithCookie() : bool { - $token = filter_input(INPUT_COOKIE, self::AUTHENTICATION_COOKIE_NAME); - - if (empty($token) === false && $this->isValidAuthToken((string)$token) === true) { - return true; - } - - if (empty($token) === false) { - unset($_COOKIE[self::AUTHENTICATION_COOKIE_NAME]); - setcookie(self::AUTHENTICATION_COOKIE_NAME, '', -1); + $authenticationObject = $this->createAuthenticationObjectFromCookie(); + if($authenticationObject === null) { + return false; } - - return false; + return true; } public function isUserPageVisibleForApiRequest(Request $request, UserEntity $targetUser) : bool @@ -155,16 +194,19 @@ public function isUserPageVisibleForWebRequest(UserEntity $targetUser) : bool return $this->isUserPageVisibleForUser($targetUser, $requestUserId); } - public function isValidAuthToken(string $token) : bool + public function isValidToken(string $token) : bool { $tokenExpirationDate = $this->repository->findAuthTokenExpirationDate($token); - if ($tokenExpirationDate === null || $tokenExpirationDate->isAfter(DateTime::create()) === false) { - if ($tokenExpirationDate !== null) { + if ($tokenExpirationDate === null) { + if($this->repository->findUserByToken($token) === null) { + return false; + } + } else { + if($tokenExpirationDate->isAfter(DateTime::create()) === false) { $this->repository->deleteAuthToken($token); + return false; } - - return false; } return true; diff --git a/src/HttpController/Api/AuthenticationController.php b/src/HttpController/Api/AuthenticationController.php index 11324f47b..60da653e0 100644 --- a/src/HttpController/Api/AuthenticationController.php +++ b/src/HttpController/Api/AuthenticationController.php @@ -138,7 +138,7 @@ public function getTokenData(Request $request) : Response return Response::createUnauthorized(); } - if ($this->authenticationService->isUserAuthenticatedWithCookie() && $this->authenticationService->isValidAuthToken($token) === false) { + if($this->authenticationService->isUserAuthenticatedWithCookie() && $this->authenticationService->isValidToken($token) === false) { return Response::createUnauthorized(); } diff --git a/src/HttpController/Api/ValueObject/AuthenticationObject.php b/src/HttpController/Api/ValueObject/AuthenticationObject.php new file mode 100644 index 000000000..2eab2fb98 --- /dev/null +++ b/src/HttpController/Api/ValueObject/AuthenticationObject.php @@ -0,0 +1,46 @@ +token; + } + + public function getAuthenticationMethod() : int + { + return $this->authenticationMethod; + } + + public function getUser() : UserEntity + { + return $this->userId; + } + + public function hasCookieAuthentication() : bool + { + return $this->authenticationMethod === self::COOKIE_AUTHENTICATION; + } + + public function hasHeaderAuthentication() : bool + { + return $this->authenticationMethod === self::HEADER_AUTHENTICATION; + } +} From eca3498474424bcca602475d89d3c623f88ce322 Mon Sep 17 00:00:00 2001 From: JVT038 <47184046+JVT038@users.noreply.github.com> Date: Sat, 2 Mar 2024 12:57:46 +0100 Subject: [PATCH 3/8] Rename userid to user Signed-off-by: JVT038 <47184046+JVT038@users.noreply.github.com> --- .../Api/ValueObject/AuthenticationObject.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/HttpController/Api/ValueObject/AuthenticationObject.php b/src/HttpController/Api/ValueObject/AuthenticationObject.php index 2eab2fb98..7fc8ae348 100644 --- a/src/HttpController/Api/ValueObject/AuthenticationObject.php +++ b/src/HttpController/Api/ValueObject/AuthenticationObject.php @@ -11,12 +11,12 @@ class AuthenticationObject public function __construct( public readonly string $token, public readonly int $authenticationMethod, - public readonly UserEntity $userId, + public readonly UserEntity $user, ) { } - public static function createAuthenticationObject(string $token, int $authenticationMethod, UserEntity $userId) : self + public static function createAuthenticationObject(string $token, int $authenticationMethod, UserEntity $user) : self { - return new self($token, $authenticationMethod, $userId); + return new self($token, $authenticationMethod, $user); } public function getToken() : string @@ -31,7 +31,7 @@ public function getAuthenticationMethod() : int public function getUser() : UserEntity { - return $this->userId; + return $this->user; } public function hasCookieAuthentication() : bool From ef32ca7ec075e70b25f9a9bb389f95d6fa95efe5 Mon Sep 17 00:00:00 2001 From: JVT038 <47184046+JVT038@users.noreply.github.com> Date: Sat, 2 Mar 2024 13:05:19 +0100 Subject: [PATCH 4/8] Add method to find user by API token Signed-off-by: JVT038 <47184046+JVT038@users.noreply.github.com> --- src/Domain/User/Service/Authentication.php | 2 +- src/Domain/User/UserRepository.php | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/Domain/User/Service/Authentication.php b/src/Domain/User/Service/Authentication.php index 2ca1a211a..2f8db9e8a 100644 --- a/src/Domain/User/Service/Authentication.php +++ b/src/Domain/User/Service/Authentication.php @@ -199,7 +199,7 @@ public function isValidToken(string $token) : bool $tokenExpirationDate = $this->repository->findAuthTokenExpirationDate($token); if ($tokenExpirationDate === null) { - if($this->repository->findUserByToken($token) === null) { + if($this->repository->findUserByApiToken($token) === null) { return false; } } else { diff --git a/src/Domain/User/UserRepository.php b/src/Domain/User/UserRepository.php index 2619867a2..e82b70115 100644 --- a/src/Domain/User/UserRepository.php +++ b/src/Domain/User/UserRepository.php @@ -350,6 +350,24 @@ public function findUserByName(string $name) : ?UserEntity return UserEntity::createFromArray($data); } + public function findUserByApiToken(string $apiToken) : ?UserEntity + { + $data = $this->dbConnection->fetchAssociative( + 'SELECT user.* + FROM user + LEFT JOIN user_api_token ON user.id = user_api_token.user_id + LEFT JOIN user_auth_token ON user.id = user_auth_token.user_id + WHERE user_api_token.token = ?', + [$apiToken], + ); + + if (empty($data) === true) { + return null; + } + + return UserEntity::createFromArray($data); + } + public function findUserByToken(string $apiToken) : ?UserEntity { $data = $this->dbConnection->fetchAssociative( From 9ad9a0d684ab7ee5620a4ceb82d9da2a249201f1 Mon Sep 17 00:00:00 2001 From: JVT038 <47184046+JVT038@users.noreply.github.com> Date: Sat, 2 Mar 2024 13:09:50 +0100 Subject: [PATCH 5/8] Reorder authentication priority and fix authenticationMethod Signed-off-by: JVT038 <47184046+JVT038@users.noreply.github.com> --- src/Domain/User/Service/Authentication.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Domain/User/Service/Authentication.php b/src/Domain/User/Service/Authentication.php index 2f8db9e8a..0a70af60b 100644 --- a/src/Domain/User/Service/Authentication.php +++ b/src/Domain/User/Service/Authentication.php @@ -48,13 +48,13 @@ public function createAuthenticationObjectFromCookie() : ?AuthenticationObject public function createAuthenticationObjectDynamically(Request $request) : ?AuthenticationObject { - $token = $request->getHeaders()['X-Movary-Token'] ?? filter_input(INPUT_COOKIE, self::AUTHENTICATION_COOKIE_NAME); - $authenticationMethod = AuthenticationObject::COOKIE_AUTHENTICATION; + $token = filter_input(INPUT_COOKIE, self::AUTHENTICATION_COOKIE_NAME) ?? $request->getHeaders()['X-Movary-Token'] ?? null; if (empty($token) === true || $this->isValidToken($token) === false) { unset($_COOKIE[self::AUTHENTICATION_COOKIE_NAME]); setcookie(self::AUTHENTICATION_COOKIE_NAME, '', -1); - $authenticationMethod = AuthenticationObject::HEADER_AUTHENTICATION; + return null; } + $authenticationMethod = empty($request->getHeaders()['X-Movary-Token']) === true ? AuthenticationObject::COOKIE_AUTHENTICATION : AuthenticationObject::HEADER_AUTHENTICATION; $user = $this->userApi->findByToken($token); if(empty($user) === true) { return null; From 396eb33c1a073b68257cf772b3a0fefa85ff1654 Mon Sep 17 00:00:00 2001 From: JVT038 <47184046+JVT038@users.noreply.github.com> Date: Sat, 30 Nov 2024 17:24:42 +0100 Subject: [PATCH 6/8] Fix type --- src/Domain/User/Service/Authentication.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Domain/User/Service/Authentication.php b/src/Domain/User/Service/Authentication.php index 0a70af60b..6f3d5413f 100644 --- a/src/Domain/User/Service/Authentication.php +++ b/src/Domain/User/Service/Authentication.php @@ -18,9 +18,9 @@ class Authentication { - private const AUTHENTICATION_COOKIE_NAME = 'id'; + private const string AUTHENTICATION_COOKIE_NAME = 'id'; - private const MAX_EXPIRATION_AGE_IN_DAYS = 30; + private const int MAX_EXPIRATION_AGE_IN_DAYS = 30; public function __construct( private readonly UserRepository $repository, @@ -46,6 +46,13 @@ public function createAuthenticationObjectFromCookie() : ?AuthenticationObject return AuthenticationObject::createAuthenticationObject($token, $authenticationMethod, $user); } + /** + * This method will 'dynamically' create an authentication object. + * It will first check if cookie authentication is possible and if not, it'll check if header authentication is possible. + * If neither are possible and/or the authentication token is invalid, then the cookie is deleted and null is returned. + * @param Request $request + * @return AuthenticationObject|null + */ public function createAuthenticationObjectDynamically(Request $request) : ?AuthenticationObject { $token = filter_input(INPUT_COOKIE, self::AUTHENTICATION_COOKIE_NAME) ?? $request->getHeaders()['X-Movary-Token'] ?? null; @@ -75,7 +82,6 @@ public function createAuthenticationObjectFromHeader(Request $request) : ?Authen } return AuthenticationObject::createAuthenticationObject($token, $authenticationMethod, $user); } - public function createExpirationDate(int $days = 1) : DateTime { $timestamp = strtotime('+' . $days . ' day'); From 2bd71c51b97069e45381f0f1e16ce1808833ba09 Mon Sep 17 00:00:00 2001 From: JVT038 <47184046+JVT038@users.noreply.github.com> Date: Sat, 30 Nov 2024 20:19:35 +0100 Subject: [PATCH 7/8] Fix breaking error --- src/Domain/User/Service/Authentication.php | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Domain/User/Service/Authentication.php b/src/Domain/User/Service/Authentication.php index 6fc0de784..a0fbea50f 100644 --- a/src/Domain/User/Service/Authentication.php +++ b/src/Domain/User/Service/Authentication.php @@ -34,7 +34,7 @@ public function createAuthenticationObjectFromCookie() : ?AuthenticationObject { $token = filter_input(INPUT_COOKIE, self::AUTHENTICATION_COOKIE_NAME); $authenticationMethod = AuthenticationObject::COOKIE_AUTHENTICATION; - if (empty($token) === true || $this->isValidToken((string)$token) === true) { + if (empty($token) === true || $this->isValidToken((string)$token) === false) { unset($_COOKIE[self::AUTHENTICATION_COOKIE_NAME]); setcookie(self::AUTHENTICATION_COOKIE_NAME, '', -1); return null; @@ -145,9 +145,10 @@ public function getCurrentUserId() : int throw new RuntimeException('Could not find a current user'); } $this->sessionWrapper->set('userId', $authenticationObject->getUser()->getId()); + $userId = $authenticationObject->getUser()->getId(); } - if ($userId === null) { + if ($userId == null) { throw new RuntimeException('Could not find a current user'); } return $userId; @@ -200,12 +201,16 @@ public function isUserPageVisibleForWebRequest(UserEntity $targetUser) : bool return $this->isUserPageVisibleForUser($targetUser, $requestUserId); } - public function isValidAuthToken(string $token) : bool + public function isValidToken(string $token) : bool { $tokenExpirationDate = $this->repository->findAuthTokenExpirationDate($token); - if ($tokenExpirationDate === null || $tokenExpirationDate->isAfter(DateTime::create()) === false) { - if ($tokenExpirationDate !== null) { + if ($tokenExpirationDate === null) { + if($this->repository->findUserByApiToken($token) === null) { + return false; + } + } else { + if($tokenExpirationDate->isAfter(DateTime::create()) === false) { $this->repository->deleteAuthToken($token); return false; } From 2a23ddbc748c3d20272fa60fcd051bbc970ddb98 Mon Sep 17 00:00:00 2001 From: JVT038 <47184046+JVT038@users.noreply.github.com> Date: Sun, 1 Dec 2024 12:03:00 +0100 Subject: [PATCH 8/8] Fix variable --- tests/rest/api/movie.http | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/rest/api/movie.http b/tests/rest/api/movie.http index 5cfd5dd7e..a663a418f 100644 --- a/tests/rest/api/movie.http +++ b/tests/rest/api/movie.http @@ -3,7 +3,7 @@ GET http://127.0.0.1/api/movies/search?search=Matrix&page=1&releaseYear=2012 Accept: */* Cache-Control: no-cache Content-Type: application/json -X-Movary-Token: {{xAuthToken}} +X-Movary-Token: {{xMovaryToken}} #### #@name Get movie data