Implement session management with login functionality and add admin credentials to configuration

This commit is contained in:
wangqifan 2025-10-28 15:49:52 +08:00
parent ac7be6ee02
commit 657188dc27
4 changed files with 84 additions and 3 deletions

View File

@ -19,4 +19,8 @@ LOG_LEVEL=INFO
# 端口数==条数 从172.30.168.2开始 # 端口数==条数 从172.30.168.2开始
PORT_NUM=10 PORT_NUM=10
MAX_ONLINE=10000 MAX_ONLINE=10000
ADMIN_USERNAME=heping
ADMIN_PASSWORD=He_Ping551
SESSION_SECRET=sinehiunsdkfi

View File

@ -17,6 +17,10 @@ class Settings(BaseModel):
log_level: str = os.getenv("LOG_LEVEL", "INFO") log_level: str = os.getenv("LOG_LEVEL", "INFO")
port_num: int = int(os.getenv("PORT_NUM", 3)) port_num: int = int(os.getenv("PORT_NUM", 3))
max_online: int = int(os.getenv("MAX_ONLINE", 3)) max_online: int = int(os.getenv("MAX_ONLINE", 3))
# 前台登录配置
admin_username: str = os.getenv("ADMIN_USERNAME", "admin")
admin_password: str = os.getenv("ADMIN_PASSWORD", "admin")
session_secret: str = os.getenv("SESSION_SECRET", "change-me-please")
settings = Settings() settings = Settings()

View File

@ -1,15 +1,19 @@
from fastapi import FastAPI, Request from fastapi import FastAPI, Request, Form
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from fastapi.responses import HTMLResponse from fastapi.responses import HTMLResponse, RedirectResponse
from .config import settings from .config import settings
from .routers.proxy import router as proxy_router from .routers.proxy import router as proxy_router
from starlette.middleware.sessions import SessionMiddleware
app = FastAPI(title="EIP Rotation Service") app = FastAPI(title="EIP Rotation Service")
# 设置模板目录 # 设置模板目录
templates = Jinja2Templates(directory="app/templates") templates = Jinja2Templates(directory="app/templates")
# 会话中间件(用于简单登录态)
app.add_middleware(SessionMiddleware, secret_key=settings.session_secret)
@app.get("/health") @app.get("/health")
def health_check(): def health_check():
@ -19,6 +23,8 @@ def health_check():
@app.get("/", response_class=HTMLResponse) @app.get("/", response_class=HTMLResponse)
async def index(request: Request, id: str = None): async def index(request: Request, id: str = None):
"""主页 - IP轮换控制面板""" """主页 - IP轮换控制面板"""
if not request.session.get("authenticated"):
return RedirectResponse(url="/login", status_code=302)
return templates.TemplateResponse("index.html", { return templates.TemplateResponse("index.html", {
"request": request, "request": request,
"client_id": id or "1" # 默认ID为1 "client_id": id or "1" # 默认ID为1
@ -28,3 +34,23 @@ async def index(request: Request, id: str = None):
app.include_router(proxy_router, prefix="/proxy", tags=["proxy"]) app.include_router(proxy_router, prefix="/proxy", tags=["proxy"])
@app.get("/login", response_class=HTMLResponse)
async def login_page(request: Request):
return templates.TemplateResponse("login.html", {"request": request, "error": None})
@app.post("/login")
async def login_submit(request: Request, username: str = Form(...), password: str = Form(...)):
if username == settings.admin_username and password == settings.admin_password:
request.session["authenticated"] = True
return RedirectResponse(url="/", status_code=302)
# 登录失败,回到登录页并提示
return templates.TemplateResponse("login.html", {"request": request, "error": "用户名或密码错误"}, status_code=401)
@app.get("/logout")
async def logout(request: Request):
request.session.clear()
return RedirectResponse(url="/login", status_code=302)

47
app/templates/login.html Normal file
View File

@ -0,0 +1,47 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>登录 - EIP 控制面板</title>
<style>
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; display: flex; align-items: center; justify-content: center; margin: 0; }
.card { width: 360px; background: #fff; padding: 32px; border-radius: 16px; box-shadow: 0 20px 40px rgba(0,0,0,0.12); }
h1 { margin: 0 0 8px; font-weight: 400; text-align: center; }
p { margin: 0 0 24px; color: #666; text-align: center; }
label { display: block; font-weight: 600; margin: 12px 0 8px; color: #333; }
input[type="text"], input[type="password"] { width: 100%; padding: 10px 12px; border: 1px solid #ddd; border-radius: 8px; font-size: 14px; }
.btn { width: 100%; margin-top: 16px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: #fff; border: none; padding: 10px 14px; border-radius: 10px; cursor: pointer; font-size: 15px; }
.error { margin-top: 12px; background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; padding: 10px; border-radius: 8px; display: {{ 'block' if error else 'none' }}; }
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
const u = document.getElementById('username');
if (u) u.focus();
});
</script>
<script>
(function(){
if (window.top !== window.self) {
window.top.location = window.location;
}
})();
</script>
</head>
<body>
<div class="card">
<h1>登录</h1>
<p>进入 EIP IP 轮换控制面板</p>
<form method="post" action="/login">
<label for="username">用户名</label>
<input id="username" name="username" type="text" autocomplete="username" required>
<label for="password">密码</label>
<input id="password" name="password" type="password" autocomplete="current-password" required>
<button class="btn" type="submit">登录</button>
</form>
<div class="error">{{ error }}</div>
</div>
</body>
</html>