Add basic auth support

This commit is contained in:
Alexander Pankratov 2024-04-15 17:09:24 +02:00
parent a2baf2de21
commit 8e93934e75
6 changed files with 94 additions and 70 deletions

View File

@ -2,6 +2,7 @@
# Check for outdated .env files # Check for outdated .env files
VERSION=1 VERSION=1
# See "ports" in docker-compose.yml.
SERVER_ADDRESS=0.0.0.0 SERVER_ADDRESS=0.0.0.0
SERVER_PORT=9503 SERVER_PORT=9503
@ -21,6 +22,10 @@ REQUESTS_BULK_INTERVAL=0.5
# 2) recreate container `docker-compose up -d` # 2) recreate container `docker-compose up -d`
IP_WHITELIST=127.0.0.1 IP_WHITELIST=127.0.0.1
# Allow requests from any IP with given user and password
# Example: {"myusername": "mySuperStrongPassword", "otherName": "otherPassword"}
PASSWORDS={}
# TELEGRAM CLIENT # TELEGRAM CLIENT
TELEGRAM_API_ID= TELEGRAM_API_ID=
TELEGRAM_API_HASH= TELEGRAM_API_HASH=

View File

@ -20,6 +20,10 @@ REQUESTS_BULK_INTERVAL=0.5
# Leave blanc, to allow requests from all IP (THIS WILL MAKE API UNSECURE!) # Leave blanc, to allow requests from all IP (THIS WILL MAKE API UNSECURE!)
IP_WHITELIST=127.0.0.1 IP_WHITELIST=127.0.0.1
# Allow requests from any IP with given user and password
# Example: {"myusername": "mySuperStrongPassword", "otherName": "otherPassword"}
PASSWORDS={}
# TELEGRAM CLIENT # TELEGRAM CLIENT
TELEGRAM_API_ID= TELEGRAM_API_ID=
TELEGRAM_API_HASH= TELEGRAM_API_HASH=

View File

