Skip to content

Commit

Permalink
Use JwtContext and JwtMiddleware to validate the requests
Browse files Browse the repository at this point in the history
  • Loading branch information
byjg committed May 8, 2024
1 parent f9e6e3e commit 6a6131d
Show file tree
Hide file tree
Showing 12 changed files with 114 additions and 80 deletions.
16 changes: 8 additions & 8 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@
"ext-json": "*",
"ext-openssl": "*",
"ext-curl": "*",
"byjg/config": "^4.9",
"byjg/anydataset-db": "^4.9",
"byjg/micro-orm": "^4.9",
"byjg/authuser": "^4.9",
"byjg/config": "^4.9.x-dev",
"byjg/anydataset-db": "^4.9.x-dev",
"byjg/micro-orm": "^4.9.x-dev",
"byjg/authuser": "^4.9.x-dev",
"byjg/mailwrapper": "^4.9",
"byjg/restserver": "^4.9",
"byjg/restserver": "^4.9.x-dev",
"zircote/swagger-php": "^4.6.1",
"byjg/swagger-test": "^4.9",
"byjg/migration": "^4.9",
"byjg/php-daemonize": "^4.9",
"byjg/swagger-test": "^4.9.x-dev",
"byjg/migration": "^4.9.x-dev",
"byjg/php-daemonize": "^4.9.x-dev",
"byjg/shortid": "^4.9",
"byjg/jinja-php": "^4.9"
},
Expand Down
8 changes: 8 additions & 0 deletions config/config-dev.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use ByJG\Mail\Wrapper\MailWrapperInterface;
use ByJG\RestServer\HttpRequestHandler;
use ByJG\RestServer\Middleware\CorsMiddleware;
use ByJG\RestServer\Middleware\JwtMiddleware;
use ByJG\RestServer\OutputProcessor\JsonCleanOutputProcessor;
use ByJG\RestServer\Route\OpenApiRouteList;
use ByJG\Util\JwtKeySecret;
Expand Down Expand Up @@ -118,6 +119,12 @@
return preg_split('/,(?![^{}]*})/', Psr11::container()->get('CORS_SERVERS'));
},

JwtMiddleware::class => DI::bind(JwtMiddleware::class)
->withConstructorArgs([
Param::get(JwtWrapper::class)
])
->toSingleton(),

CorsMiddleware::class => DI::bind(CorsMiddleware::class)
->withNoConstructor()
->withMethodCall("withCorsOrigins", [Param::get("CORS_SERVER_LIST")]) // Required to enable CORS
Expand All @@ -126,6 +133,7 @@
->toSingleton(),

HttpRequestHandler::class => DI::bind(HttpRequestHandler::class)
->withMethodCall('withMiddleware', [Param::get(JwtMiddleware::class)])
->withMethodCall("withMiddleware", [Param::get(CorsMiddleware::class)])
->toSingleton(),

Expand Down
4 changes: 2 additions & 2 deletions docs/getting_started_03_create_rest_method.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,10 @@ public function putExampleCrudStatus(HttpResponse $response, HttpRequest $reques
// Use one of the following methods:

// a. Require a user with role admin
$data = JwtContext::requireRole("admin");
JwtContext::requireRole($request, "admin");

// b. OR require any logged user
$data = JwtContext::requireAuthenticated();
JwtContext::requireAuthenticated($request);

// c. OR do nothing to make the endpoint public
}
Expand Down
11 changes: 11 additions & 0 deletions src/Repository/BaseRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,17 @@ public function getMapper()
return $this->repository->getMapper();
}

public function getDbDriver()
{
return $this->repository->getDbDriver();
}

public function getByQuery($query)
{
$query->table($this->repository->getMapper()->getTable());
return $this->repository->getByQuery($query);
}

