diff --git a/Dockerfile b/Dockerfile index dd47ea9..4a9fe2d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,6 +19,7 @@ VOLUME ["/app/data"] ENV FLASK_DEBUG=0 ENV TZ=Asia/Shanghai +ENV ADMIN_PASSWORD=admin123 RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone CMD ["python", "run.py"] diff --git a/docker-compose.yml b/docker-compose.yml index 2b28768..ba514a4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,4 +6,6 @@ services: - "9000:9000" volumes: - ./data:/app/data + environment: + - ADMIN_PASSWORD=${ADMIN_PASSWORD:-admin123} restart: unless-stopped diff --git a/server/app.py b/server/app.py index 4686f54..1117efe 100644 --- a/server/app.py +++ b/server/app.py @@ -1,15 +1,19 @@ import os import sys +import hashlib sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) -from flask import Flask, redirect, url_for +from flask import Flask, redirect, url_for, request, session, render_template from flask_socketio import SocketIO from server.database import init_db from server.routers import accounts, tasks, orders socketio = SocketIO(cors_allowed_origins="*") +# 访问密码,可通过环境变量 ADMIN_PASSWORD 设置,默认 admin123 +ADMIN_PASSWORD = os.environ.get('ADMIN_PASSWORD', 'admin123') + def create_app(): app = Flask( @@ -17,10 +21,41 @@ def create_app(): template_folder=os.path.join(os.path.dirname(__file__), '..', 'templates'), static_folder=os.path.join(os.path.dirname(__file__), '..', 'static'), ) - app.secret_key = 'snatcher-secret-key-change-me' + app.secret_key = os.environ.get( + 'SECRET_KEY', 'snatcher-secret-key-change-me') init_db() + # ── 登录认证 ── + @app.route('/login', methods=['GET', 'POST']) + def login(): + if request.method == 'POST': + pwd = request.form.get('password', '') + if pwd == ADMIN_PASSWORD: + session['authenticated'] = _hash(pwd) + session.permanent = True + app.permanent_session_lifetime = __import__( + 'datetime').timedelta(days=7) + return redirect(request.args.get('next', '/')) + return render_template('login.html', error='密码错误') + return render_template('login.html') + + @app.route('/logout') + def logout(): + session.clear() + return redirect('/login') + + @app.before_request + def require_auth(): + # 放行登录页和静态资源 + if request.path in ('/login',) or request.path.startswith('/static'): + return + # 放行 SocketIO 的内部路径 + if request.path.startswith('/socket.io'): + return + if session.get('authenticated') != _hash(ADMIN_PASSWORD): + return redirect(url_for('login', next=request.path)) + app.register_blueprint(accounts.bp) app.register_blueprint(tasks.bp) app.register_blueprint(orders.bp) @@ -35,6 +70,10 @@ def create_app(): return app +def _hash(s): + return hashlib.sha256(s.encode()).hexdigest()[:16] + + def _register_socketio_events(): import asyncio from server.services.remote_browser import get_session diff --git a/templates/base.html b/templates/base.html index 9b6d0ab..ef30da7 100644 --- a/templates/base.html +++ b/templates/base.html @@ -35,6 +35,7 @@ 任务 账号 订单 + diff --git a/templates/login.html b/templates/login.html new file mode 100644 index 0000000..0eccd04 --- /dev/null +++ b/templates/login.html @@ -0,0 +1,47 @@ + + +
+ + +