mirror of
https://github.com/danog/TelegramApiServer.git
synced 2024-11-26 20:04:45 +01:00
Authorization middleware
This commit is contained in:
parent
1844a1665f
commit
1b72ce8cb9
12
README.md
12
README.md
@ -11,8 +11,8 @@ Fast, simple, async php telegram api server:
|
||||
* Fast async server
|
||||
* Full access to telegram api: bot and user
|
||||
|
||||
**Example Architecture**
|
||||
![Proposed Architecture](https://hsto.org/webt/j-/ob/ky/j-obkye1dv68ngsrgi12qevutra.png)
|
||||
**Architecture Example**
|
||||
![Architecture Example](https://hsto.org/webt/j-/ob/ky/j-obkye1dv68ngsrgi12qevutra.png)
|
||||
|
||||
**Installation**
|
||||
|
||||
@ -92,14 +92,6 @@ Fast, simple, async php telegram api server:
|
||||
* sendMessage: `http://127.0.0.1:9503/api/sendMessage/?data[peer]=@xtrime&data[message]=Hello!`
|
||||
* copy message from one channel to other (not repost): `http://127.0.0.1:9503/api/copyMessages/?data[from_peer]=@xtrime&data[to_peer]=@xtrime&data[id][0]=1`
|
||||
|
||||
**INPORTANT SECURITY NOTICE!**
|
||||
|
||||
Do not use `SERVER_ADDRESS=0.0.0.0` in version 1.5.0+, because websocket EventHandler endpoint currently not use `IP_WHITELIST` option.
|
||||
This means, anyone from internet can listen your updates via websocket in this mode.
|
||||
|
||||
Use only default setting: `SERVER_ADDRESS=127.0.0.1`, or protect your app with external firewall.
|
||||
|
||||
This security issue will be fixed in one of next releases in January 2020.
|
||||
|
||||
**Contacts**
|
||||
|
||||
|
@ -23,6 +23,7 @@ $options = [
|
||||
Amp\Loop::run(function () use($options) {
|
||||
echo "Connecting to: {$options['url']}" . PHP_EOL;
|
||||
|
||||
try {
|
||||
/** @var Connection $connection */
|
||||
$connection = yield connect($options['url']);
|
||||
|
||||
@ -36,4 +37,8 @@ Amp\Loop::run(function () use($options) {
|
||||
$payload = yield $message->buffer();
|
||||
printf("Received event: %s\n", $payload);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
printf("Error: %s\n", $e->getMessage());
|
||||
}
|
||||
|
||||
});
|
@ -5,7 +5,7 @@ chdir(__DIR__);
|
||||
require_once __DIR__ . '/bootstrap.php';
|
||||
|
||||
if (PHP_SAPI !== 'cli') {
|
||||
throw new \Exception('Start in CLI');
|
||||
throw new \RuntimeException('Start in CLI');
|
||||
}
|
||||
|
||||
$shortopts = 'a::p::s::';
|
||||
@ -58,4 +58,4 @@ foreach ($options['session'] as $session) {
|
||||
}
|
||||
|
||||
$client = new TelegramApiServer\Client($sessionFiles);
|
||||
new TelegramApiServer\Server($client, $options);
|
||||
new TelegramApiServer\Server\Server($client, $options);
|
@ -9,18 +9,18 @@ use Amp\Http\Server\Response;
|
||||
use Amp\Http\Server\Router;
|
||||
use Amp\Promise;
|
||||
use TelegramApiServer\Client;
|
||||
use TelegramApiServer\Config;
|
||||
use TelegramApiServer\ClientCustomMethods;
|
||||
|
||||
class ApiController
|
||||
{
|
||||
public const JSON_HEADER = ['Content-Type'=>'application/json;charset=utf-8'];
|
||||
|
||||
private Client $client;
|
||||
private array $ipWhiteList;
|
||||
public array $page = [
|
||||
'headers' => [
|
||||
'Content-Type'=>'application/json;charset=utf-8',
|
||||
self::JSON_HEADER,
|
||||
],
|
||||
'success' => 0,
|
||||
'success' => false,
|
||||
'errors' => [],
|
||||
'code' => 200,
|
||||
'response' => null,
|
||||
@ -52,7 +52,6 @@ class ApiController
|
||||
*/
|
||||
public function __construct(Client $client)
|
||||
{
|
||||
$this->ipWhiteList = (array) Config::getInstance()->get('api.ip_whitelist', []);
|
||||
$this->client = $client;
|
||||
}
|
||||
|
||||
@ -137,9 +136,6 @@ class ApiController
|
||||
}
|
||||
|
||||
try {
|
||||
if (!in_array($request->getClient()->getRemoteAddress()->getHost(), $this->ipWhiteList, true)) {
|
||||
throw new \Exception('Requests from your IP is forbidden');
|
||||
}
|
||||
$this->page['response'] = $this->callApi();
|
||||
|
||||
if ($this->page['response'] instanceof Promise) {
|
||||
@ -191,16 +187,21 @@ class ApiController
|
||||
*/
|
||||
private function setError(\Throwable $e): self
|
||||
{
|
||||
$errorCode = $e->getCode();
|
||||
if ($errorCode >= 400 && $errorCode < 500) {
|
||||
$this->setPageCode($errorCode);
|
||||
} else {
|
||||
$this->setPageCode(400);
|
||||
}
|
||||
|
||||
$this->page['errors'][] = [
|
||||
'code' => $e->getCode(),
|
||||
'code' => $errorCode,
|
||||
'message' => $e->getMessage(),
|
||||
];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Кодирует ответ в нужный формат: json
|
||||
*
|
||||
@ -223,7 +224,7 @@ class ApiController
|
||||
'response' => $this->page['response'],
|
||||
];
|
||||
if (!$data['errors']) {
|
||||
$data['success'] = 1;
|
||||
$data['success'] = true;
|
||||
}
|
||||
|
||||
$result = json_encode(
|
||||
|
@ -32,7 +32,6 @@ class EventsController extends Websocket
|
||||
}
|
||||
} catch (\Throwable $e){
|
||||
$response->setStatus(400);
|
||||
$response->setBody($e->getMessage());
|
||||
}
|
||||
|
||||
return new Success($response);
|
||||
@ -65,7 +64,18 @@ class EventsController extends Websocket
|
||||
return;
|
||||
}
|
||||
$update = [$session => $update];
|
||||
$this->multicast(json_encode($update, JSON_INVALID_UTF8_IGNORE | JSON_UNESCAPED_UNICODE), [$clientId]);
|
||||
|
||||
$this->multicast(
|
||||
json_encode(
|
||||
$update,
|
||||
JSON_THROW_ON_ERROR |
|
||||
JSON_INVALID_UTF8_SUBSTITUTE |
|
||||
JSON_PRETTY_PRINT |
|
||||
JSON_UNESCAPED_SLASHES |
|
||||
JSON_UNESCAPED_UNICODE
|
||||
),
|
||||
[$clientId]
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
43
src/Server/Authorization.php
Normal file
43
src/Server/Authorization.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace TelegramApiServer\Server;
|
||||
|
||||
use Amp\Http\Server\Middleware;
|
||||
use Amp\Http\Server\Request;
|
||||
use Amp\Http\Server\RequestHandler;
|
||||
use Amp\Http\Status;
|
||||
use Amp\Promise;
|
||||
use TelegramApiServer\Config;
|
||||
use function Amp\call;
|
||||
|
||||
class Authorization implements Middleware
|
||||
{
|
||||
private array $ipWhitelist;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->ipWhitelist = (array) Config::getInstance()->get('api.ip_whitelist', []);
|
||||
}
|
||||
|
||||
public function handleRequest(Request $request, RequestHandler $next): Promise {
|
||||
return call(function () use ($request, $next) {
|
||||
|
||||
$host = $request->getClient()->getRemoteAddress()->getHost();
|
||||
if ($this->isIpAllowed($host)) {
|
||||
$response = yield $next->handleRequest($request);
|
||||
} else {
|
||||
$response = ErrorResponses::get(Status::FORBIDDEN, 'Your host is not allowed');
|
||||
}
|
||||
|
||||
return $response;
|
||||
});
|
||||
}
|
||||
|
||||
private function isIpAllowed(string $host): bool
|
||||
{
|
||||
if (!in_array($host, $this->ipWhitelist, true)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
36
src/Server/ErrorResponses.php
Normal file
36
src/Server/ErrorResponses.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace TelegramApiServer\Server;
|
||||
|
||||
use Amp\Http\Server\Response;
|
||||
use TelegramApiServer\Controllers\ApiController;
|
||||
|
||||
class ErrorResponses
|
||||
{
|
||||
/**
|
||||
* @param int $status
|
||||
* @param string|array $message
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public static function get(int $status, $message): Response
|
||||
{
|
||||
return new Response(
|
||||
$status,
|
||||
ApiController::JSON_HEADER,
|
||||
json_encode(
|
||||
[
|
||||
'success' => false,
|
||||
'errors' => [
|
||||
[
|
||||
'code' => $status,
|
||||
'message' => $message,
|
||||
]
|
||||
]
|
||||
],
|
||||
JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT
|
||||
) . "\n"
|
||||
);
|
||||
}
|
||||
|
||||
}
|
52
src/Server/Router.php
Normal file
52
src/Server/Router.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace TelegramApiServer\Server;
|
||||
|
||||
use Amp\Http\Server\Request;
|
||||
use Amp\Http\Server\RequestHandler\CallableRequestHandler;
|
||||
use TelegramApiServer\Client;
|
||||
use TelegramApiServer\Controllers\ApiController;
|
||||
use TelegramApiServer\Controllers\EventsController;
|
||||
use Amp\Http\Status;
|
||||
use function Amp\Http\Server\Middleware\stack;
|
||||
|
||||
class Router
|
||||
{
|
||||
private \Amp\Http\Server\Router $router;
|
||||
|
||||
public function __construct(Client $client)
|
||||
{
|
||||
$this->router = new \Amp\Http\Server\Router();
|
||||
$this->setRoutes($client);
|
||||
$this->setFallback();
|
||||
}
|
||||
|
||||
public function getRouter(): \Amp\Http\Server\Router
|
||||
{
|
||||
return $this->router;
|
||||
}
|
||||
|
||||
private function setFallback(): void
|
||||
{
|
||||
$this->router->setFallback(new CallableRequestHandler(static function (Request $request) {
|
||||
return ErrorResponses::get(Status::NOT_FOUND, 'Path not found');
|
||||
}));
|
||||
}
|
||||
|
||||
private function setRoutes($client): void
|
||||
{
|
||||
$authorization = new Authorization();
|
||||
$apiHandler = stack(ApiController::getRouterCallback($client), $authorization);
|
||||
$eventsHandler = stack(EventsController::getRouterCallback($client), $authorization);
|
||||
|
||||
foreach (['GET', 'POST'] as $method) {
|
||||
$this->router->addRoute($method, '/api/{session}/{method}[/]', $apiHandler);
|
||||
$this->router->addRoute($method, '/api/{method}[/]', $apiHandler);
|
||||
}
|
||||
|
||||
$this->router->addRoute('GET', '/events/{session}[/]', $eventsHandler);
|
||||
$this->router->addRoute('GET', '/events[/]', $eventsHandler);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,14 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace TelegramApiServer;
|
||||
namespace TelegramApiServer\Server;
|
||||
|
||||
use Amp;
|
||||
use Amp\Http\Server\RequestHandler\CallableRequestHandler;
|
||||
use Amp\Http\Server\Request;
|
||||
use Amp\Http\Server\Response;
|
||||
use Psr\Log\LogLevel;
|
||||
use TelegramApiServer\Controllers\ApiController;
|
||||
use TelegramApiServer\Controllers\EventsController;
|
||||
use TelegramApiServer\Client;
|
||||
use TelegramApiServer\Config;
|
||||
use TelegramApiServer\Logger;
|
||||
|
||||
class Server
|
||||
{
|
||||
@ -22,7 +19,7 @@ class Server
|
||||
Amp\Loop::run(function () use ($client, $options) {
|
||||
$server = new Amp\Http\Server\Server(
|
||||
$this->getServerAddresses(static::getConfig($options)),
|
||||
static::getRouter($client),
|
||||
(new Router($client))->getRouter(),
|
||||
Logger::getInstance(),
|
||||
(new Amp\Http\Server\Options())
|
||||
->withCompression()
|
||||
@ -42,42 +39,12 @@ class Server
|
||||
];
|
||||
}
|
||||
|
||||
private static function getRouter(Client $client): Amp\Http\Server\Router
|
||||
{
|
||||
$router = new Amp\Http\Server\Router();
|
||||
foreach (['GET', 'POST'] as $method) {
|
||||
$router->addRoute($method, '/api/{session}/{method}[/]', ApiController::getRouterCallback($client));
|
||||
$router->addRoute($method, '/api/{method}[/]', ApiController::getRouterCallback($client));
|
||||
$router->addRoute($method, '/events/{session}[/]', EventsController::getRouterCallback($client));
|
||||
$router->addRoute($method, '/events[/]', EventsController::getRouterCallback($client));
|
||||
}
|
||||
|
||||
$router->setFallback(new CallableRequestHandler(static function (Request $request) {
|
||||
return new Response(
|
||||
Amp\Http\Status::NOT_FOUND,
|
||||
[ 'Content-Type'=>'application/json;charset=utf-8'],
|
||||
json_encode(
|
||||
[
|
||||
'success' => 0,
|
||||
'errors' => [
|
||||
[
|
||||
'code' => 404,
|
||||
'message' => 'Path not found',
|
||||
]
|
||||
]
|
||||
],
|
||||
JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT
|
||||
) . "\n"
|
||||
);
|
||||
}));
|
||||
|
||||
return $router;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the server gracefully when SIGINT is received.
|
||||
* This is technically optional, but it is best to call Server::stop().
|
||||
*
|
||||
* @param Amp\Http\Server\Server $server
|
||||
*
|
||||
* @throws Amp\Loop\UnsupportedFeatureException
|
||||
*/
|
||||
private static function registerShutdown(Amp\Http\Server\Server $server)
|
Loading…
Reference in New Issue
Block a user