protected function prepareUuidQuery($itemId)
{
$result = [];
Expand Down
8 changes: 4 additions & 4 deletions src/Rest/DummyHexRest.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ class DummyHexRest
)]
public function getDummyHex(HttpResponse $response, HttpRequest $request): void
{
$data = JwtContext::requireAuthenticated();
JwtContext::requireAuthenticated($request);

$dummyHexRepo = Psr11::container()->get(DummyHexRepository::class);
$id = $request->param('id');
Expand Down Expand Up @@ -154,7 +154,7 @@ public function getDummyHex(HttpResponse $response, HttpRequest $request): void
)]
public function listDummyHex(HttpResponse $response, HttpRequest $request): void
{
$data = JwtContext::requireAuthenticated();
JwtContext::requireAuthenticated($request);

$repo = Psr11::container()->get(DummyHexRepository::class);

Expand Down Expand Up @@ -227,7 +227,7 @@ public function listDummyHex(HttpResponse $response, HttpRequest $request): void
)]
public function postDummyHex(HttpResponse $response, HttpRequest $request): void
{
$data = JwtContext::requireRole(User::ROLE_ADMIN);
JwtContext::requireRole($request, User::ROLE_ADMIN);

$payload = OpenApiContext::validateRequest($request);

Expand Down Expand Up @@ -286,7 +286,7 @@ public function postDummyHex(HttpResponse $response, HttpRequest $request): void
)]
public function putDummyHex(HttpResponse $response, HttpRequest $request): void
{
$data = JwtContext::requireRole(User::ROLE_ADMIN);
JwtContext::requireRole($request, User::ROLE_ADMIN);

$payload = OpenApiContext::validateRequest($request);

Expand Down
8 changes: 4 additions & 4 deletions src/Rest/DummyRest.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ class DummyRest
)]
public function getDummy(HttpResponse $response, HttpRequest $request): void
{
$data = JwtContext::requireAuthenticated();
JwtContext::requireAuthenticated($request);

$dummyRepo = Psr11::container()->get(DummyRepository::class);
$id = $request->param('id');
Expand Down Expand Up @@ -154,7 +154,7 @@ public function getDummy(HttpResponse $response, HttpRequest $request): void
)]
public function listDummy(HttpResponse $response, HttpRequest $request): void
{
$data = JwtContext::requireAuthenticated();
JwtContext::requireAuthenticated($request);

$repo = Psr11::container()->get(DummyRepository::class);

Expand Down Expand Up @@ -227,7 +227,7 @@ public function listDummy(HttpResponse $response, HttpRequest $request): void
)]
public function postDummy(HttpResponse $response, HttpRequest $request): void
{
$data = JwtContext::requireRole(User::ROLE_ADMIN);
JwtContext::requireRole($request, User::ROLE_ADMIN);

$payload = OpenApiContext::validateRequest($request);

Expand Down Expand Up @@ -286,7 +286,7 @@ public function postDummy(HttpResponse $response, HttpRequest $request): void
)]
public function putDummy(HttpResponse $response, HttpRequest $request): void
{
$data = JwtContext::requireRole(User::ROLE_ADMIN);
JwtContext::requireRole($request, User::ROLE_ADMIN);

$payload = OpenApiContext::validateRequest($request);

Expand Down
6 changes: 3 additions & 3 deletions src/Rest/Login.php
Original file line number Diff line number Diff line change
Expand Up @@ -101,16 +101,16 @@ public function post(HttpResponse $response, HttpRequest $request)
)]
public function refreshToken(HttpResponse $response, HttpRequest $request)
{
$result = JwtContext::requireAuthenticated(null, true);
JwtContext::requireAuthenticated($request);

$diff = ($result["exp"] - time()) / 60;
$diff = ($request->param("jwt.exp") - time()) / 60;

if ($diff > 5) {
throw new Error401Exception("You only can refresh the token 5 minutes before expire");
}

$users = Psr11::container()->get(UsersDBDataset::class);
$user = $users->getById(new HexUuidLiteral($result["data"]["userid"]));
$user = $users->getById(new HexUuidLiteral(JwtContext::getUserId()));

$metadata = JwtContext::createUserMetadata($user);

Expand Down
4 changes: 2 additions & 2 deletions src/Rest/SampleProtected.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class SampleProtected
)]
public function getPing(HttpResponse $response, HttpRequest $request)
{
JwtContext::requireAuthenticated();
JwtContext::requireAuthenticated($request);

$response->write([
'result' => 'pong'
Expand Down Expand Up @@ -84,7 +84,7 @@ public function getPing(HttpResponse $response, HttpRequest $request)
)]
public function getPingAdm(HttpResponse $response, HttpRequest $request)
{
JwtContext::requireRole('admin');
JwtContext::requireRole($request, 'admin');

$response->write([
'result' => 'pongadm'
Expand Down
28 changes: 13 additions & 15 deletions src/Util/FakeApiRequester.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,14 @@
use ByJG\Config\Exception\DependencyInjectionException;
use ByJG\Config\Exception\InvalidDateException;
use ByJG\Config\Exception\KeyNotFoundException;
use ByJG\RestServer\Exception\ClassNotFoundException;
use ByJG\RestServer\Exception\Error404Exception;
use ByJG\RestServer\Exception\Error405Exception;
use ByJG\RestServer\Exception\Error520Exception;
use ByJG\RestServer\Exception\InvalidClassException;
use ByJG\RestServer\Middleware\CorsMiddleware;
use ByJG\RestServer\Middleware\JwtMiddleware;
use ByJG\RestServer\MockRequestHandler;
use ByJG\RestServer\Route\OpenApiRouteList;
use ByJG\Util\Exception\MessageException;
use ByJG\Util\MockClient;
use ByJG\Util\Psr7\MessageException;
use ByJG\Util\Psr7\Response;
use KingPandaApi\Middleware\BlockMultiRequestsMiddleware;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\SimpleCache\InvalidArgumentException;
Expand All @@ -34,23 +32,23 @@ class FakeApiRequester extends AbstractRequester
/**
* @param RequestInterface $request
* @return Response|ResponseInterface
* @throws ClassNotFoundException
* @throws ConfigException
* @throws ConfigNotFoundException
* @throws DependencyInjectionException
* @throws Error404Exception
* @throws Error405Exception
* @throws Error520Exception
* @throws InvalidArgumentException
* @throws InvalidClassException
* @throws InvalidDateException
* @throws KeyNotFoundException
* @throws MessageException
* @throws ReflectionException
* @throws ConfigException
* @throws InvalidDateException
* @throws MessageException
*/
protected function handleRequest(RequestInterface $request)
{
$mock = MockRequestHandler::mock(Psr11::container()->get(OpenApiRouteList::class), $request);

$mock = new MockRequestHandler($request);
$mock->withMiddleware(Psr11::container()->get(JwtMiddleware::class));
$mock->withMiddleware(Psr11::container()->get(CorsMiddleware::class));
$mock->withRequestObject($request);
$mock->handle(Psr11::container()->get(OpenApiRouteList::class));

$httpClient = new MockClient($mock->getPsr7Response());
return $httpClient->sendRequest($request);
Expand Down
87 changes: 52 additions & 35 deletions src/Util/JwtContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,22 @@
use ByJG\Config\Exception\KeyNotFoundException;
use ByJG\RestServer\Exception\Error401Exception;
use ByJG\RestServer\Exception\Error403Exception;
use ByJG\RestServer\HttpRequest;
use ByJG\RestServer\Middleware\JwtMiddleware;
use ByJG\Util\JwtWrapper;
use Exception;
use Psr\SimpleCache\InvalidArgumentException;
use ReflectionException;
use RestReferenceArchitecture\Model\User;
use RestReferenceArchitecture\Psr11;

class JwtContext
{
protected static ?HttpRequest $request;

/**
* @param ?UserModel $user
* @return array
* @throws Error401Exception
* @throws Error403Exception
*/
public static function createUserMetadata(?UserModel $user): array
{
Expand All @@ -40,12 +43,13 @@ public static function createUserMetadata(?UserModel $user): array
/**
* @param array $properties
* @return mixed
* @throws ConfigException
* @throws ConfigNotFoundException
* @throws DependencyInjectionException
* @throws InvalidArgumentException
* @throws KeyNotFoundException
* @throws ConfigException
* @throws InvalidDateException
* @throws KeyNotFoundException
* @throws ReflectionException
*/
public static function createToken($properties = [])
{
Expand All @@ -54,51 +58,64 @@ public static function createToken($properties = [])
return $jwt->generateToken($jwtData);
}

public static function extractToken($token = null, $fullToken = false, $throwException = false)
/**
* @param HttpRequest $request
* @return void
* @throws Error401Exception
*/
public static function requireAuthenticated(HttpRequest $request): void
{
try {
$jwt = Psr11::container()->get(JwtWrapper::class);
$tokenInfo = json_decode(json_encode($jwt->extractData($token)), true);
if ($fullToken) {
return $tokenInfo;
} else {
return $tokenInfo['data'];
}
} catch (Exception $ex) {
if ($throwException) {
throw new Error401Exception($ex->getMessage());
} else {
return false;
}
self::$request = $request;
if ($request->param(JwtMiddleware::JWT_PARAM_PARSE_STATUS) !== JwtMiddleware::JWT_SUCCESS) {
throw new Error401Exception($request->param(JwtMiddleware::JWT_PARAM_PARSE_MESSAGE));
}
}

/**
* @param string|null $token
* @param bool $fullToken
* @return mixed
* @throws Error401Exception
* @throws InvalidArgumentException
*/
public static function requireAuthenticated($token = null, $fullToken = false)
public static function parseJwt(HttpRequest $request): void
{
return self::extractToken($token, $fullToken, true);
self::$request = $request;
}

/**
* @param $role
* @param string|null $token
* @return mixed
* @param HttpRequest $request
* @param string $role
* @return void
* @throws Error401Exception
* @throws Error403Exception
* @throws InvalidArgumentException
*/
public static function requireRole($role, $token = null)
public static function requireRole(HttpRequest $request, string $role): void
{
$data = self::requireAuthenticated($token);
if ($data['role'] !== $role) {
self::requireAuthenticated($request);
if (JwtContext::getRole() !== $role) {
throw new Error403Exception('Insufficient privileges');
}
return $data;
}

protected static function getRequestParam(string $value): ?string
{
if (isset(self::$request)) {
$data = (array)self::$request->param("jwt.data");
if (isset($data[$value])) {
return $data[$value];
}
}
return null;
}

public static function getUserId(): ?string
{
return self::getRequestParam("userid");
}

public static function getRole(): ?string
{
return self::getRequestParam("role");
}

public static function getName(): ?string
{
return self::getRequestParam("name");
}

}
Loading

0 comments on commit 6a6131d

Please sign in to comment.