Upgrade code
This commit is contained in:
+45
-2
@@ -1,16 +1,22 @@
|
||||
import logging
|
||||
import os
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from urllib.parse import urlparse, urljoin
|
||||
from flask import Blueprint, render_template, redirect, url_for, flash, request
|
||||
from flask import Blueprint, render_template, redirect, url_for, flash, request, current_app, send_from_directory
|
||||
from flask_login import login_user, logout_user, login_required, current_user
|
||||
from werkzeug.utils import secure_filename
|
||||
from app import db, limiter
|
||||
from app.models import User, UserRole
|
||||
from app.services.log_service import log_action
|
||||
from app.services.validation_service import validate_password
|
||||
from app.services.validation_service import validate_password, validate_file
|
||||
|
||||
auth_bp = Blueprint('auth', __name__, url_prefix='/auth')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
AVATAR_ALLOWED_EXT = {'png', 'jpg', 'jpeg', 'gif', 'webp'}
|
||||
AVATAR_MAX_BYTES = 5 * 1024 * 1024 # 5 MB
|
||||
|
||||
|
||||
def _is_safe_url(target):
|
||||
"""Return True only when *target* points back to this same host.
|
||||
@@ -138,6 +144,31 @@ def profile():
|
||||
current_user.email_notif= email_notif
|
||||
current_user.web_notif = web_notif
|
||||
|
||||
# ── Avatar upload ─────────────────────────────────────────────────────
|
||||
avatar_file = request.files.get('avatar')
|
||||
if avatar_file and avatar_file.filename:
|
||||
file_error = validate_file(avatar_file, AVATAR_ALLOWED_EXT)
|
||||
if file_error:
|
||||
flash(f'Avatar not saved: {file_error}', 'danger')
|
||||
else:
|
||||
avatar_file.stream.seek(0, 2)
|
||||
avatar_size = avatar_file.stream.tell()
|
||||
avatar_file.stream.seek(0)
|
||||
if avatar_size > AVATAR_MAX_BYTES:
|
||||
flash('Avatar image must be under 5 MB.', 'danger')
|
||||
else:
|
||||
ext = secure_filename(avatar_file.filename).rsplit('.', 1)[-1].lower()
|
||||
stored_name = f"avatar_{current_user.id}_{uuid.uuid4().hex}.{ext}"
|
||||
upload_dir = current_app.config['UPLOAD_FOLDER']
|
||||
# Delete old avatar file from disk if present
|
||||
if current_user.avatar_url:
|
||||
old_file = os.path.join(upload_dir, os.path.basename(current_user.avatar_url))
|
||||
if os.path.exists(old_file):
|
||||
os.remove(old_file)
|
||||
avatar_file.save(os.path.join(upload_dir, stored_name))
|
||||
current_user.avatar_url = stored_name
|
||||
logger.info(f'[AUTH AVATAR UPLOAD] user_id={current_user.id} file={stored_name}')
|
||||
|
||||
if new_pw:
|
||||
pw_error = validate_password(new_pw, confirm_pw)
|
||||
if pw_error:
|
||||
@@ -152,3 +183,15 @@ def profile():
|
||||
flash('Profile updated successfully.', 'success')
|
||||
|
||||
return render_template('auth/profile.html')
|
||||
|
||||
|
||||
@auth_bp.route('/avatar/<string:filename>')
|
||||
@login_required
|
||||
def serve_avatar(filename):
|
||||
"""Serve a user avatar image stored in the upload folder."""
|
||||
# Prevent path traversal — stored_name never contains slashes
|
||||
if '/' in filename or '\\' in filename or '..' in filename:
|
||||
from flask import abort
|
||||
abort(400)
|
||||
upload_dir = current_app.config['UPLOAD_FOLDER']
|
||||
return send_from_directory(upload_dir, filename, as_attachment=False)
|
||||
|
||||
Reference in New Issue
Block a user