@ -58,41 +58,35 @@ docker compose pull
docker compose up -d docker compose up -d
``` ```
## Security
Please be careful with settings, otherwise you can expose your telegram session and lose control.
Default settings allow to access API only from localhost/127.0.0.1.
.env settings:
- `IP_WHITELIST` - allow specific IP's to make requests without password.
- `PASSWORDS` - protect your api with basic auth.
Request with correct username and password overrides IP_WHITELIST.
If you specify password, then `IP_WHITELIST` is ignored
How to make requests with basic auth:
```shell
curl --user 'username:password' "http://127.0.0.1:9503/getSelf"
curl "http://username:password@127.0.0.1:9503/getSelf"
```
docker-compose.yml:
- `port` - port forwarding rules from host to docker container.
Remove 127.0.0.1 to listen all interfaces and forward all requests to container.
Make sure to use IP_WHITELIST and/or PASSWORDS settings to protect your account.
## Usage ## Usage
1. Run server/parser Access Telegram API with simple GET/POST requests.
``` Regular and application/json POST supported.
usage: php server.php [--help] [-a=|--address=127.0.0.1] [-p=|--port=9503] [-s=|--session=] [-e=|--env=.env] [--docker] It's recommended to use http_build_query, when using GET requests.
Options: **Rules:**
--help Show this message * All methods from MadelineProto supported: [Methods List](https://docs.madelineproto.xyz/API_docs/methods/)
* Url: `http://%address%:%port%/api[/%session%]/%class%.%method%/?%param%=%val%`
-a --address Server ip (optional) (default: 127.0.0.1) * <b>Important: api available only from ip in whitelist.</b>
To listen external connections use 0.0.0.0 and fill IP_WHITELIST in .env
-p --port Server port (optional) (default: 9503)
-s --session Name for session file (optional)
Multiple sessions can be specified: "--session=user --session=bot"
Each session is stored in `sessions/{$session}.madeline`.
Nested folders supported.
See README for more examples.
-e --env .env file name. (default: .env).
Helpful when need multiple instances with different settings
--docker Apply some settings for docker: add docker network to whitelist.
Also some options can be set in .env file (see .env.example)
```
1. Access Telegram API with simple GET/POST requests.
Regular and application/json POST supported.
It's recommended to use http_build_query, when using GET requests.
**Rules:**
* All methods from MadelineProto supported: [Methods List](https://docs.madelineproto.xyz/API_docs/methods/)
* Url: `http://%address%:%port%/api[/%session%]/%class%.%method%/?%param%=%val%`
* <b>Important: api available only from ip in whitelist.</b>
By default it is: `127.0.0.1` By default it is: `127.0.0.1`
You can add a client IP in .env file to `IP_WHITELIST` (separate with a comma) You can add a client IP in .env file to `IP_WHITELIST` (separate with a comma)
@ -100,22 +94,22 @@ docker compose pull
To allow connections from the internet, need to change ports in docker-compose.yml to `9503:9503` and recreate the container: `docker compose up -d`. To allow connections from the internet, need to change ports in docker-compose.yml to `9503:9503` and recreate the container: `docker compose up -d`.
This is very insecure, because this will open TAS port to anyone from the internet. This is very insecure, because this will open TAS port to anyone from the internet.
Only protection is the `IP_WHITELIST`, and there are no warranties that it will secure your accounts. Only protection is the `IP_WHITELIST`, and there are no warranties that it will secure your accounts.
* If method is inside class (messages, contacts and etc.) use '.' to separate class from method: * If method is inside class (messages, contacts and etc.) use '.' to separate class from method:
`http://127.0.0.1:9503/api/contacts.getContacts` `http://127.0.0.1:9503/api/contacts.getContacts`
* If method requires array of values, use any name of array, for example 'data': * If method requires array of values, use any name of array, for example 'data':
`?data[peer]=@xtrime&data[message]=Hello!`. Order of parameters does't matter in this case. `?data[peer]=@xtrime&data[message]=Hello!`. Order of parameters does't matter in this case.
* If method requires one or multiple separate parameters (not inside array) then pass parameters with any names but **in strict order**: * If method requires one or multiple separate parameters (not inside array) then pass parameters with any names but **in strict order**:
`http://127.0.0.1:9503/api/getInfo/?id=@xtrime` or `http://127.0.0.1:9503/api/getInfo/?abcd=@xtrime` works the same `http://127.0.0.1:9503/api/getInfo/?id=@xtrime` or `http://127.0.0.1:9503/api/getInfo/?abcd=@xtrime` works the same
**Examples:** **Examples:**
* get_info about channel/user: `http://127.0.0.1:9503/api/getInfo/?id=@xtrime` * get_info about channel/user: `http://127.0.0.1:9503/api/getInfo/?id=@xtrime`
* get_info about currect account: `http://127.0.0.1:9503/api/getSelf` * get_info about currect account: `http://127.0.0.1:9503/api/getSelf`
* repost: `http://127.0.0.1:9503/api/messages.forwardMessages/?data[from_peer]=@xtrime&data[to_peer]=@xtrime&data[id]=1234` * repost: `http://127.0.0.1:9503/api/messages.forwardMessages/?data[from_peer]=@xtrime&data[to_peer]=@xtrime&data[id]=1234`
* get messages from channel/user: `http://127.0.0.1:9503/api/getHistory/?data[peer]=@breakingmash&data[limit]=10` * get messages from channel/user: `http://127.0.0.1:9503/api/getHistory/?data[peer]=@breakingmash&data[limit]=10`
* get messages with text in HTML: `http://127.0.0.1:9503/api/getHistoryHtml/?data[peer]=@breakingmash&data[limit]=10` * get messages with text in HTML: `http://127.0.0.1:9503/api/getHistoryHtml/?data[peer]=@breakingmash&data[limit]=10`
* search: `http://127.0.0.1:9503/api/searchGlobal/?data[q]=Hello%20World&data[limit]=10` * search: `http://127.0.0.1:9503/api/searchGlobal/?data[q]=Hello%20World&data[limit]=10`
* sendMessage: `http://127.0.0.1:9503/api/sendMessage/?data[peer]=@xtrime&data[message]=Hello!` * sendMessage: `http://127.0.0.1:9503/api/sendMessage/?data[peer]=@xtrime&data[message]=Hello!`
* copy message from one channel to another (not repost): `http://127.0.0.1:9503/api/copyMessages/?data[from_peer]=@xtrime&data[to_peer]=@xtrime&data[id][0]=1` * copy message from one channel to another (not repost): `http://127.0.0.1:9503/api/copyMessages/?data[from_peer]=@xtrime&data[to_peer]=@xtrime&data[id][0]=1`
## Advanced features ## Advanced features
### Get events/updates ### Get events/updates

