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=

108
README.md
View File

@ -58,64 +58,58 @@ docker compose pull
docker compose up -d docker compose up -d
``` ```
## Usage ## Security
1. Run server/parser 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.
usage: php server.php [--help] [-a=|--address=127.0.0.1] [-p=|--port=9503] [-s=|--session=] [-e=|--env=.env] [--docker]
Options:
--help Show this message
-a --address Server ip (optional) (default: 127.0.0.1)
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`
You can add a client IP in .env file to `IP_WHITELIST` (separate with a comma)
In docker version by default api available only from localhost (127.0.0.1).
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.
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:
`http://127.0.0.1:9503/api/contacts.getContacts`
* 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.
* 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
**Examples:** .env settings:
* get_info about channel/user: `http://127.0.0.1:9503/api/getInfo/?id=@xtrime` - `IP_WHITELIST` - allow specific IP's to make requests without password.
* get_info about currect account: `http://127.0.0.1:9503/api/getSelf` - `PASSWORDS` - protect your api with basic auth.
* repost: `http://127.0.0.1:9503/api/messages.forwardMessages/?data[from_peer]=@xtrime&data[to_peer]=@xtrime&data[id]=1234` Request with correct username and password overrides IP_WHITELIST.
* get messages from channel/user: `http://127.0.0.1:9503/api/getHistory/?data[peer]=@breakingmash&data[limit]=10` If you specify password, then `IP_WHITELIST` is ignored
* get messages with text in HTML: `http://127.0.0.1:9503/api/getHistoryHtml/?data[peer]=@breakingmash&data[limit]=10` How to make requests with basic auth:
* search: `http://127.0.0.1:9503/api/searchGlobal/?data[q]=Hello%20World&data[limit]=10` ```shell
* sendMessage: `http://127.0.0.1:9503/api/sendMessage/?data[peer]=@xtrime&data[message]=Hello!` curl --user 'username:password' "http://127.0.0.1:9503/getSelf"
* 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` 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
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`
You can add a client IP in .env file to `IP_WHITELIST` (separate with a comma)
In docker version by default api available only from localhost (127.0.0.1).
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.
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:
`http://127.0.0.1:9503/api/contacts.getContacts`
* 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.
* 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
**Examples:**
* 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`
* 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 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`
* 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`
## 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 ErrorResponses::get(HttpStatus::UNAUTHORIZED, 'Username or password is incorrect');
} }
return $response; 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