Add VK callback auth support and admin demotion
This commit is contained in:
35
deploy/nginx/vk.daemonlord.ru.conf
Normal file
35
deploy/nginx/vk.daemonlord.ru.conf
Normal file
@@ -0,0 +1,35 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name vk.daemonlord.ru;
|
||||
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name vk.daemonlord.ru;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/vk.daemonlord.ru/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/vk.daemonlord.ru/privkey.pem;
|
||||
|
||||
location = /vk/callback {
|
||||
proxy_pass http://127.0.0.1:8787/vk/callback;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
proxy_buffering off;
|
||||
}
|
||||
|
||||
location = /health {
|
||||
proxy_pass http://127.0.0.1:8787/health;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
}
|
||||
|
||||
location / {
|
||||
return 404;
|
||||
}
|
||||
}
|
||||
15
deploy/systemd/anabasis-vk-callback.service
Normal file
15
deploy/systemd/anabasis-vk-callback.service
Normal file
@@ -0,0 +1,15 @@
|
||||
[Unit]
|
||||
Description=Anabasis VK OAuth callback host
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
WorkingDirectory=/opt/anabasis-chat-remove
|
||||
ExecStart=/usr/bin/python3 /opt/anabasis-chat-remove/deploy/vk_callback_server.py --host 127.0.0.1 --port 8787 --quiet
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
User=www-data
|
||||
Group=www-data
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
81
deploy/vk-auth-setup.md
Normal file
81
deploy/vk-auth-setup.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# VK OAuth callback setup
|
||||
|
||||
The desktop app uses this redirect URI by default:
|
||||
|
||||
```text
|
||||
https://vk.daemonlord.ru/vk/callback
|
||||
```
|
||||
|
||||
The public HTTPS endpoint is expected to be handled by a reverse proxy. The
|
||||
backend callback host itself is plain HTTP on a non-standard local port:
|
||||
|
||||
```text
|
||||
http://127.0.0.1:8787/vk/callback
|
||||
```
|
||||
|
||||
## VK app settings
|
||||
|
||||
1. Open the VK developer dashboard.
|
||||
2. Select the standalone app with ID `54454043`.
|
||||
3. Make sure the app type is `Standalone application`.
|
||||
4. Add the exact redirect URI:
|
||||
|
||||
```text
|
||||
https://vk.daemonlord.ru/vk/callback
|
||||
```
|
||||
|
||||
If the redirect URI in the OAuth request does not exactly match the app settings,
|
||||
VK can return:
|
||||
|
||||
```json
|
||||
{"error":"invalid_request","error_description":"Security Error"}
|
||||
```
|
||||
|
||||
## Backend callback host
|
||||
|
||||
Run the local HTTP callback host:
|
||||
|
||||
```bash
|
||||
python deploy/vk_callback_server.py --host 127.0.0.1 --port 8787
|
||||
```
|
||||
|
||||
Health check:
|
||||
|
||||
```text
|
||||
http://127.0.0.1:8787/health
|
||||
```
|
||||
|
||||
Optional systemd unit:
|
||||
|
||||
```text
|
||||
deploy/systemd/anabasis-vk-callback.service
|
||||
```
|
||||
|
||||
Adjust `WorkingDirectory`, `ExecStart`, and `User` for the server path/user.
|
||||
|
||||
The callback page does not process or store the token. With implicit OAuth, VK
|
||||
puts `access_token` in the URL fragment. The desktop webview reads that final URL
|
||||
directly from the embedded browser.
|
||||
|
||||
## Reverse proxy
|
||||
|
||||
Use the nginx example:
|
||||
|
||||
```text
|
||||
deploy/nginx/vk.daemonlord.ru.conf
|
||||
```
|
||||
|
||||
It proxies:
|
||||
|
||||
```text
|
||||
https://vk.daemonlord.ru/vk/callback -> http://127.0.0.1:8787/vk/callback
|
||||
```
|
||||
|
||||
## Desktop app override
|
||||
|
||||
The app already defaults to `https://vk.daemonlord.ru/vk/callback`. To override it:
|
||||
|
||||
```powershell
|
||||
$env:ANABASIS_VK_REDIRECT_URI = "https://vk.daemonlord.ru/vk/callback"
|
||||
python main.py
|
||||
```
|
||||
46
deploy/vk-callback/index.html
Normal file
46
deploy/vk-callback/index.html
Normal file
@@ -0,0 +1,46 @@
|
||||
<!doctype html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>VK authorization</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
font-family: Arial, sans-serif;
|
||||
color: #1f2937;
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
main {
|
||||
width: min(520px, calc(100vw - 32px));
|
||||
padding: 24px;
|
||||
border: 1px solid #dbe3ef;
|
||||
border-radius: 8px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 12px 32px rgba(15, 23, 42, 0.08);
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0 0 12px;
|
||||
font-size: 20px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
font-size: 15px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<h1>Авторизация VK завершена</h1>
|
||||
<p>Вернитесь в приложение. Это окно можно закрыть после завершения входа.</p>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
78
deploy/vk_callback_server.py
Normal file
78
deploy/vk_callback_server.py
Normal file
@@ -0,0 +1,78 @@
|
||||
import argparse
|
||||
import os
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
DEFAULT_HOST = "127.0.0.1"
|
||||
DEFAULT_PORT = 8787
|
||||
CALLBACK_PATH = "/vk/callback"
|
||||
HEALTH_PATH = "/health"
|
||||
|
||||
|
||||
def _read_callback_html():
|
||||
html_path = Path(__file__).resolve().parent / "vk-callback" / "index.html"
|
||||
return html_path.read_bytes()
|
||||
|
||||
|
||||
class VkCallbackHandler(BaseHTTPRequestHandler):
|
||||
server_version = "AnabasisVkCallback/1.0"
|
||||
|
||||
def do_GET(self):
|
||||
path = self.path.split("?", 1)[0]
|
||||
if path == HEALTH_PATH:
|
||||
self._send_text(200, "ok\n")
|
||||
return
|
||||
if path in ("/", CALLBACK_PATH):
|
||||
self._send_html(200, _read_callback_html())
|
||||
return
|
||||
self._send_text(404, "not found\n")
|
||||
|
||||
def log_message(self, fmt, *args):
|
||||
if getattr(self.server, "quiet", False):
|
||||
return
|
||||
super().log_message(fmt, *args)
|
||||
|
||||
def _send_html(self, status, body):
|
||||
self.send_response(status)
|
||||
self.send_header("Content-Type", "text/html; charset=utf-8")
|
||||
self.send_header("Cache-Control", "no-store")
|
||||
self.send_header("Content-Length", str(len(body)))
|
||||
self.end_headers()
|
||||
self.wfile.write(body)
|
||||
|
||||
def _send_text(self, status, text):
|
||||
body = text.encode("utf-8")
|
||||
self.send_response(status)
|
||||
self.send_header("Content-Type", "text/plain; charset=utf-8")
|
||||
self.send_header("Cache-Control", "no-store")
|
||||
self.send_header("Content-Length", str(len(body)))
|
||||
self.end_headers()
|
||||
self.wfile.write(body)
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(description="Anabasis VK OAuth callback host")
|
||||
parser.add_argument("--host", default=os.getenv("ANABASIS_VK_CALLBACK_HOST", DEFAULT_HOST))
|
||||
parser.add_argument("--port", type=int, default=int(os.getenv("ANABASIS_VK_CALLBACK_PORT", DEFAULT_PORT)))
|
||||
parser.add_argument("--quiet", action="store_true")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
server = ThreadingHTTPServer((args.host, args.port), VkCallbackHandler)
|
||||
server.quiet = args.quiet
|
||||
print(f"VK callback server listening on http://{args.host}:{args.port}{CALLBACK_PATH}")
|
||||
print(f"Health check: http://{args.host}:{args.port}{HEALTH_PATH}")
|
||||
try:
|
||||
server.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
finally:
|
||||
server.server_close()
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user