diff --git a/docs/openapi.json b/docs/openapi.json
index bb676856..ce1054b4 100644
--- a/docs/openapi.json
+++ b/docs/openapi.json
@@ -1067,10 +1067,70 @@
}
},
"/authentication/token": {
+ "get": {
+ "tags": [
+ "Authentication"
+ ],
+ "summary": "Get authentication data",
+ "description": "Get information about the authenticated user.",
+ "parameters": [
+ {
+ "in": "header",
+ "name": "X-Auth-Token",
+ "schema": {
+ "type": "string"
+ },
+ "required": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "user": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "The name of the user"
+ },
+ "userId": {
+ "type": "integer",
+ "description": "The id of the user"
+ },
+ "isAdmin": {
+ "type": "boolean",
+ "description": "True if user is an admin, false if not."
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "400": {
+ "$ref": "#/components/responses/400"
+ },
+ "401": {
+ "$ref": "#/components/responses/401"
+ }
+ },
+ "security": [
+ {
+ "token": []
+ }
+ ]
+ },
"post": {
"tags": [
"Authentication"
],
+ "summary": "Create authentication token",
"description": "Create an authentication token via email, password and optionally TOTP code. Add the token as X-Auth-Token header to further requests. Token lifetime 1d default, 30d with rememberMe.",
"parameters": [
{
@@ -1131,13 +1191,26 @@
"schema": {
"type": "object",
"properties": {
- "userId": {
- "type": "integer",
- "description": "The id of the authenticated user."
- },
- "token": {
+ "authToken": {
"type": "string",
"description": "The authentication token to be used in future requests."
+ },
+ "user": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "The username of the user"
+ },
+ "userId": {
+ "type": "integer",
+ "description": "The ID of the authenticated user"
+ },
+ "isAdmin": {
+ "type": "boolean",
+ "description": "True if the user is an admin, False if the user isn't."
+ }
+ }
}
}
}
@@ -1156,6 +1229,7 @@
"tags": [
"Authentication"
],
+ "summary": "Delete authentication token",
"description": "Delete the authentication token provided in the X-Auth-Token header value.",
"parameters": [
{
@@ -1399,11 +1473,6 @@
"type": "apiKey",
"name": "X-Auth-Token",
"in": "header"
- },
- "cookie": {
- "type": "apiKey",
- "name": "id",
- "in": "cookie"
}
}
}
diff --git a/settings/phpcs.xml b/settings/phpcs.xml
index 558074bc..edcebc3e 100644
--- a/settings/phpcs.xml
+++ b/settings/phpcs.xml
@@ -18,7 +18,11 @@
-
+
+
+
+
+
diff --git a/settings/routes.php b/settings/routes.php
index 90801d6c..9f0d58aa 100644
--- a/settings/routes.php
+++ b/settings/routes.php
@@ -202,6 +202,7 @@ function addApiRoutes(RouterService $routerService, FastRoute\RouteCollector $ro
$routes->add('GET', '/openapi', [Api\OpenApiController::class, 'getSchema']);
$routes->add('POST', '/authentication/token', [Api\AuthenticationController::class, 'createToken']);
$routes->add('DELETE', '/authentication/token', [Api\AuthenticationController::class, 'destroyToken']);
+ $routes->add('GET', '/authentication/token', [Api\AuthenticationController::class, 'getTokenData']);
$routeUserHistory = '/users/{username:[a-zA-Z0-9]+}/history/movies';
$routes->add('GET', $routeUserHistory, [Api\HistoryController::class, 'getHistory'], [Api\Middleware\IsAuthorizedToReadUserData::class]);
diff --git a/src/Domain/User/Service/Authentication.php b/src/Domain/User/Service/Authentication.php
index 4eb2c3bf..0d03aeb8 100644
--- a/src/Domain/User/Service/Authentication.php
+++ b/src/Domain/User/Service/Authentication.php
@@ -98,14 +98,19 @@ public function getCurrentUserId() : int
return $userId;
}
- public function getToken() : ?string
+ public function getToken(Request $request) : ?string
{
- return $_COOKIE[self::AUTHENTICATION_COOKIE_NAME];
+ $tokenInCookie = filter_input(INPUT_COOKIE, self::AUTHENTICATION_COOKIE_NAME);
+ if ($tokenInCookie !== false && $tokenInCookie !== null) {
+ return $tokenInCookie;
+ }
+
+ return $request->getHeaders()['X-Auth-Token'] ?? null;
}
public function getUserIdByApiToken(Request $request) : ?int
{
- $apiToken = $request->getHeaders()['X-Auth-Token'] ?? filter_input(INPUT_COOKIE, self::AUTHENTICATION_COOKIE_NAME) ?? null;
+ $apiToken = $this->getToken($request);
if ($apiToken === null) {
return null;
}
@@ -117,7 +122,7 @@ public function isUserAuthenticatedWithCookie() : bool
{
$token = filter_input(INPUT_COOKIE, self::AUTHENTICATION_COOKIE_NAME);
- if (empty($token) === false && $this->isValidToken((string)$token) === true) {
+ if (empty($token) === false && $this->isValidAuthToken((string)$token) === true) {
return true;
}
@@ -159,7 +164,7 @@ public function isUserPageVisibleForCurrentUser(int $privacyLevel, int $userId)
return $this->isUserAuthenticatedWithCookie() === true && $this->getCurrentUserId() === $userId;
}
- public function isValidToken(string $token) : bool
+ public function isValidAuthToken(string $token) : bool
{
$tokenExpirationDate = $this->repository->findAuthTokenExpirationDate($token);
diff --git a/src/HttpController/Api/AuthenticationController.php b/src/HttpController/Api/AuthenticationController.php
index f1687c0c..f2c76057 100644
--- a/src/HttpController/Api/AuthenticationController.php
+++ b/src/HttpController/Api/AuthenticationController.php
@@ -16,6 +16,7 @@ class AuthenticationController
{
public function __construct(
private readonly Authentication $authenticationService,
+ private readonly UserApi $userApi,
) {
}
@@ -85,8 +86,12 @@ public function createToken(Request $request) : Response
return Response::createJson(
Json::encode([
- 'userId' => $userAndAuthToken['user']->getId(),
- 'authToken' => $userAndAuthToken['token']
+ 'authToken' => $userAndAuthToken['token'],
+ 'user' => [
+ 'id' => $userAndAuthToken['user']->getId(),
+ 'name' => $userAndAuthToken['user']->getName(),
+ 'isAdmin' => $userAndAuthToken['user']->isAdmin(),
+ ]
]),
);
}
@@ -99,12 +104,12 @@ public function destroyToken(Request $request) : Response
return Response::CreateNoContent();
}
- $apiToken = $request->getHeaders()['X-Auth-Token'] ?? null;
+ $apiToken = $this->authenticationService->getToken($request);
if ($apiToken === null) {
return Response::createBadRequest(
Json::encode([
'error' => 'MissingAuthToken',
- 'message' => 'Authentication token to delete in headers missing'
+ 'message' => 'Authentication token header is missing'
]),
[Header::createContentTypeJson()],
);
@@ -114,4 +119,37 @@ public function destroyToken(Request $request) : Response
return Response::CreateNoContent();
}
+
+ public function getTokenData(Request $request) : Response
+ {
+ $token = $this->authenticationService->getToken($request);
+ if ($token === null) {
+ return Response::createBadRequest(
+ Json::encode([
+ 'error' => 'MissingAuthToken',
+ 'message' => 'Authentication token header is missing'
+ ]),
+ [Header::createContentTypeJson()],
+ );
+ }
+
+ $user = $this->userApi->findByToken($token);
+ if ($user === null) {
+ return Response::createUnauthorized();
+ }
+
+ if($this->authenticationService->isUserAuthenticatedWithCookie() && $this->authenticationService->isValidAuthToken($token) === false) {
+ return Response::createUnauthorized();
+ }
+
+ return Response::createJson(
+ Json::encode([
+ 'user' => [
+ 'id' => $user->getId(),
+ 'name' => $user->getName(),
+ 'isAdmin' => $user->isAdmin(),
+ ]
+ ]),
+ );
+ }
}
diff --git a/tests/rest/api/authentication.assert.http b/tests/rest/api/authentication.assert.http
index 96c5fcf4..d056dbc7 100644
--- a/tests/rest/api/authentication.assert.http
+++ b/tests/rest/api/authentication.assert.http
@@ -54,8 +54,10 @@ X-Movary-Client: RestAPI Test
client.assert(response.status === expected, "Expected status code: " + expected);
});
client.test("Response has correct body", function() {
- client.assert(response.body.hasOwnProperty("'userId'") === false, "Response body missing property: userId");
- client.assert(response.body.hasOwnProperty("'authToken'") === false, "Response body missing property: authToken");
+ client.assert(response.body.hasOwnProperty("authToken") === true, "Response body missing property: authToken");
+ client.assert(response.body.user.hasOwnProperty("id") === true, "Response body missing property: user.id");
+ client.assert(response.body.user.hasOwnProperty("name") === true, "Response body missing property: user.name");
+ client.assert(response.body.user.hasOwnProperty("isAdmin") === true, "Response body missing property: user.isAdmin");
});
client.global.set("responseAuthToken", response.body.authToken);
@@ -63,6 +65,26 @@ X-Movary-Client: RestAPI Test
###
+GET http://127.0.0.1/api/authentication/token
+Accept: */*
+Cache-Control: no-cache
+Content-Type: application/json
+X-Auth-Token: {{responseAuthToken}}
+
+> {%
+ client.test("Response has correct status code", function() {
+ let expected = 200
+ client.assert(response.status === expected, "Expected status code: " + expected);
+ });
+ client.test("Response has correct body", function() {
+ client.assert(response.body.user.hasOwnProperty("id") === true, "Response body missing property: user.id");
+ client.assert(response.body.user.hasOwnProperty("name") === true, "Response body missing property: user.name");
+ client.assert(response.body.user.hasOwnProperty("isAdmin") === true, "Response body missing property: user.isAdmin");
+ });
+%}
+
+###
+
DELETE http://127.0.0.1/api/authentication/token
Accept: */*
Cache-Control: no-cache
@@ -78,6 +100,38 @@ X-Auth-Token: {{responseAuthToken}}
###
+GET http://127.0.0.1/api/authentication/token
+Accept: */*
+Cache-Control: no-cache
+Content-Type: application/json
+X-Auth-Token: {{responseAuthToken}}
+
+> {%
+ client.test("Response has correct status code", function() {
+ let expected = 401
+ client.assert(response.status === expected, "Expected status code: " + expected);
+ });
+%}
+
+###
+
+GET http://127.0.0.1/api/authentication/token
+Accept: */*
+Cache-Control: no-cache
+Content-Type: application/json
+
+> {%
+ client.test("Response has correct status code", function() {
+ let expectedStatusCode = 400
+ let expectedError = "MissingAuthToken";
+ client.assert(response.status === expectedStatusCode, "Expected status code: " + expectedStatusCode);
+ client.assert(response.body.error === expectedError, "Expected error: " + expectedError);
+ client.assert(response.body.message === 'Authentication token header is missing');
+ });
+%}
+
+###
+
DELETE http://127.0.0.1/api/authentication/token
Accept: */*
Cache-Control: no-cache
@@ -89,7 +143,7 @@ Content-Type: application/json
client.assert(response.status === expected, "Expected status code: " + expected);
});
client.test("Response has correct body", function() {
- let expected = '{"error":"MissingAuthToken","message":"Authentication token to delete in headers missing"}';
+ let expected = '{"error":"MissingAuthToken","message":"Authentication token header is missing"}';
client.assert(JSON.stringify(response.body) === expected, "Expected response body: " + expected);
});
%}
diff --git a/tests/rest/api/authentication.http b/tests/rest/api/authentication.http
index 9e2d2a5e..324ccbab 100644
--- a/tests/rest/api/authentication.http
+++ b/tests/rest/api/authentication.http
@@ -8,6 +8,14 @@ X-Movary-Client: RestAPI Test
###
+GET http://127.0.0.1/api/authentication/token
+Accept: */*
+Cache-Control: no-cache
+Content-Type: application/json
+X-Auth-Token: {{xAuthToken}}
+
+###
+
DELETE http://127.0.0.1/api/authentication/token
Accept: */*
Cache-Control: no-cache