feat(private): initialize private API
This commit is contained in:
@@ -20,6 +20,8 @@ HOST="0.0.0.0"
|
|||||||
PORT=8000
|
PORT=8000
|
||||||
# 调试模式,生产环境请设置为 false
|
# 调试模式,生产环境请设置为 false
|
||||||
DEBUG=false
|
DEBUG=false
|
||||||
|
# 私有 API 密钥,用于前后端 API 调用,使用 openssl rand -hex 32 生成
|
||||||
|
PRIVATE_API_SECRET="your_private_api_secret_here"
|
||||||
|
|
||||||
# osu! 登录设置
|
# osu! 登录设置
|
||||||
OSU_CLIENT_ID=5 # lazer client ID
|
OSU_CLIENT_ID=5 # lazer client ID
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ docker-compose -f docker-compose-osurx.yml up -d
|
|||||||
| `HOST` | 服务器监听地址 | `0.0.0.0` |
|
| `HOST` | 服务器监听地址 | `0.0.0.0` |
|
||||||
| `PORT` | 服务器监听端口 | `8000` |
|
| `PORT` | 服务器监听端口 | `8000` |
|
||||||
| `DEBUG` | 调试模式 | `false` |
|
| `DEBUG` | 调试模式 | `false` |
|
||||||
|
| `PRIVATE_API_SECRET` | 私有 API 密钥,用于前后端 API 调用 | `your_private_api_secret_here` |
|
||||||
|
|
||||||
### OAuth 设置
|
### OAuth 设置
|
||||||
| 变量名 | 描述 | 默认值 |
|
| 变量名 | 描述 | 默认值 |
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ class Settings(BaseSettings):
|
|||||||
host: str = "0.0.0.0"
|
host: str = "0.0.0.0"
|
||||||
port: int = 8000
|
port: int = 8000
|
||||||
debug: bool = False
|
debug: bool = False
|
||||||
|
private_api_secret: str = "your_private_api_secret_here"
|
||||||
|
|
||||||
# SignalR 设置
|
# SignalR 设置
|
||||||
signalr_negotiate_timeout: int = 30
|
signalr_negotiate_timeout: int = 30
|
||||||
|
|||||||
@@ -4,6 +4,13 @@ from app.signalr import signalr_router as signalr_router
|
|||||||
|
|
||||||
from .auth import router as auth_router
|
from .auth import router as auth_router
|
||||||
from .fetcher import fetcher_router as fetcher_router
|
from .fetcher import fetcher_router as fetcher_router
|
||||||
|
from .private import private_router as private_router
|
||||||
from .v2 import api_v2_router as api_v2_router
|
from .v2 import api_v2_router as api_v2_router
|
||||||
|
|
||||||
__all__ = ["api_v2_router", "auth_router", "fetcher_router", "signalr_router"]
|
__all__ = [
|
||||||
|
"api_v2_router",
|
||||||
|
"auth_router",
|
||||||
|
"fetcher_router",
|
||||||
|
"private_router",
|
||||||
|
"signalr_router",
|
||||||
|
]
|
||||||
|
|||||||
7
app/router/private/__init__.py
Normal file
7
app/router/private/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from .router import router as private_router
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"private_router",
|
||||||
|
]
|
||||||
39
app/router/private/router.py
Normal file
39
app/router/private/router.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
import hmac
|
||||||
|
import time
|
||||||
|
|
||||||
|
from app.config import settings
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Depends, Header, HTTPException, Request
|
||||||
|
|
||||||
|
|
||||||
|
async def verify_signature(
|
||||||
|
request: Request,
|
||||||
|
ts: int = Header(..., alias="X-Timestamp"),
|
||||||
|
nonce: str = Header(..., alias="X-Nonce"),
|
||||||
|
signature: str = Header(..., alias="X-Signature"),
|
||||||
|
):
|
||||||
|
path = request.url.path
|
||||||
|
data = await request.body()
|
||||||
|
body = data.decode("utf-8")
|
||||||
|
|
||||||
|
py_ts = ts // 1000
|
||||||
|
if abs(time.time() - py_ts) > 30:
|
||||||
|
raise HTTPException(status_code=403, detail="Invalid timestamp")
|
||||||
|
|
||||||
|
payload = f"{path}|{body}|{ts}|{nonce}"
|
||||||
|
expected_sig = hmac.new(
|
||||||
|
settings.private_api_secret.encode(), payload.encode(), hashlib.sha256
|
||||||
|
).hexdigest()
|
||||||
|
|
||||||
|
if not hmac.compare_digest(expected_sig, signature):
|
||||||
|
raise HTTPException(status_code=403, detail="Invalid signature")
|
||||||
|
|
||||||
|
|
||||||
|
router = APIRouter(
|
||||||
|
prefix="/api/private",
|
||||||
|
dependencies=[Depends(verify_signature)],
|
||||||
|
include_in_schema=False,
|
||||||
|
)
|
||||||
7
main.py
7
main.py
@@ -12,6 +12,7 @@ from app.router import (
|
|||||||
api_v2_router,
|
api_v2_router,
|
||||||
auth_router,
|
auth_router,
|
||||||
fetcher_router,
|
fetcher_router,
|
||||||
|
private_router,
|
||||||
signalr_router,
|
signalr_router,
|
||||||
)
|
)
|
||||||
from app.service.daily_challenge import daily_challenge_job
|
from app.service.daily_challenge import daily_challenge_job
|
||||||
@@ -39,6 +40,7 @@ app.include_router(api_v2_router)
|
|||||||
app.include_router(signalr_router)
|
app.include_router(signalr_router)
|
||||||
app.include_router(fetcher_router)
|
app.include_router(fetcher_router)
|
||||||
app.include_router(auth_router)
|
app.include_router(auth_router)
|
||||||
|
app.include_router(private_router)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/")
|
@app.get("/")
|
||||||
@@ -63,6 +65,11 @@ if settings.osu_web_client_secret == "your_osu_web_client_secret_here":
|
|||||||
"osu_web_client_secret is unset. Your server is unsafe. "
|
"osu_web_client_secret is unset. Your server is unsafe. "
|
||||||
"Use this command to generate: openssl rand -hex 40"
|
"Use this command to generate: openssl rand -hex 40"
|
||||||
)
|
)
|
||||||
|
if settings.private_api_secret == "your_private_api_secret_here":
|
||||||
|
logger.warning(
|
||||||
|
"private_api_secret is unset. Your server is unsafe. "
|
||||||
|
"Use this command to generate: openssl rand -hex 32"
|
||||||
|
)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import uvicorn
|
import uvicorn
|
||||||
|
|||||||
Reference in New Issue
Block a user