patch cdn webservice
This commit is contained in:
+45
-24
@@ -6,6 +6,8 @@ import secrets
|
|||||||
import datetime
|
import datetime
|
||||||
import subprocess
|
import subprocess
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
from flask import *
|
from flask import *
|
||||||
from werkzeug.security import generate_password_hash, check_password_hash
|
from werkzeug.security import generate_password_hash, check_password_hash
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
@@ -13,7 +15,6 @@ from werkzeug.utils import secure_filename
|
|||||||
APP_DIR = os.path.dirname(os.path.abspath(__file__))
|
APP_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
DB_PATH = os.path.join(APP_DIR, "data.db")
|
DB_PATH = os.path.join(APP_DIR, "data.db")
|
||||||
UPLOAD_DIR = os.path.join(APP_DIR, "uploads")
|
UPLOAD_DIR = os.path.join(APP_DIR, "uploads")
|
||||||
FLAG_PATH = os.path.join("/flag.txt")
|
|
||||||
|
|
||||||
ALLOWED_EXT = {"png", "jpg", "jpeg", "bmp"}
|
ALLOWED_EXT = {"png", "jpg", "jpeg", "bmp"}
|
||||||
MAX_CONTENT_LENGTH = 8 * 1024 * 1024
|
MAX_CONTENT_LENGTH = 8 * 1024 * 1024
|
||||||
@@ -39,7 +40,8 @@ def close_db(_exc):
|
|||||||
def init_db():
|
def init_db():
|
||||||
Path(UPLOAD_DIR).mkdir(parents=True, exist_ok=True)
|
Path(UPLOAD_DIR).mkdir(parents=True, exist_ok=True)
|
||||||
db = get_db()
|
db = get_db()
|
||||||
db.executescript("""
|
db.executescript(
|
||||||
|
"""
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
username TEXT UNIQUE NOT NULL,
|
username TEXT UNIQUE NOT NULL,
|
||||||
@@ -57,24 +59,12 @@ def init_db():
|
|||||||
created_at TEXT NOT NULL,
|
created_at TEXT NOT NULL,
|
||||||
FOREIGN KEY(user_id) REFERENCES users(id)
|
FOREIGN KEY(user_id) REFERENCES users(id)
|
||||||
);
|
);
|
||||||
""")
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_posts_user_file ON posts(user_id, filename);
|
||||||
|
"""
|
||||||
|
)
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
def _resolve_flag_file_path():
|
|
||||||
p = FLAG_PATH
|
|
||||||
if os.path.isdir(p):
|
|
||||||
p = os.path.join(p, "flag.txt")
|
|
||||||
os.makedirs(os.path.dirname(p), exist_ok=True)
|
|
||||||
return p
|
|
||||||
|
|
||||||
def generate_flag_at_boot():
|
|
||||||
path = _resolve_flag_file_path()
|
|
||||||
if not os.path.exists(path) or os.environ.get("RESEED_FLAG") == "1":
|
|
||||||
token = secrets.token_bytes(32)
|
|
||||||
sha = hashlib.sha256(token).hexdigest()
|
|
||||||
with open(path, "w", encoding="utf-8") as fh:
|
|
||||||
fh.write(f"GEMASTIK{{{sha}}}\n")
|
|
||||||
|
|
||||||
def current_user():
|
def current_user():
|
||||||
if "uid" not in session:
|
if "uid" not in session:
|
||||||
return None
|
return None
|
||||||
@@ -82,6 +72,16 @@ def current_user():
|
|||||||
cur = db.execute("SELECT id, username, role FROM users WHERE id = ?", (session["uid"],))
|
cur = db.execute("SELECT id, username, role FROM users WHERE id = ?", (session["uid"],))
|
||||||
return cur.fetchone()
|
return cur.fetchone()
|
||||||
|
|
||||||
|
def login_required(view):
|
||||||
|
@wraps(view)
|
||||||
|
def wrapped(*args, **kwargs):
|
||||||
|
if not current_user():
|
||||||
|
dest = request.path
|
||||||
|
flash("Please log in to continue.")
|
||||||
|
return redirect(url_for("login", next=dest))
|
||||||
|
return view(*args, **kwargs)
|
||||||
|
return wrapped
|
||||||
|
|
||||||
def _is_within(child_path: str, parent_dir: str) -> bool:
|
def _is_within(child_path: str, parent_dir: str) -> bool:
|
||||||
child_real = os.path.realpath(child_path)
|
child_real = os.path.realpath(child_path)
|
||||||
parent_real = os.path.realpath(parent_dir)
|
parent_real = os.path.realpath(parent_dir)
|
||||||
@@ -156,8 +156,13 @@ def login():
|
|||||||
if not row or not check_password_hash(row["password_hash"], password):
|
if not row or not check_password_hash(row["password_hash"], password):
|
||||||
flash("Invalid credentials")
|
flash("Invalid credentials")
|
||||||
return render_template("login.html")
|
return render_template("login.html")
|
||||||
|
|
||||||
session["uid"] = row["id"]
|
session["uid"] = row["id"]
|
||||||
flash(f"Welcome, {row['username']}!")
|
flash(f"Welcome, {row['username']}!")
|
||||||
|
|
||||||
|
next_url = request.args.get("next") or request.form.get("next")
|
||||||
|
if next_url and next_url.startswith("/"):
|
||||||
|
return redirect(next_url)
|
||||||
return redirect(url_for("gallery"))
|
return redirect(url_for("gallery"))
|
||||||
return render_template("login.html")
|
return render_template("login.html")
|
||||||
|
|
||||||
@@ -168,10 +173,9 @@ def logout():
|
|||||||
return redirect(url_for("login"))
|
return redirect(url_for("login"))
|
||||||
|
|
||||||
@app.route("/upload", methods=["GET", "POST"])
|
@app.route("/upload", methods=["GET", "POST"])
|
||||||
|
@login_required
|
||||||
def upload():
|
def upload():
|
||||||
user = current_user()
|
user = current_user()
|
||||||
if not user:
|
|
||||||
return redirect(url_for("login"))
|
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
title = request.form.get("title", "").strip() or "(untitled)"
|
title = request.form.get("title", "").strip() or "(untitled)"
|
||||||
f = request.files.get("image")
|
f = request.files.get("image")
|
||||||
@@ -205,6 +209,7 @@ def upload():
|
|||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
@app.route("/gallery")
|
@app.route("/gallery")
|
||||||
|
@login_required
|
||||||
def gallery():
|
def gallery():
|
||||||
user = current_user()
|
user = current_user()
|
||||||
db = get_db()
|
db = get_db()
|
||||||
@@ -212,18 +217,22 @@ def gallery():
|
|||||||
"""
|
"""
|
||||||
SELECT p.id, p.title, p.filename, p.created_at, u.username
|
SELECT p.id, p.title, p.filename, p.created_at, u.username
|
||||||
FROM posts p JOIN users u ON p.user_id = u.id
|
FROM posts p JOIN users u ON p.user_id = u.id
|
||||||
|
WHERE p.user_id = ?
|
||||||
ORDER BY p.id DESC
|
ORDER BY p.id DESC
|
||||||
"""
|
""",
|
||||||
|
(user["id"],),
|
||||||
)
|
)
|
||||||
posts = cur.fetchall()
|
posts = cur.fetchall()
|
||||||
return render_template("gallery.html", posts=posts, user=user)
|
return render_template("gallery.html", posts=posts, user=user)
|
||||||
|
|
||||||
@app.route("/post/<int:pid>")
|
@app.route("/post/<int:pid>")
|
||||||
|
@login_required
|
||||||
def view_post(pid):
|
def view_post(pid):
|
||||||
|
user = current_user()
|
||||||
db = get_db()
|
db = get_db()
|
||||||
cur = db.execute(
|
cur = db.execute(
|
||||||
"SELECT p.*, u.username FROM posts p JOIN users u ON p.user_id = u.id WHERE p.id = ?",
|
"SELECT p.*, u.username FROM posts p JOIN users u ON p.user_id = u.id WHERE p.id = ? AND p.user_id = ?",
|
||||||
(pid,),
|
(pid, user["id"]),
|
||||||
)
|
)
|
||||||
post = cur.fetchone()
|
post = cur.fetchone()
|
||||||
if not post:
|
if not post:
|
||||||
@@ -250,8 +259,20 @@ def view_post(pid):
|
|||||||
return render_template_string(page_src, post=post)
|
return render_template_string(page_src, post=post)
|
||||||
|
|
||||||
@app.route("/i/<path:filename>")
|
@app.route("/i/<path:filename>")
|
||||||
|
@login_required
|
||||||
def cdn_serve(filename):
|
def cdn_serve(filename):
|
||||||
return send_from_directory(UPLOAD_DIR, filename, as_attachment=False)
|
user = current_user()
|
||||||
|
db = get_db()
|
||||||
|
cur = db.execute(
|
||||||
|
"SELECT 1 FROM posts WHERE user_id = ? AND filename = ? LIMIT 1",
|
||||||
|
(user["id"], filename),
|
||||||
|
)
|
||||||
|
if not cur.fetchone():
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
resp = send_from_directory(UPLOAD_DIR, filename, as_attachment=False)
|
||||||
|
resp.headers["Cache-Control"] = "private, max-age=0, no-store"
|
||||||
|
return resp
|
||||||
|
|
||||||
@app.errorhandler(413)
|
@app.errorhandler(413)
|
||||||
def too_large(_):
|
def too_large(_):
|
||||||
|
|||||||
Reference in New Issue
Block a user