Reauth endpoint
This commit is contained in:
parent
7723dd0722
commit
9828ee762a
@ -33,7 +33,7 @@ class Login extends Api
|
||||
case "NotFound":
|
||||
throw ApiError::notFound("user");
|
||||
case "Invalid":
|
||||
throw ApiError::unauthorized("Invalid username or password");
|
||||
throw ApiError::notAllowed("Invalid username or password");
|
||||
default:
|
||||
throw $err;
|
||||
}
|
||||
|
||||
@ -60,7 +60,7 @@ class Posts extends Api
|
||||
$post = Post::getByID($id);
|
||||
|
||||
// Throw 400 if we aren't admin but trying to edit another users post.
|
||||
if (!$self->getIsAdmin() && $post->getUser()->getID() !== $self->getID()) throw ApiError::unauthorized("Not allowed");
|
||||
if (!$self->getIsAdmin() && $post->getUser()->getID() !== $self->getID()) throw ApiError::notAllowed("Not allowed");
|
||||
|
||||
// Try update.
|
||||
Response::json($post->update($content));
|
||||
|
||||
38
exam/api/Refresh/Refresh.php
Normal file
38
exam/api/Refresh/Refresh.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace Api\Refresh;
|
||||
|
||||
use Exception;
|
||||
use Khofmann\Api\Api;
|
||||
use Khofmann\ApiError\ApiError;
|
||||
use Khofmann\Input\Input;
|
||||
use Khofmann\Response\Response;
|
||||
use Khofmann\Models\User\User;
|
||||
use Khofmann\Request\Request;
|
||||
|
||||
class Refresh extends Api
|
||||
{
|
||||
public function post(): void
|
||||
{
|
||||
// Fetch all required inputs.
|
||||
// Throw 400 error if a required one is missing.
|
||||
$token = Request::token();
|
||||
$refreshToken = Input::post("refreshToken");
|
||||
if (empty($refreshToken)) throw ApiError::missingField(["refreshToken"]);
|
||||
|
||||
// Try and log in user.
|
||||
// Throw errors according to situation.
|
||||
try {
|
||||
Response::json(User::refresh($token, $refreshToken));
|
||||
} catch (Exception $err) {
|
||||
switch ($err->getMessage()) {
|
||||
case "Failed":
|
||||
throw ApiError::failed("Refresh failed");
|
||||
case "NotFound":
|
||||
throw ApiError::unauthorized("Not authorized");
|
||||
default:
|
||||
throw $err;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -310,6 +310,45 @@ paths:
|
||||
value: { "code": "NotFound", "entity": "post" }
|
||||
tags:
|
||||
- Post
|
||||
/refresh:
|
||||
post:
|
||||
summary: Refresh
|
||||
description: Token refresh.
|
||||
security:
|
||||
- BasicAuth: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/RefreshRequest"
|
||||
responses:
|
||||
200:
|
||||
description: Success.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/LoginResponse"
|
||||
400:
|
||||
description: Missing fields.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/MissingFieldResponse"
|
||||
examples:
|
||||
Missing fields:
|
||||
value: { "code": "MissingField", "fields": ["refreshToken"] }
|
||||
500:
|
||||
description: Failed.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/FailedResponse"
|
||||
examples:
|
||||
Failed:
|
||||
value: { "code": "Failed", "message": "Refresh failed" }
|
||||
tags:
|
||||
- Refresh
|
||||
/register:
|
||||
post:
|
||||
summary: Register
|
||||
@ -678,8 +717,7 @@ components:
|
||||
UnauthorizedResponse:
|
||||
type: object
|
||||
properties:
|
||||
code:
|
||||
type: Unauthorized
|
||||
code: type:NotAllowed
|
||||
message:
|
||||
type: string
|
||||
FailedResponse:
|
||||
@ -731,6 +769,8 @@ components:
|
||||
$ref: "#/components/schemas/UserResponse"
|
||||
token:
|
||||
type: string
|
||||
refreshToken:
|
||||
type: string
|
||||
UserResponse:
|
||||
type: object
|
||||
properties:
|
||||
@ -855,6 +895,14 @@ components:
|
||||
properties:
|
||||
content:
|
||||
type: string
|
||||
RefreshRequest:
|
||||
type: object
|
||||
required:
|
||||
- refreshToken
|
||||
properties:
|
||||
refreshToken:
|
||||
type: string
|
||||
format: uuid4
|
||||
securitySchemes:
|
||||
BasicAuth:
|
||||
type: apiKey
|
||||
@ -864,4 +912,5 @@ tags:
|
||||
- name: Login/Logout
|
||||
- name: Post
|
||||
- name: Register
|
||||
- name: Refresh
|
||||
- name: User
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -27,7 +27,7 @@ class ApiError extends Exception
|
||||
]), 400);
|
||||
}
|
||||
|
||||
public static function unauthorized(string $message)
|
||||
public static function notAllowed(string $message)
|
||||
{
|
||||
return new ApiError(json_encode([
|
||||
"code" => "NotAllowed",
|
||||
@ -35,6 +35,14 @@ class ApiError extends Exception
|
||||
]), 401);
|
||||
}
|
||||
|
||||
public static function unauthorized(string $message)
|
||||
{
|
||||
return new ApiError(json_encode([
|
||||
"code" => "Unauthorized",
|
||||
"message" => $message,
|
||||
]), 401);
|
||||
}
|
||||
|
||||
public static function notFound(string $entity)
|
||||
{
|
||||
return new ApiError(json_encode([
|
||||
|
||||
@ -34,7 +34,7 @@ class AdminAuth implements IMiddleware
|
||||
->header("Access-Control-Allow-Methods: *")
|
||||
->header("Access-Control-Allow-Headers: *")
|
||||
->httpCode(401)
|
||||
->json(["code" => "Unauthorized", "message" => "Not Authorized"]);
|
||||
->json(["code" => "NotAllowed", "message" => "Not Authorized"]);
|
||||
}
|
||||
} catch (Exception $err) {
|
||||
// No user with this token exists
|
||||
|
||||
@ -114,7 +114,8 @@ class User implements JsonSerializable
|
||||
FROM
|
||||
egb_benutzer AS b
|
||||
WHERE
|
||||
token = :TOKEN"
|
||||
token = :TOKEN AND
|
||||
tokenExpiry > NOW()"
|
||||
);
|
||||
$stmt->bindValue(":TOKEN", $token);
|
||||
$stmt->execute();
|
||||
@ -154,18 +155,29 @@ class User implements JsonSerializable
|
||||
$stmt->bindValue(":ID", $user->getID());
|
||||
$stmt->execute();
|
||||
}
|
||||
// Generate token
|
||||
$stmt = $db->prepare("UPDATE egb_benutzer SET token = UUID() WHERE id = :ID");
|
||||
$stmt->bindValue(":ID", $user->getID());
|
||||
$stmt->execute();
|
||||
if (empty($data["token"]) || new DateTime($data["tokenExpiry"]) <= new DateTime()) {
|
||||
// Generate token
|
||||
$stmt = $db->prepare(
|
||||
"UPDATE
|
||||
egb_benutzer
|
||||
SET
|
||||
token = UUID(),
|
||||
tokenExpiry = DATE_ADD(NOW(), INTERVAL 1 HOUR),
|
||||
refreshToken = UUID(),
|
||||
refreshExpiry = DATE_ADD(NOW(), INTERVAL 30 DAY)
|
||||
WHERE id = :ID"
|
||||
);
|
||||
$stmt->bindValue(":ID", $user->getID());
|
||||
$stmt->execute();
|
||||
}
|
||||
// Get token
|
||||
$stmt = $db->prepare("SELECT token FROM egb_benutzer WHERE id = :ID");
|
||||
$stmt = $db->prepare("SELECT token, refreshToken FROM egb_benutzer WHERE id = :ID");
|
||||
$stmt->bindValue(":ID", $user->getID());
|
||||
$stmt->execute();
|
||||
$token = $stmt->fetch(PDO::FETCH_COLUMN, 0);
|
||||
// Return user and token
|
||||
[$token, $refresh] = $stmt->fetch(PDO::FETCH_NUM);
|
||||
// Return user and tokens
|
||||
if ($token) {
|
||||
return ["user" => $user, "token" => $token];
|
||||
return ["user" => $user, "token" => $token, "refreshToken" => $refresh];
|
||||
}
|
||||
// Token generation failed
|
||||
throw new Exception("Failed");
|
||||
@ -261,6 +273,55 @@ class User implements JsonSerializable
|
||||
return ["pages" => intdiv($count, $limit + 1), "data" => $list];
|
||||
}
|
||||
|
||||
public static function refresh(string $token, string $refreshToken)
|
||||
{
|
||||
$db = Database::getInstance();
|
||||
$stmt = $db->prepare(
|
||||
"SELECT
|
||||
b.id, b.benutzer, b.status, b.email, b.image, b.isadmin, b.zeitstempel,
|
||||
(SELECT COUNT(*) FROM egb_gaestebuch WHERE benutzer_id = b.id) as postCount
|
||||
FROM
|
||||
egb_benutzer AS b
|
||||
WHERE
|
||||
token = :TOKEN AND
|
||||
refreshToken = :REFRESH AND
|
||||
refreshExpiry > NOW()"
|
||||
);
|
||||
$stmt->bindValue(":TOKEN", $token);
|
||||
$stmt->bindValue(":REFRESH", $refreshToken);
|
||||
$stmt->execute();
|
||||
$data = $stmt->fetch();
|
||||
|
||||
if (!$data) throw new Exception("NotFound");
|
||||
|
||||
$user = new User($data["id"], $data["benutzer"], $data["status"], $data["email"], $data["zeitstempel"], $data["image"], $data["isadmin"] === 1, $data["postCount"]);
|
||||
|
||||
$stmt = $db->prepare(
|
||||
"UPDATE
|
||||
egb_benutzer
|
||||
SET
|
||||
token = UUID(),
|
||||
tokenExpiry = DATE_ADD(NOW(), INTERVAL 1 HOUR),
|
||||
refreshToken = UUID(),
|
||||
refreshExpiry = DATE_ADD(NOW(), INTERVAL 30 DAY)
|
||||
WHERE id = :ID"
|
||||
);
|
||||
$stmt->bindValue(":ID", $user->getID());
|
||||
$stmt->execute();
|
||||
|
||||
// Get token
|
||||
$stmt = $db->prepare("SELECT token, refreshToken FROM egb_benutzer WHERE id = :ID");
|
||||
$stmt->bindValue(":ID", $user->getID());
|
||||
$stmt->execute();
|
||||
[$token, $refresh] = $stmt->fetch(PDO::FETCH_NUM);
|
||||
// Return user and tokens
|
||||
if ($token) {
|
||||
return ["user" => $user, "token" => $token, "refreshToken" => $refresh];
|
||||
}
|
||||
// Token generation failed
|
||||
throw new Exception("Failed");
|
||||
}
|
||||
|
||||
/*
|
||||
* Members
|
||||
*/
|
||||
@ -268,7 +329,16 @@ class User implements JsonSerializable
|
||||
public function logOut(): bool
|
||||
{
|
||||
$db = Database::getInstance();
|
||||
$stmt = $db->prepare("UPDATE egb_benutzer SET token = NULL WHERE id = :ID");
|
||||
$stmt = $db->prepare(
|
||||
"UPDATE
|
||||
egb_benutzer
|
||||
SET
|
||||
token = NULL,
|
||||
tokenExpiry = NULL,
|
||||
refreshToken = NULL,
|
||||
refreshExpiry = NULL
|
||||
WHERE id = :ID"
|
||||
);
|
||||
$stmt->bindValue(":ID", $this->id);
|
||||
return $stmt->execute();
|
||||
}
|
||||
|
||||
@ -35,6 +35,8 @@ SimpleRouter::post("/login", [Api\Login\Login::class, "post"]);
|
||||
// Register and confirm
|
||||
SimpleRouter::post("/register", [Api\Register\Register::class, "post"]);
|
||||
SimpleRouter::patch("/register", [Api\Register\Register::class, "patch"]);
|
||||
// Refresh (open, but still needs token)
|
||||
SimpleRouter::post("/refresh", [Api\Refresh\Refresh::class, "post"]);
|
||||
|
||||
/*
|
||||
* Optional Auth
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user