diff --git a/app.log b/app.log deleted file mode 100644 index 5d40469..0000000 --- a/app.log +++ /dev/null @@ -1,11 +0,0 @@ -Admin already exists. - * Serving Flask app 'app' - * Debug mode: on -[31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m - * Running on all addresses (0.0.0.0) - * Running on http://127.0.0.1:5001 - * Running on http://192.168.1.115:5001 -[33mPress CTRL+C to quit[0m - * Restarting with stat - * Debugger is active! - * Debugger PIN: 837-836-472 diff --git a/app/models.py b/app/models.py index f0e888e..45529db 100644 --- a/app/models.py +++ b/app/models.py @@ -26,6 +26,15 @@ class PrintFile(db.Model): status = db.Column(db.String(50), default='waiting') # waiting, slicing, sliced, failed transform_matrix = db.Column(db.Text, nullable=True) # json format of 16-element array +class UserSession(db.Model): + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) + session_token = db.Column(db.String(100), unique=True, nullable=False) + ip_address = db.Column(db.String(50)) + user_agent = db.Column(db.String(255)) + last_active = db.Column(db.DateTime, default=datetime.utcnow) + is_active = db.Column(db.Boolean, default=True) + class SystemConfig(db.Model): id = db.Column(db.Integer, primary_key=True) key = db.Column(db.String(50), unique=True, nullable=False) diff --git a/app/routes/auth_routes.py b/app/routes/auth_routes.py index 7d413a5..3c04bfb 100644 --- a/app/routes/auth_routes.py +++ b/app/routes/auth_routes.py @@ -32,9 +32,28 @@ def login(): remember = bool(request.form.get('remember')) merge_data = bool(request.form.get('merge_data')) + + if user and check_password_hash(user.password_hash, password): login_user(user, remember=remember) + from app.models import UserSession + session_token = str(uuid.uuid4()) + # 尝试获取反向代理传递的真实 IP + client_ip = request.headers.get('X-Real-IP') + if not client_ip: + client_ip = request.remote_addr + + user_session = UserSession( + user_id=user.id, + session_token=session_token, + ip_address=client_ip, + user_agent=request.user_agent.string + ) + db.session.add(user_session) + db.session.commit() + session['user_session_token'] = session_token + if merge_data: guest_id = request.cookies.get('guest_id') if guest_id: @@ -110,10 +129,20 @@ def login(): flash('Invalid username or password', 'danger') return render_template('auth/login.html') + @auth_bp.route('/logout') @login_required def logout(): + session_token = session.get('user_session_token') + if session_token: + from app.models import UserSession + user_session = UserSession.query.filter_by(session_token=session_token).first() + if user_session: + user_session.is_active = False + db.session.commit() logout_user() + session.pop('user_session_token', None) + response = make_response(redirect(url_for('main.index'))) response.delete_cookie('guest_id') # Optionally clear guest cookie return response diff --git a/app/routes/main_routes.py b/app/routes/main_routes.py index 7f50b16..ed683ce 100644 --- a/app/routes/main_routes.py +++ b/app/routes/main_routes.py @@ -18,6 +18,24 @@ from app.routes.admin_routes import get_gcode_dir main_bp = Blueprint('main', __name__) +@main_bp.before_app_request +def check_user_session(): + if current_user.is_authenticated and not current_user.is_guest: + from app.models import UserSession + session_token = session.get('user_session_token') + if session_token: + user_session = UserSession.query.filter_by(session_token=session_token).first() + if not user_session or not user_session.is_active: + from flask_login import logout_user + logout_user() + session.pop('user_session_token', None) + flash('Your session has been terminated.', 'warning') + return redirect(url_for('auth.login')) + else: + user_session.last_active = datetime.utcnow() + db.session.commit() + + auth_bp = Blueprint('auth', __name__, url_prefix='/auth') admin_bp = Blueprint('admin', __name__, url_prefix='/admin') @@ -562,3 +580,52 @@ def engine_options(engine_name): patterns = engine.get_support_patterns(current_app) materials = engine.get_materials(current_app) if hasattr(engine, 'get_materials') else [] return jsonify({'presets': presets, 'support_patterns': patterns, 'materials': materials}) + +@main_bp.route('/account', methods=['GET', 'POST']) +@login_required +def account(): + if current_user.is_guest: + flash('Guests cannot manage accounts.', 'danger') + return redirect(url_for('main.index')) + + from app.models import UserSession + + if request.method == 'POST': + action = request.form.get('action') + + if action == 'change_password': + current_pass = request.form.get('current_password') + new_pass = request.form.get('new_password') + confirm_pass = request.form.get('confirm_password') + + if not check_password_hash(current_user.password_hash, current_pass): + flash('Current password is incorrect.', 'danger') + elif new_pass != confirm_pass: + flash('New passwords do not match.', 'danger') + elif len(new_pass) < 6: + flash('New password must be at least 6 characters.', 'danger') + else: + current_user.password_hash = generate_password_hash(new_pass) + db.session.commit() + flash('Password updated successfully.', 'success') + + elif action == 'terminate_session': + session_id = request.form.get('session_id') + token_to_terminate = request.form.get('session_token') + + my_session_token = session.get('user_session_token') + if token_to_terminate == my_session_token: + flash('You cannot terminate your current session from here. Please logout instead.', 'warning') + else: + us = UserSession.query.filter_by(id=session_id, user_id=current_user.id).first() + if us: + us.is_active = False + db.session.commit() + flash('Session terminated.', 'success') + + return redirect(url_for('main.account')) + + sessions = UserSession.query.filter_by(user_id=current_user.id, is_active=True).order_by(UserSession.last_active.desc()).all() + current_token = session.get('user_session_token') + + return render_template('slice/account.html', sessions=sessions, current_token=current_token) diff --git a/app/templates/base.html b/app/templates/base.html index 2a3198e..2a335d6 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -107,6 +107,13 @@ {{ _('Control') }} + {% if current_user.is_authenticated and not current_user.is_guest %} +
| {{ _('Device') }} | +{{ _('IP Address') }} | +{{ _('Last Active') }} | +{{ _('Action') }} | +
|---|---|---|---|
|
+
+
+
+
+
+ {{ s.user_agent.split(' ')[0] if s.user_agent else _('Unknown Device') }}
+ {% if s.session_token == current_token %}
+ {{ _('This Device') }}
+ {% endif %}
+ |
+ + {{ s.ip_address }} + | ++ {{ s.last_active.strftime('%Y-%m-%d %H:%M:%S') }} + | ++ {% if s.session_token != current_token %} + + {% else %} + + {% endif %} + | +
| {{ _('No active sessions found.') }} | +|||