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
VERSION=1
# See "ports" in docker-compose.yml.
SERVER_ADDRESS=0.0.0.0
SERVER_PORT=9503
@ -21,6 +22,10 @@ REQUESTS_BULK_INTERVAL=0.5
# 2) recreate container `docker-compose up -d`
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_API_ID=
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!)
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_API_ID=
TELEGRAM_API_HASH=

108
README.md
View File

@ -58,64 +58,58 @@ docker compose pull
docker compose up -d
```
## Usage
1. Run server/parser
```
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
## 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.
**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`
.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
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
### Get events/updates

12
composer.lock generated
View File

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

View File

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

View File

@ -13,23 +13,43 @@ class Authorization implements Middleware
{
private array $ipWhitelist;
private int $selfIp;
/**
* @var array<string,string>
*/
private array $passwords;
public function __construct()
{
$this->ipWhitelist = (array)Config::getInstance()->get('api.ip_whitelist', []);
$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
{
$host = explode(':', $request->getClient()->getRemoteAddress()->toString())[0];
if ($this->isIpAllowed($host)) {
$response = $requestHandler->handleRequest($request);
} else {
$response = ErrorResponses::get(HttpStatus::FORBIDDEN, 'Your host is not allowed: ' . $host);
[$host] = explode(':', $request->getClient()->getRemoteAddress()->toString(), 2);
if ($this->passwords) {
$header = (string)$request->getHeader('Authorization');
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