Files
gangyan/scripts/overleaf-deploy/overleaf-register.py

129 lines
4.8 KiB
Python
Raw Normal View History

"""
Overleaf 自助注册服务
提供简单的注册页面用户输入邮箱和密码即可注册
内部通过管理员账号调用 Overleaf API 创建用户
"""
from fastapi import FastAPI, Request, Form
from fastapi.responses import HTMLResponse
import requests
import os
app = FastAPI()
OL_URL = os.getenv("OL_INSTANCE", "http://overleaf")
OL_ADMIN_EMAIL = os.getenv("OL_ADMIN_EMAIL", "")
OL_ADMIN_PASSWORD = os.getenv("OL_ADMIN_PASSWORD", "")
REGISTER_HTML = """
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>注册 - LaTeX 论文编辑器</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, sans-serif; background: #f5f5f5; display: flex; justify-content: center; align-items: center; min-height: 100vh; }
.card { background: #fff; border-radius: 12px; padding: 40px; width: 400px; box-shadow: 0 4px 20px rgba(0,0,0,0.1); }
h2 { text-align: center; margin-bottom: 8px; color: #333; }
.sub { text-align: center; color: #999; font-size: 14px; margin-bottom: 30px; }
label { display: block; margin-bottom: 6px; color: #555; font-size: 14px; }
input { width: 100%; padding: 10px 14px; border: 1px solid #ddd; border-radius: 8px; font-size: 14px; margin-bottom: 16px; }
input:focus { outline: none; border-color: #004EA0; }
button { width: 100%; padding: 12px; background: #004EA0; color: #fff; border: none; border-radius: 8px; font-size: 16px; cursor: pointer; }
button:hover { background: #003a7a; }
.msg { text-align: center; margin-top: 16px; font-size: 14px; }
.msg.ok { color: #16a34a; }
.msg.err { color: #dc2626; }
a { color: #004EA0; text-decoration: none; }
</style>
</head>
<body>
<div class="card">
<h2>注册账号</h2>
<p class="sub">LaTeX 论文编辑器</p>
<form method="POST" action="/register">
<label>邮箱</label>
<input type="email" name="email" required placeholder="请输入邮箱">
<button type="submit">注册</button>
</form>
<p class="sub" style="margin-top:20px">已有账号<a href="OLURL">去登录</a></p>
MSG_PLACEHOLDER
</div>
</body>
</html>
""".replace("OLURL", OL_URL)
class OverleafAdmin:
def __init__(self):
self.session = requests.Session()
self.logged_in = False
def _get_csrf(self, path="/login"):
resp = self.session.get(f"{OL_URL}{path}")
# 从页面中提取 CSRF token
import re
match = re.search(r'name="_csrf"[^>]*value="([^"]+)"', resp.text)
if not match:
match = re.search(r'ol-csrfToken["\s]*content="([^"]+)"', resp.text)
if not match:
match = re.search(r'csrfToken["\s]*:\s*"([^"]+)"', resp.text)
return match.group(1) if match else ""
def login(self):
csrf = self._get_csrf("/login")
resp = self.session.post(f"{OL_URL}/login", data={
"_csrf": csrf,
"email": OL_ADMIN_EMAIL,
"password": OL_ADMIN_PASSWORD,
}, allow_redirects=False)
self.logged_in = resp.status_code in (200, 302)
return self.logged_in
def register_user(self, email):
if not self.logged_in:
self.login()
csrf = self._get_csrf("/admin/register")
resp = self.session.post(f"{OL_URL}/admin/register", data={
"_csrf": csrf,
"email": email,
})
# 从响应中提取设置密码的链接
import re
set_password_url = ""
match = re.search(r'(https?://[^\s"<>]*user/password/set\?[^\s"<>]+)', resp.text)
if match:
set_password_url = match.group(1)
return resp.status_code == 200, set_password_url
admin = OverleafAdmin()
@app.get("/", response_class=HTMLResponse)
async def index():
return REGISTER_HTML.replace("MSG_PLACEHOLDER", "")
@app.post("/register", response_class=HTMLResponse)
async def register(email: str = Form(...)):
try:
ok, set_url = admin.register_user(email)
if ok and set_url:
# 把链接中的 localhost 替换为实际访问地址
import re
set_url = re.sub(r'https?://[^/]+', OL_URL, set_url)
msg = f'<p class="msg ok">注册成功!请点击下方链接设置密码:</p><p style="text-align:center;margin-top:10px"><a href="{set_url}" target="_blank" style="background:#004EA0;color:#fff;padding:10px 24px;border-radius:8px;text-decoration:none;font-size:14px">设置密码</a></p>'
elif ok:
msg = f'<p class="msg ok">注册成功!该邮箱可能已注册。请前往 <a href="{OL_URL}/login">登录页面</a> 登录。</p>'
else:
msg = f'<p class="msg err">注册失败,请稍后重试。</p>'
except Exception as e:
msg = f'<p class="msg err">注册失败:{str(e)[:100]}</p>'
return REGISTER_HTML.replace("MSG_PLACEHOLDER", msg)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=18091)