Update app.py
Browse files
app.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
import os
|
| 2 |
import json
|
| 3 |
-
from flask import Flask, request, g, Response, render_template, redirect, url_for, flash # Thêm
|
| 4 |
import psycopg2
|
| 5 |
from dotenv import load_dotenv
|
| 6 |
from werkzeug.security import generate_password_hash, check_password_hash
|
|
@@ -21,8 +21,8 @@ DB_PORT = os.getenv('DB_PORT')
|
|
| 21 |
# Biến môi trường cho mật khẩu trang
|
| 22 |
PAGE_PASSWORD = os.getenv('PAGE_PASSWORD')
|
| 23 |
|
| 24 |
-
# Cấu hình secret key cho Flask (
|
| 25 |
-
# Trong môi trường production, hãy tạo một chuỗi ngẫu nhiên mạnh và lưu vào biến môi trường
|
| 26 |
app.config['SECRET_KEY'] = os.getenv('FLASK_SECRET_KEY', 'mot_chuoi_bi_mat_rat_ngau_nhien_va_dai')
|
| 27 |
|
| 28 |
if not PAGE_PASSWORD:
|
|
@@ -52,7 +52,7 @@ def close_db_connection(exception):
|
|
| 52 |
db.close()
|
| 53 |
print("Đã đóng kết nối Supabase PostgreSQL.")
|
| 54 |
|
| 55 |
-
# --- Hàm tiện ích để trả về JSON với tiếng Việt (
|
| 56 |
def custom_jsonify(data, status_code=200):
|
| 57 |
response = Response(
|
| 58 |
response=json.dumps(data, ensure_ascii=False),
|
|
@@ -65,25 +65,58 @@ def custom_jsonify(data, status_code=200):
|
|
| 65 |
def require_page_password(f):
|
| 66 |
@wraps(f)
|
| 67 |
def decorated_function(*args, **kwargs):
|
| 68 |
-
# Kiểm tra
|
| 69 |
-
|
|
|
|
| 70 |
|
| 71 |
-
# Nếu
|
|
|
|
| 72 |
if request.method == 'POST' and not provided_password:
|
| 73 |
provided_password = request.headers.get('X-Page-Password')
|
| 74 |
|
| 75 |
if not PAGE_PASSWORD:
|
| 76 |
flash("Lỗi cấu hình server: Mật khẩu trang chưa được thiết lập.", "error")
|
| 77 |
-
return redirect(url_for('home'))
|
| 78 |
|
| 79 |
if provided_password == PAGE_PASSWORD:
|
| 80 |
-
|
|
|
|
|
|
|
|
|
|
| 81 |
else:
|
| 82 |
flash("Truy cập bị từ chối: Mật khẩu trang không hợp lệ.", "error")
|
| 83 |
-
|
| 84 |
-
return redirect(request.referrer or url_for('home'))
|
| 85 |
return decorated_function
|
| 86 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
# --- Các tuyến đường (Routes) của ứng dụng ---
|
| 88 |
|
| 89 |
@app.route('/')
|
|
@@ -91,43 +124,51 @@ def home():
|
|
| 91 |
"""Trang chủ của ứng dụng."""
|
| 92 |
return render_template('index.html')
|
| 93 |
|
| 94 |
-
|
| 95 |
-
@
|
|
|
|
| 96 |
def test_db():
|
| 97 |
"""Kiểm tra kết nối đến cơ sở dữ liệu và hiển thị kết quả."""
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
|
|
|
|
|
|
| 104 |
try:
|
| 105 |
cur = conn.cursor()
|
| 106 |
cur.execute("SELECT version();")
|
| 107 |
db_version = cur.fetchone()[0]
|
| 108 |
cur.close()
|
| 109 |
flash("Kết nối cơ sở dữ liệu thành công!", "success")
|
| 110 |
-
|
|
|
|
| 111 |
except Exception as e:
|
| 112 |
flash(f"Lỗi truy vấn cơ sở dữ liệu: {e}", "error")
|
| 113 |
-
|
| 114 |
-
|
|
|
|
|
|
|
| 115 |
|
| 116 |
-
@app.route('/create_table', methods=['GET'
|
| 117 |
-
@require_page_password
|
| 118 |
def create_table():
|
| 119 |
"""Tạo bảng 'users' nếu chưa tồn tại và hiển thị kết quả."""
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
|
|
|
|
|
|
| 126 |
try:
|
| 127 |
cur = conn.cursor()
|
| 128 |
cur.execute("""
|
| 129 |
CREATE TABLE IF NOT EXISTS users (
|
| 130 |
-
id SERIAL PRIMARY KEY,
|
| 131 |
name VARCHAR(100) NOT NULL,
|
| 132 |
email VARCHAR(100) UNIQUE NOT NULL,
|
| 133 |
password VARCHAR(255) NOT NULL
|
|
@@ -136,15 +177,18 @@ def create_table():
|
|
| 136 |
conn.commit()
|
| 137 |
cur.close()
|
| 138 |
flash("Bảng 'users' đã được tạo (nếu chưa tồn tại) với cột mật khẩu.", "success")
|
| 139 |
-
|
|
|
|
| 140 |
except Exception as e:
|
| 141 |
conn.rollback()
|
| 142 |
flash(f"Lỗi khi tạo bảng: {e}", "error")
|
| 143 |
-
|
| 144 |
-
|
|
|
|
|
|
|
| 145 |
|
| 146 |
@app.route('/add_user', methods=['GET', 'POST'])
|
| 147 |
-
@require_page_password
|
| 148 |
def add_user():
|
| 149 |
"""Thêm người dùng mới vào cơ sở dữ liệu với mật khẩu đã băm."""
|
| 150 |
if request.method == 'POST':
|
|
@@ -184,9 +228,9 @@ def add_user():
|
|
| 184 |
return render_template('add_user.html')
|
| 185 |
return render_template('add_user.html')
|
| 186 |
|
| 187 |
-
@app.route('/
|
| 188 |
-
@require_page_password
|
| 189 |
-
def get_users():
|
| 190 |
"""Lấy danh sách tất cả người dùng từ cơ sở dữ liệu."""
|
| 191 |
conn = get_db_connection()
|
| 192 |
users_list = []
|
|
|
|
| 1 |
import os
|
| 2 |
import json
|
| 3 |
+
from flask import Flask, request, g, Response, render_template, redirect, url_for, flash, session # Thêm 'session'
|
| 4 |
import psycopg2
|
| 5 |
from dotenv import load_dotenv
|
| 6 |
from werkzeug.security import generate_password_hash, check_password_hash
|
|
|
|
| 21 |
# Biến môi trường cho mật khẩu trang
|
| 22 |
PAGE_PASSWORD = os.getenv('PAGE_PASSWORD')
|
| 23 |
|
| 24 |
+
# Cấu hình secret key cho Flask (CẦN THIẾT cho flash messages và session)
|
| 25 |
+
# Trong môi trường production, hãy tạo một chuỗi ngẫu nhiên mạnh và lưu vào biến môi trường FLASK_SECRET_KEY
|
| 26 |
app.config['SECRET_KEY'] = os.getenv('FLASK_SECRET_KEY', 'mot_chuoi_bi_mat_rat_ngau_nhien_va_dai')
|
| 27 |
|
| 28 |
if not PAGE_PASSWORD:
|
|
|
|
| 52 |
db.close()
|
| 53 |
print("Đã đóng kết nối Supabase PostgreSQL.")
|
| 54 |
|
| 55 |
+
# --- Hàm tiện ích để trả về JSON với tiếng Việt (giữ cho các API nếu cần) ---
|
| 56 |
def custom_jsonify(data, status_code=200):
|
| 57 |
response = Response(
|
| 58 |
response=json.dumps(data, ensure_ascii=False),
|
|
|
|
| 65 |
def require_page_password(f):
|
| 66 |
@wraps(f)
|
| 67 |
def decorated_function(*args, **kwargs):
|
| 68 |
+
# Kiểm tra session trước
|
| 69 |
+
if session.get('page_logged_in'):
|
| 70 |
+
return f(*args, **kwargs)
|
| 71 |
|
| 72 |
+
# Nếu chưa đăng nhập qua session, kiểm tra mật khẩu từ form/query/header
|
| 73 |
+
provided_password = request.form.get('page_password') or request.args.get('page_password')
|
| 74 |
if request.method == 'POST' and not provided_password:
|
| 75 |
provided_password = request.headers.get('X-Page-Password')
|
| 76 |
|
| 77 |
if not PAGE_PASSWORD:
|
| 78 |
flash("Lỗi cấu hình server: Mật khẩu trang chưa được thiết lập.", "error")
|
| 79 |
+
return redirect(url_for('home'))
|
| 80 |
|
| 81 |
if provided_password == PAGE_PASSWORD:
|
| 82 |
+
session['page_logged_in'] = True # Đặt session là đã đăng nhập
|
| 83 |
+
# Để tránh lỗi chuyển hướng lại chính trang đó với POST,
|
| 84 |
+
# hãy chuyển hướng đến chính URL hiện tại nhưng là GET request
|
| 85 |
+
return redirect(request.url)
|
| 86 |
else:
|
| 87 |
flash("Truy cập bị từ chối: Mật khẩu trang không hợp lệ.", "error")
|
| 88 |
+
return render_template('page_password_form.html', next_url=request.url) # Hiển thị form mật khẩu riêng
|
|
|
|
| 89 |
return decorated_function
|
| 90 |
|
| 91 |
+
# --- Tuyến đường mới để hiển thị form mật khẩu trang và xử lý đăng nhập ---
|
| 92 |
+
@app.route('/page_password', methods=['GET', 'POST'])
|
| 93 |
+
def page_password_form():
|
| 94 |
+
next_url = request.args.get('next') or url_for('home') # Lấy URL gốc muốn truy cập sau khi nhập mật khẩu
|
| 95 |
+
|
| 96 |
+
if request.method == 'POST':
|
| 97 |
+
provided_password = request.form.get('page_password_input') # Lấy từ form riêng này
|
| 98 |
+
|
| 99 |
+
if not PAGE_PASSWORD:
|
| 100 |
+
flash("Lỗi cấu hình server: Mật khẩu trang chưa được thiết lập.", "error")
|
| 101 |
+
return redirect(url_for('home'))
|
| 102 |
+
|
| 103 |
+
if provided_password == PAGE_PASSWORD:
|
| 104 |
+
session['page_logged_in'] = True
|
| 105 |
+
flash("Mật khẩu trang đã được xác thực thành công!", "success")
|
| 106 |
+
return redirect(next_url)
|
| 107 |
+
else:
|
| 108 |
+
flash("Mật khẩu trang không hợp lệ. Vui lòng thử lại.", "error")
|
| 109 |
+
return render_template('page_password_form.html', next_url=next_url)
|
| 110 |
+
|
| 111 |
+
# Nếu là GET request, hiển thị form mật khẩu
|
| 112 |
+
return render_template('page_password_form.html', next_url=next_url)
|
| 113 |
+
|
| 114 |
+
@app.route('/page_logout')
|
| 115 |
+
def page_logout():
|
| 116 |
+
session.pop('page_logged_in', None)
|
| 117 |
+
flash("Bạn đã đăng xuất khỏi phiên mật khẩu trang.", "info")
|
| 118 |
+
return redirect(url_for('home'))
|
| 119 |
+
|
| 120 |
# --- Các tuyến đường (Routes) của ứng dụng ---
|
| 121 |
|
| 122 |
@app.route('/')
|
|
|
|
| 124 |
"""Trang chủ của ứng dụng."""
|
| 125 |
return render_template('index.html')
|
| 126 |
|
| 127 |
+
# Áp dụng decorator cho các trang cần bảo vệ
|
| 128 |
+
@app.route('/test_db', methods=['GET']) # Đổi thành GET
|
| 129 |
+
@require_page_password # Áp dụng bảo vệ
|
| 130 |
def test_db():
|
| 131 |
"""Kiểm tra kết nối đến cơ sở dữ liệu và hiển thị kết quả."""
|
| 132 |
+
conn = get_db_connection()
|
| 133 |
+
db_version_info = "Chưa kiểm tra"
|
| 134 |
+
status_info = "none"
|
| 135 |
+
if conn is None:
|
| 136 |
+
flash("Không thể kết nối đến cơ sở dữ liệu.", "error")
|
| 137 |
+
db_version_info = "Lỗi kết nối"
|
| 138 |
+
status_info = "error"
|
| 139 |
+
else:
|
| 140 |
try:
|
| 141 |
cur = conn.cursor()
|
| 142 |
cur.execute("SELECT version();")
|
| 143 |
db_version = cur.fetchone()[0]
|
| 144 |
cur.close()
|
| 145 |
flash("Kết nối cơ sở dữ liệu thành công!", "success")
|
| 146 |
+
db_version_info = db_version
|
| 147 |
+
status_info = "success"
|
| 148 |
except Exception as e:
|
| 149 |
flash(f"Lỗi truy vấn cơ sở dữ liệu: {e}", "error")
|
| 150 |
+
db_version_info = "Lỗi truy vấn"
|
| 151 |
+
status_info = "error"
|
| 152 |
+
return render_template('test_db.html', db_version=db_version_info, status=status_info)
|
| 153 |
+
|
| 154 |
|
| 155 |
+
@app.route('/create_table', methods=['GET']) # Đổi thành GET
|
| 156 |
+
@require_page_password # Áp dụng bảo vệ
|
| 157 |
def create_table():
|
| 158 |
"""Tạo bảng 'users' nếu chưa tồn tại và hiển thị kết quả."""
|
| 159 |
+
conn = get_db_connection()
|
| 160 |
+
message_info = "Chưa tạo"
|
| 161 |
+
status_info = "none"
|
| 162 |
+
if conn is None:
|
| 163 |
+
flash("Không thể kết nối đến cơ sở dữ liệu.", "error")
|
| 164 |
+
message_info = "Lỗi kết nối"
|
| 165 |
+
status_info = "error"
|
| 166 |
+
else:
|
| 167 |
try:
|
| 168 |
cur = conn.cursor()
|
| 169 |
cur.execute("""
|
| 170 |
CREATE TABLE IF NOT EXISTS users (
|
| 171 |
+
id SERIAL PRIMARY음을 PRIMARY KEY,
|
| 172 |
name VARCHAR(100) NOT NULL,
|
| 173 |
email VARCHAR(100) UNIQUE NOT NULL,
|
| 174 |
password VARCHAR(255) NOT NULL
|
|
|
|
| 177 |
conn.commit()
|
| 178 |
cur.close()
|
| 179 |
flash("Bảng 'users' đã được tạo (nếu chưa tồn tại) với cột mật khẩu.", "success")
|
| 180 |
+
message_info = "Tạo bảng thành công"
|
| 181 |
+
status_info = "success"
|
| 182 |
except Exception as e:
|
| 183 |
conn.rollback()
|
| 184 |
flash(f"Lỗi khi tạo bảng: {e}", "error")
|
| 185 |
+
message_info = "Lỗi tạo bảng"
|
| 186 |
+
status_info = "error"
|
| 187 |
+
return render_template('create_table.html', message=message_info, status=status_info)
|
| 188 |
+
|
| 189 |
|
| 190 |
@app.route('/add_user', methods=['GET', 'POST'])
|
| 191 |
+
@require_page_password # Áp dụng bảo vệ
|
| 192 |
def add_user():
|
| 193 |
"""Thêm người dùng mới vào cơ sở dữ liệu với mật khẩu đã băm."""
|
| 194 |
if request.method == 'POST':
|
|
|
|
| 228 |
return render_template('add_user.html')
|
| 229 |
return render_template('add_user.html')
|
| 230 |
|
| 231 |
+
@app.route('/get_users', methods=['GET']) # Đổi thành GET
|
| 232 |
+
@require_page_password # Áp dụng bảo vệ
|
| 233 |
+
def get_users(): # Đổi tên hàm thành get_users cho rõ ràng
|
| 234 |
"""Lấy danh sách tất cả người dùng từ cơ sở dữ liệu."""
|
| 235 |
conn = get_db_connection()
|
| 236 |
users_list = []
|