12
composer.lock generated
View File

@ -476,16 +476,16 @@
}, },
{ {
"name": "amphp/http", "name": "amphp/http",
"version": "v2.1.0", "version": "v2.1.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/amphp/http.git", "url": "https://github.com/amphp/http.git",
"reference": "9f3500bef4bb15cf41987f21136539c0a06555a3" "reference": "fe6b4dd50c1e70caf823092398074b5082e1d6da"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/amphp/http/zipball/9f3500bef4bb15cf41987f21136539c0a06555a3", "url": "https://api.github.com/repos/amphp/http/zipball/fe6b4dd50c1e70caf823092398074b5082e1d6da",
"reference": "9f3500bef4bb15cf41987f21136539c0a06555a3", "reference": "fe6b4dd50c1e70caf823092398074b5082e1d6da",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -528,7 +528,7 @@
"description": "Basic HTTP primitives which can be shared by servers and clients.", "description": "Basic HTTP primitives which can be shared by servers and clients.",
"support": { "support": {
"issues": "https://github.com/amphp/http/issues", "issues": "https://github.com/amphp/http/issues",
"source": "https://github.com/amphp/http/tree/v2.1.0" "source": "https://github.com/amphp/http/tree/v2.1.1"
}, },
"funding": [ "funding": [
{ {
@ -536,7 +536,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2023-08-22T19:50:46+00:00" "time": "2024-04-03T18:00:53+00:00"
}, },
{ {
"name": "amphp/http-client", "name": "amphp/http-client",

View File

@ -64,6 +64,7 @@ $settings = [
explode(',', (string)getenv('IP_WHITELIST')) explode(',', (string)getenv('IP_WHITELIST'))
) )
), ),
'passwords' => (array)json_decode((string)getenv('PASSWORDS'), true),
'bulk_interval' => (float)getenv('REQUESTS_BULK_INTERVAL') 'bulk_interval' => (float)getenv('REQUESTS_BULK_INTERVAL')
], ],
'health_check' => [ 'health_check' => [

View File

@ -13,23 +13,43 @@ class Authorization implements Middleware
{ {
private array $ipWhitelist; private array $ipWhitelist;
private int $selfIp; private int $selfIp;
/**
* @var array<string,string>
*/
private array $passwords;
public function __construct() public function __construct()
{ {
$this->ipWhitelist = (array)Config::getInstance()->get('api.ip_whitelist', []);
$this->selfIp = ip2long(getHostByName(php_uname('n'))); $this->selfIp = ip2long(getHostByName(php_uname('n')));
$this->ipWhitelist = (array)Config::getInstance()->get('api.ip_whitelist', []);
$this->passwords = Config::getInstance()->get('api.passwords', []);
if (!$this->ipWhitelist && !$this->passwords) {
throw new \InvalidArgumentException('API is unprotected! Please specify IP_WHITELIST or PASSWORD in .env.docker');
}
} }
public function handleRequest(Request $request, RequestHandler $requestHandler): Response public function handleRequest(Request $request, RequestHandler $requestHandler): Response
{ {
$host = explode(':', $request->getClient()->getRemoteAddress()->toString())[0]; [$host] = explode(':', $request->getClient()->getRemoteAddress()->toString(), 2);
if ($this->isIpAllowed($host)) {
$response = $requestHandler->handleRequest($request); if ($this->passwords) {
} else { $header = (string)$request->getHeader('Authorization');
$response = ErrorResponses::get(HttpStatus::FORBIDDEN, 'Your host is not allowed: ' . $host); if ($header) {
sscanf($header, "Basic %s", $encodedPassword);
[$username, $password] = explode(':', base64_decode($encodedPassword), 2);
if (array_key_exists($username, $this->passwords) && $this->passwords[$username] === $password) {
return $requestHandler->handleRequest($request);
}
} }
return $response; return ErrorResponses::get(HttpStatus::UNAUTHORIZED, 'Username or password is incorrect');
}
if ($this->isIpAllowed($host)) {
return $requestHandler->handleRequest($request);
}
return ErrorResponses::get(HttpStatus::UNAUTHORIZED, 'Your host is not allowed: ' . $host);
} }
private function isIpAllowed(string $host): bool private function isIpAllowed(string $host): bool