Alten Flask/Python-Code und Jinja-Templates entfernt
Go-Reimplementierung hat Feature-Paritaet (inkl. URL-Linkify im JS-Frontend via linkify()). static/ bleibt (Go liefert /static/* aus und legt Uploads unter static/media/ ab); alte Flask-Assets dort unberuehrt. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,215 +0,0 @@
|
|||||||
from flask import render_template, request, make_response, redirect
|
|
||||||
import database, re, bcrypt, secrets, time
|
|
||||||
import user_controller
|
|
||||||
|
|
||||||
from app import app
|
|
||||||
|
|
||||||
def get_session(request):
|
|
||||||
sessionid = request.cookies.get('session')
|
|
||||||
|
|
||||||
if not sessionid:
|
|
||||||
return False
|
|
||||||
|
|
||||||
sessionid = str(sessionid)
|
|
||||||
sessioninfo = database.fetch("""SELECT uid, created_at, expires, description
|
|
||||||
FROM session
|
|
||||||
WHERE value=?;""",
|
|
||||||
[sessionid])
|
|
||||||
|
|
||||||
if len(sessioninfo) == 0:
|
|
||||||
return False
|
|
||||||
|
|
||||||
if len(sessioninfo) > 1:
|
|
||||||
# there is a problem
|
|
||||||
return False
|
|
||||||
|
|
||||||
return sessioninfo[0] + (sessionid,)
|
|
||||||
|
|
||||||
def check_pw(uid, password):
|
|
||||||
userquery = database.fetch("SELECT uid, password FROM user WHERE username=?", [username])
|
|
||||||
|
|
||||||
# STOP TIMING ATTACKS
|
|
||||||
time.sleep(secrets.randbelow(100)/50)
|
|
||||||
|
|
||||||
if len(userquery) == 0:
|
|
||||||
return render_template("auth/login.html",
|
|
||||||
response=f"Der Nutzername >{username} oder das Passwort ist falsch.",
|
|
||||||
username=username)
|
|
||||||
|
|
||||||
uidquery = userquery[0][0]
|
|
||||||
passquery = userquery[0][1]
|
|
||||||
|
|
||||||
passform = str(request.form['pass'])
|
|
||||||
passform = passform.encode("utf8")
|
|
||||||
passcorrect = bcrypt.checkpw(passform, passquery)
|
|
||||||
|
|
||||||
if not passcorrect:
|
|
||||||
return render_template("auth/login.html",
|
|
||||||
response=f"Der Nutzername {username} oder das >Passwort ist falsch.",
|
|
||||||
username=username)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/auth/headerbar")
|
|
||||||
def headerbar():
|
|
||||||
sessioninfo = get_session(request)
|
|
||||||
|
|
||||||
if not sessioninfo:
|
|
||||||
return render_template("auth/headerbar.html", loggedin=False)
|
|
||||||
|
|
||||||
return render_template("auth/headerbar.html", loggedin=True)
|
|
||||||
|
|
||||||
@app.route("/auth/sessioninfo", methods=['GET'])
|
|
||||||
def sessioninfo():
|
|
||||||
time.sleep(secrets.randbelow(100)/50) # Rate limiting
|
|
||||||
|
|
||||||
sessioninfo = get_session(request)
|
|
||||||
|
|
||||||
if not sessioninfo:
|
|
||||||
return "Invalid session"
|
|
||||||
|
|
||||||
(uid, created_at, expires, description) = sessioninfo
|
|
||||||
# uid = sessioninfo[0][0]
|
|
||||||
# created_at = sessioninfo[0][1]
|
|
||||||
# expires = sessioninfo[0][2]
|
|
||||||
# description = sessioninfo[0][3]
|
|
||||||
return render_template("/auth/sessioninfo.html",
|
|
||||||
sessionid=sessionid,
|
|
||||||
uid=uid,
|
|
||||||
created_at=created_at,
|
|
||||||
expires=expires,
|
|
||||||
description=description)
|
|
||||||
|
|
||||||
@app.route("/auth/logout", methods=['GET'])
|
|
||||||
def logout():
|
|
||||||
sessioninfo = get_session(request)
|
|
||||||
|
|
||||||
response = redirect("/")
|
|
||||||
response.set_cookie('session', value='', expires=0)
|
|
||||||
|
|
||||||
if not sessioninfo:
|
|
||||||
return response
|
|
||||||
|
|
||||||
database.execute("DELETE FROM session WHERE value = ?", [sessioninfo[4]])
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
@app.route("/auth/login", methods=['GET', 'POST'])
|
|
||||||
def login():
|
|
||||||
sessioninfo = get_session(request)
|
|
||||||
|
|
||||||
if sessioninfo:
|
|
||||||
uid = sessioninfo[0]
|
|
||||||
username = database.fetch("SELECT username FROM user WHERE uid = ?", [uid])
|
|
||||||
username = username[0][0]
|
|
||||||
return render_template("auth/loginsuccess.html", username=username)
|
|
||||||
|
|
||||||
|
|
||||||
if request.method == 'GET':
|
|
||||||
return render_template("auth/login.html",
|
|
||||||
response="",
|
|
||||||
username="")
|
|
||||||
|
|
||||||
username = _sanitizeUsername(request.form['user'])
|
|
||||||
if len(username) < 3:
|
|
||||||
return render_template("auth/login.html",
|
|
||||||
response=f"Der Nutzername {username} ist zu kurz.",
|
|
||||||
username=username)
|
|
||||||
|
|
||||||
userquery = database.fetch("SELECT uid, password FROM user WHERE username=?", [username])
|
|
||||||
|
|
||||||
# STOP TIMING ATTACKS
|
|
||||||
time.sleep(secrets.randbelow(100)/50)
|
|
||||||
|
|
||||||
if len(userquery) == 0:
|
|
||||||
return render_template("auth/login.html",
|
|
||||||
response=f"Der Nutzername >{username} oder das Passwort ist falsch.",
|
|
||||||
username=username)
|
|
||||||
|
|
||||||
uidquery = userquery[0][0]
|
|
||||||
passquery = userquery[0][1]
|
|
||||||
|
|
||||||
passform = str(request.form['pass'])
|
|
||||||
passform = passform.encode("utf8")
|
|
||||||
passcorrect = bcrypt.checkpw(passform, passquery)
|
|
||||||
|
|
||||||
if not passcorrect:
|
|
||||||
return render_template("auth/login.html",
|
|
||||||
response=f"Der Nutzername {username} oder das >Passwort ist falsch.",
|
|
||||||
username=username)
|
|
||||||
|
|
||||||
now = int(time.time())
|
|
||||||
|
|
||||||
timeout = int(request.form['timeout'])
|
|
||||||
timeout = now + timeout
|
|
||||||
|
|
||||||
session = secrets.randbelow(999999999)
|
|
||||||
session = str(session) + str(now)
|
|
||||||
|
|
||||||
database.execute("DELETE FROM session WHERE expires < ?", [str(now)])
|
|
||||||
database.execute("""INSERT INTO session
|
|
||||||
(uid, created_at, expires, description, value)
|
|
||||||
VALUES (?, ?, ?, ?, ?);""",
|
|
||||||
(uidquery, int(time.time()), timeout, "", session)
|
|
||||||
)
|
|
||||||
|
|
||||||
response = make_response("<script>location.href = '/';</script>")
|
|
||||||
response.set_cookie('session', value=session, expires=timeout)
|
|
||||||
return response
|
|
||||||
|
|
||||||
@app.route("/auth/newuser", methods=['GET', 'POST'])
|
|
||||||
def newuser():
|
|
||||||
if request.method == 'GET':
|
|
||||||
return render_template("auth/register.html",
|
|
||||||
responses=[],
|
|
||||||
username="")
|
|
||||||
|
|
||||||
success = True
|
|
||||||
responses = []
|
|
||||||
|
|
||||||
username = _sanitizeUsername(request.form['user'])
|
|
||||||
if len(username) < 3:
|
|
||||||
success = False
|
|
||||||
responses.append(f"Der Nutzername {username} ist zu kurz.")
|
|
||||||
|
|
||||||
if user_controller.id_from_username(username) != 0:
|
|
||||||
success = False
|
|
||||||
responses.append(f"Der Nutzername {username} wird bereits verwendet.")
|
|
||||||
|
|
||||||
pass1 = str(request.form['pass1'])
|
|
||||||
pass2 = str(request.form['pass2'])
|
|
||||||
if pass1 != pass2:
|
|
||||||
success = False
|
|
||||||
responses.append("Passwort und Passwortbestätigung sind ungleich.")
|
|
||||||
|
|
||||||
if len(pass1) < 10:
|
|
||||||
success = False
|
|
||||||
responses.append("Das Passwort ist kürzer als 10 Zeichen.")
|
|
||||||
|
|
||||||
if not success:
|
|
||||||
return render_template("auth/register.html",
|
|
||||||
responses=responses,
|
|
||||||
username=username)
|
|
||||||
|
|
||||||
user_controller.new(username, pass1)
|
|
||||||
|
|
||||||
return render_template("auth/registersuccess.html", username=username)
|
|
||||||
|
|
||||||
def _sanitizeUsername(username):
|
|
||||||
username = str(username)
|
|
||||||
username = username.strip()
|
|
||||||
username = username[:32]
|
|
||||||
|
|
||||||
# Remove all non-word characters (everything except numbers and letters)
|
|
||||||
username = re.sub(r"[^._\w\s-]", '', username)
|
|
||||||
|
|
||||||
# Replace all runs of whitespace with a single dash
|
|
||||||
username = re.sub(r"\s+", '_', username)
|
|
||||||
|
|
||||||
return username
|
|
||||||
|
|
||||||
def _verifyEmail(email):
|
|
||||||
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
|
|
||||||
if re.match(pattern, email):
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
-25
@@ -1,25 +0,0 @@
|
|||||||
import sqlite3
|
|
||||||
|
|
||||||
def execute(query, data):
|
|
||||||
connection = sqlite3.connect("kver.db")
|
|
||||||
cursor = connection.cursor()
|
|
||||||
|
|
||||||
cursor.execute(query, data)
|
|
||||||
|
|
||||||
connection.commit()
|
|
||||||
connection.close()
|
|
||||||
|
|
||||||
def fetch(query, data, many=True):
|
|
||||||
connection = sqlite3.connect("kver.db")
|
|
||||||
cursor = connection.cursor()
|
|
||||||
|
|
||||||
result = cursor.execute(query, data)
|
|
||||||
|
|
||||||
if many:
|
|
||||||
result = result.fetchall()
|
|
||||||
else:
|
|
||||||
result = result.fetchone()
|
|
||||||
|
|
||||||
connection.close()
|
|
||||||
|
|
||||||
return result
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
from flask import render_template, request
|
|
||||||
from PIL import Image
|
|
||||||
# import database, re, bcrypt, secrets, time
|
|
||||||
import secrets, time, os, database
|
|
||||||
from auth import get_session
|
|
||||||
from app import app
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/entry/<pid>/interactions/<mode>", methods=['GET'])
|
|
||||||
def interactions(pid, mode):
|
|
||||||
# session = get_session(request)
|
|
||||||
|
|
||||||
# empty = (mode!='left' and mode!='right')
|
|
||||||
# selected = 'none'
|
|
||||||
|
|
||||||
# if session and not empty:
|
|
||||||
# uid = session[0]
|
|
||||||
# previous = database.fetch("SELECT mode FROM vote WHERE uid=? AND pid=?", [uid, pid])
|
|
||||||
# if len(previous) == 0:
|
|
||||||
# selected=mode
|
|
||||||
# database.execute("INSERT INTO vote (uid, pid, mode) VALUES (?,?,?)", [uid, pid, mode])
|
|
||||||
# else:
|
|
||||||
|
|
||||||
# previous = previous[0][0]
|
|
||||||
# previous = 'none'
|
|
||||||
|
|
||||||
# if previous < 1:
|
|
||||||
# elif previous[0][0]==mode:
|
|
||||||
# database.execute("DELETE FROM vote WHERE uid=? AND pid=?", [uid, pid])
|
|
||||||
# else:
|
|
||||||
# database.execute("UPDATE vote SET mode=? WHERE uid=? AND pid=?", [mode, uid, pid])
|
|
||||||
|
|
||||||
# left = database.fetch("SELECT COUNT(id) FROM vote WHERE pid=? and mode='left';", [pid])[0][0]
|
|
||||||
# right = database.fetch("SELECT COUNT(id) FROM vote WHERE pid=? and mode='right';", [pid])[0][0]
|
|
||||||
|
|
||||||
# return render_template("entry/interactions.html", pid=pid, stats=f"{left} {right} {mode}")
|
|
||||||
return "--"
|
|
||||||
|
|
||||||
@app.route("/entry/feed/<page>", methods=['GET'])
|
|
||||||
def feed(page):
|
|
||||||
page = int(page, 0)
|
|
||||||
page = max(0,page)
|
|
||||||
page = min(100,page)
|
|
||||||
|
|
||||||
count = 20
|
|
||||||
offset = page*count
|
|
||||||
|
|
||||||
posts = database.fetch("SELECT * FROM entry ORDER BY created_at DESC LIMIT ? OFFSET ?", [count, offset])
|
|
||||||
|
|
||||||
if len(posts) <= 0:
|
|
||||||
return """<div id="infinite-scroll-indicator">
|
|
||||||
<h2>YOU REACHED THE END!</h2>
|
|
||||||
</div>"""
|
|
||||||
|
|
||||||
posts_with_usernames = []
|
|
||||||
for post in posts:
|
|
||||||
username = database.fetch("SELECT username FROM user WHERE uid = ?", [post[2]])
|
|
||||||
posts_with_usernames.append(post + username[0])
|
|
||||||
|
|
||||||
return render_template("entry/feed.html", posts=posts_with_usernames, page=page)
|
|
||||||
|
|
||||||
@app.route("/entry/create", methods=['GET', 'POST'])
|
|
||||||
def newentry():
|
|
||||||
session = get_session(request)
|
|
||||||
|
|
||||||
if not session:
|
|
||||||
return "Melde dich an, um einen Beitrag zu erstellen!"
|
|
||||||
|
|
||||||
uid = session[0]
|
|
||||||
|
|
||||||
if request.method == 'GET':
|
|
||||||
return render_template('entry/create.html')
|
|
||||||
|
|
||||||
content = str(request.form["content"])
|
|
||||||
content = content.strip()
|
|
||||||
content = content[:1000]
|
|
||||||
|
|
||||||
fileattached = False
|
|
||||||
if 'file' in request.files:
|
|
||||||
file = request.files['file']
|
|
||||||
fileattached=True
|
|
||||||
|
|
||||||
if fileattached:
|
|
||||||
fileattached = _storeimage(file)
|
|
||||||
|
|
||||||
if not fileattached and content == '':
|
|
||||||
return render_template('entry/create.html', responses=["Leerer Beitrag!"])
|
|
||||||
|
|
||||||
if not fileattached:
|
|
||||||
fileattached = "none"
|
|
||||||
|
|
||||||
pid = secrets.randbelow(999999999999)
|
|
||||||
now = int(time.time())
|
|
||||||
database.execute("""INSERT INTO entry
|
|
||||||
(pid, uid, created_at, content, filepath)
|
|
||||||
VALUES (?, ?, ?, ?, ?)""",
|
|
||||||
(pid, uid, now, content, fileattached))
|
|
||||||
|
|
||||||
return render_template('entry/create.html', responses=[content, str(fileattached)])
|
|
||||||
|
|
||||||
def _storeimage(file):
|
|
||||||
maxsize = 1024
|
|
||||||
tmpfile = secrets.randbelow(999999)
|
|
||||||
tmppath = f"tmp/{tmpfile}"
|
|
||||||
|
|
||||||
import resizegif
|
|
||||||
|
|
||||||
try:
|
|
||||||
file.save(tmppath)
|
|
||||||
|
|
||||||
i = Image.open(tmppath)
|
|
||||||
if i.format == 'GIF':
|
|
||||||
filetype = 'gif'
|
|
||||||
else:
|
|
||||||
i = i.convert("RGB")
|
|
||||||
filetype = 'jpg'
|
|
||||||
|
|
||||||
newpath = f"static/media/{int(time.time())}-{tmpfile}.{filetype}"
|
|
||||||
|
|
||||||
(width, height) = i.size;
|
|
||||||
aspect = width/height
|
|
||||||
|
|
||||||
if width < maxsize and height < maxsize:
|
|
||||||
if filetype == 'gif':
|
|
||||||
resizegif.resize_gif(i, newpath, (width, height))
|
|
||||||
else:
|
|
||||||
i.save(newpath)
|
|
||||||
|
|
||||||
os.remove(tmppath)
|
|
||||||
return newpath
|
|
||||||
|
|
||||||
if width > height:
|
|
||||||
width = maxsize
|
|
||||||
height = maxsize/aspect
|
|
||||||
else:
|
|
||||||
height = maxsize
|
|
||||||
width = aspect*maxsize
|
|
||||||
|
|
||||||
if filetype == 'gif':
|
|
||||||
resizegif.resize_gif(i, newpath, (width, height))
|
|
||||||
else:
|
|
||||||
i.thumbnail((width, height), Image.Resampling.LANCZOS)
|
|
||||||
i.save(newpath)
|
|
||||||
|
|
||||||
os.remove(tmppath)
|
|
||||||
return newpath
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
os.remove(tmppath)
|
|
||||||
return False
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
import os
|
|
||||||
from flask import Flask, request, render_template
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from werkzeug.exceptions import HTTPException
|
|
||||||
|
|
||||||
from app import app
|
|
||||||
app.config['UPLOAD_FOLDER'] = "uploads/"
|
|
||||||
|
|
||||||
@app.errorhandler(400)
|
|
||||||
def handle_bad_request(e):
|
|
||||||
print(f"400 BAD REQUEST: {e}")
|
|
||||||
return '400<br>BAD REQUEST'
|
|
||||||
|
|
||||||
@app.template_filter('timestamp')
|
|
||||||
def timestamp_filter(value):
|
|
||||||
try:
|
|
||||||
dt = datetime.fromtimestamp(value)
|
|
||||||
return dt.strftime("%d.%m.%Y %H:%M")
|
|
||||||
except:
|
|
||||||
return value
|
|
||||||
|
|
||||||
import html, re
|
|
||||||
|
|
||||||
@app.template_filter('content')
|
|
||||||
def content_filter(value):
|
|
||||||
value = value.strip()
|
|
||||||
value = html.escape(value)
|
|
||||||
# Regulärer Ausdruck, um URLs zu finden
|
|
||||||
url_pattern = re.compile(
|
|
||||||
r'https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:;%_\+.~#?&//=]*)'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Ersetze jede gefundene URL durch einen <a>-Tag
|
|
||||||
def replace_url(match):
|
|
||||||
url = match.group(0)
|
|
||||||
if not url.startswith(('http://', 'https://')):
|
|
||||||
url = 'http://' + url
|
|
||||||
return f'<a href="{url}" target="_blank">{match.group(0)}</a>'
|
|
||||||
|
|
||||||
value = url_pattern.sub(replace_url, value)
|
|
||||||
value = value.replace('\n', '<br>')
|
|
||||||
|
|
||||||
return value
|
|
||||||
|
|
||||||
import auth
|
|
||||||
import entry
|
|
||||||
import user
|
|
||||||
|
|
||||||
@app.route("/")
|
|
||||||
def template():
|
|
||||||
return render_template("index.html", name="Hmmm")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
app.run(debug=True)
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
# Source - https://stackoverflow.com/a
|
|
||||||
# Posted by brvoisin
|
|
||||||
# Retrieved 2025-12-13, License - CC BY-SA 4.0
|
|
||||||
|
|
||||||
"""
|
|
||||||
# Resize an animated GIF
|
|
||||||
Inspired from https://gist.github.com/skywodd/8b68bd9c7af048afcedcea3fb1807966
|
|
||||||
Useful links:
|
|
||||||
* https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html#saving
|
|
||||||
* https://stackoverflow.com/a/69850807
|
|
||||||
Example:
|
|
||||||
```
|
|
||||||
python resize_gif.py input.gif output.gif 400,300
|
|
||||||
```
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from PIL import Image
|
|
||||||
from PIL import ImageSequence
|
|
||||||
|
|
||||||
|
|
||||||
def resize_gif(input_image, output_path, max_size):
|
|
||||||
frames = list(_thumbnail_frames(input_image, max_size))
|
|
||||||
output_image = frames[0]
|
|
||||||
output_image.save(
|
|
||||||
output_path,
|
|
||||||
save_all=True,
|
|
||||||
append_images=frames[1:],
|
|
||||||
disposal=input_image.disposal_method,
|
|
||||||
**input_image.info,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _thumbnail_frames(image, max_size):
|
|
||||||
for frame in ImageSequence.Iterator(image):
|
|
||||||
new_frame = frame.copy()
|
|
||||||
new_frame.thumbnail(max_size, Image.Resampling.LANCZOS)
|
|
||||||
yield new_frame
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
max_size = [int(px) for px in sys.argv[3].split(",")] # "150,100" -> (150, 100)
|
|
||||||
resize_gif(sys.argv[1], sys.argv[2], max_size)
|
|
||||||
-41
@@ -1,41 +0,0 @@
|
|||||||
import smtplib, ssl, database, time
|
|
||||||
|
|
||||||
def send(subject, message, recipient, type):
|
|
||||||
hour_ago = int(time.time()) - 3600
|
|
||||||
count = database.fetch("SELECT COUNT(*) FROM email WHERE time > ?", [hour_ago])
|
|
||||||
if count[0][0] > 5:
|
|
||||||
return False
|
|
||||||
|
|
||||||
database.execute("INSERT INTO email (recipient, time, type) VALUES (?, ?, ?)", [
|
|
||||||
recipient,
|
|
||||||
int(time.time()),
|
|
||||||
type
|
|
||||||
])
|
|
||||||
|
|
||||||
smtp_server = "webspace29.do.de"
|
|
||||||
port = 587 # For starttls
|
|
||||||
sender_email = "system@bnd.wtf"
|
|
||||||
password = ""
|
|
||||||
message = f"""From: system@bnd.wtf\nSubject: {subject}
|
|
||||||
|
|
||||||
{message}"""
|
|
||||||
|
|
||||||
# Create a secure SSL context
|
|
||||||
context = ssl.create_default_context()
|
|
||||||
|
|
||||||
# Try to log in to server and send email
|
|
||||||
try:
|
|
||||||
server = smtplib.SMTP(smtp_server,port)
|
|
||||||
server.ehlo() # Can be omitted
|
|
||||||
server.starttls(context=context) # Secure the connection
|
|
||||||
server.ehlo() # Can be omitted
|
|
||||||
server.login(sender_email, password)
|
|
||||||
server.sendmail(sender_email, recipient, message)
|
|
||||||
# TODO: Send email here
|
|
||||||
except Exception as e:
|
|
||||||
# Print any error messages to stdout
|
|
||||||
print(e)
|
|
||||||
finally:
|
|
||||||
server.quit()
|
|
||||||
|
|
||||||
return True
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
<div id="headerbar">
|
|
||||||
<div class="headerbar-left">
|
|
||||||
{% if loggedin %}
|
|
||||||
<!--button hx-get="/auth/sessioninfo" hx-target="#main-frame">Username</button-->
|
|
||||||
<button onclick="if(confirm('Abmelden?')){location.href = '/auth/logout';}">Abmelden</button>
|
|
||||||
{% else %}
|
|
||||||
<button hx-get="/auth/login" hx-target="#main-frame">Anmelden</button>
|
|
||||||
<button hx-get="/auth/newuser" hx-target="#main-frame">Neuen Account erstellen</button>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
<div class="headerbar-right">
|
|
||||||
<a href="/static/docs/impressum.html">Impressum</a>
|
|
||||||
<a href="/static/docs/beitragsordnung.html">Beitragsordnung</a>
|
|
||||||
<a href="/static/docs/datenschutz.html">Datenschutz</a>
|
|
||||||
<a href="/static/docs/kontakt.html">Kontakt</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
<h2>Einloggen</h2>
|
|
||||||
<form hx-post="/auth/login" hx-target="#main-frame">
|
|
||||||
<input type="text" name="user" placeholder="Nutzername" value="{{ username }}"><br>
|
|
||||||
<input type="password" name="pass" placeholder="Passphrase"><br>
|
|
||||||
<label>Automatisch abmelden in:</label>
|
|
||||||
<select name="timeout">
|
|
||||||
<option value="1200">20 Minuten</option>
|
|
||||||
<option value="3600" selected="selected">1 Stunde</option>
|
|
||||||
<option value="18000">5 Stunden</option>
|
|
||||||
<option value="86400">24 Stunden</option>
|
|
||||||
<option value="31536000">1 Jahr</option>
|
|
||||||
</select>
|
|
||||||
<button type="submit">Anmelden</button>
|
|
||||||
<div>
|
|
||||||
{{response}}
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
<h1>Willkommen, {{username}}!</h1>
|
|
||||||
|
|
||||||
<button hx-get="/auth/logout" hx-swap="outerHTML">Abmelden...</button>
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
<h2>Neuen Account registrieren</h2>
|
|
||||||
<form hx-post="/auth/newuser" hx-target="#main-frame" method="post">
|
|
||||||
<input type="text" name="user" placeholder="Nutzername" value="{{ username }}"><br>
|
|
||||||
<input type="password" name="pass1" placeholder="Passphrase"><br>
|
|
||||||
<input type="password" name="pass2" placeholder="Passphrase wiederholen"><br>
|
|
||||||
<label><input type="checkbox" name="pass2"> Ich bin mit der <a href="">Datenschutzerklärung</a> einverstanden!</label>
|
|
||||||
<button type="submit">Account registrieren!</button>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
{% for r in responses %}
|
|
||||||
{{ r }} <br>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
<h2>Willkommen im Kontrollverlust!</h2>
|
|
||||||
<p>Du kannst dich nun als {{username}} <button hx-get="/auth/login" hx-target="#main-frame">Anmelden</button>
|
|
||||||
!</p>
|
|
||||||
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
Sessionid: {{ sessionid }} <br>
|
|
||||||
UID: {{ uid }} <br>
|
|
||||||
Created: {{ created_at | timestamp }} <br>
|
|
||||||
Expires: {{ expires | timestamp }} <br>
|
|
||||||
Description: {{ description }} <br>
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
|
|
||||||
<title>{% block title %}{% endblock %}</title>
|
|
||||||
|
|
||||||
<link
|
|
||||||
rel="stylesheet"
|
|
||||||
href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css"
|
|
||||||
integrity="sha512-NhSC1YmyruXifcj/KFRWoC561YpHpc5Jtzgvbuzx5VozKpWvQ+4nXhPdFgmx8xqexRcpAglTj9sIBWINXa8x5w=="
|
|
||||||
crossorigin="anonymous"
|
|
||||||
referrerpolicy="no-referrer"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="/static/css/main.css" />
|
|
||||||
|
|
||||||
<script
|
|
||||||
src="https://cdn.jsdelivr.net/npm/htmx.org@2.0.8/dist/htmx.min.js"
|
|
||||||
integrity="sha384-/TgkGk7p307TH7EXJDuUlgG3Ce1UVolAOFopFekQkkXihi5u/6OCvVKyz1W+idaz"
|
|
||||||
crossorigin="anonymous"
|
|
||||||
></script>
|
|
||||||
<script
|
|
||||||
src="https://code.jquery.com/jquery-3.7.1.min.js"
|
|
||||||
integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo="
|
|
||||||
crossorigin="anonymous"
|
|
||||||
></script>
|
|
||||||
<script src="/static/js/main.js"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="contentnotice">
|
|
||||||
<h1>Achtung!</h1>
|
|
||||||
<h3>Kontrollverlust</h3>
|
|
||||||
<div>
|
|
||||||
<p>Dieses Forum richtet sich ausschließlich an <strong>erwachsene Besucher:innen</strong>.</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Die Inhalte könnten <strong>explizite Sprache, anstößige Themen, gewalthaltige Äußerungen, sowie pornografische Darstellungen in Text-, Zeichnungs- oder angedeuteter Form</strong> enthalten.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<strong>Fotografische Nacktheit, reale Gewalt, Gore oder NSFL-Inhalte sind verboten</strong> – dennoch können Diskussionen und Beiträge für sensible Personen belastend sein.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Wenn du mit solchen Inhalten nicht konfrontiert werden möchtest, <a href="https://optical.toys/">verlasse die Seite jetzt</a>. Durch das Fortsetzen erklärst du dich damit einverstanden, dass du die Inhalte auf eigene Verantwortung nutzt.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<button onclick="location.href='https://optical.toys/'" >Ich möchte lieber etwas anderes sehen!</button>
|
|
||||||
<button onclick="setCookie('agreedToWarning', 'True', '1'); $('#contentnotice').hide()" >Ich bin erwachsen und verstehe die Warnung – weiter</button>
|
|
||||||
<br style="margin: 30px;">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div hx-get="/auth/headerbar" hx-trigger="load"></div>
|
|
||||||
<div class="htmx-indicator" id="loading"></div>
|
|
||||||
<h1>Kontrollverlust</h1>
|
|
||||||
<br />
|
|
||||||
<span> Verbuggte scheise hier. Komm hack mich doch, trau dich! </span>
|
|
||||||
<br />
|
|
||||||
<span id="uptime-counter"></span>
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
|
|
||||||
<div id="main-frame" hx-indicator="#loading">
|
|
||||||
{% block content %}{% endblock %}
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
|
||||||
<title>Database</title>
|
|
||||||
<link rel="stylesheet" href="style.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
{% for tablename in tables %}
|
|
||||||
<a href="/database/{{ tablename[0] }}">{{ tablename[0] }}</a><br>
|
|
||||||
{% endfor %}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
<table>
|
|
||||||
{% for row in data %}
|
|
||||||
<tr>
|
|
||||||
{% for column in row %}
|
|
||||||
<td>{{ column }}</td>
|
|
||||||
{% endfor %}
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</table>
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
<form hx-post="/entry/create" hx-target="#entry-rendertarget" method="post" enctype="multipart/form-data">
|
|
||||||
<textarea name="content"></textarea>
|
|
||||||
<input type="file" name="file">
|
|
||||||
<button type="submit">Beitrag erstellen!</button>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
{% for r in responses %}
|
|
||||||
{{ r }} <br>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
{% for p in posts %}
|
|
||||||
|
|
||||||
<div class="entry">
|
|
||||||
<div class="meta">{{ p[6] }} - {{ p[3] | timestamp }}<br /></div>
|
|
||||||
{% if p[4] != '' %}
|
|
||||||
<div class="content">
|
|
||||||
<p>{{ p[4] | content | safe}}</p>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% if p[5] != 'none' %}
|
|
||||||
<img src="/{{ p[5] }}" />
|
|
||||||
{% endif %}
|
|
||||||
<div
|
|
||||||
class="interactions"
|
|
||||||
id="int-{{p[1]}}"
|
|
||||||
hx-get="/entry/{{p[1]}}/interactions/none"
|
|
||||||
hx-trigger="load"
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
<hr />
|
|
||||||
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
<div
|
|
||||||
id="infinite-scroll-indicator"
|
|
||||||
hx-get="/entry/feed/{{page+1}}"
|
|
||||||
hx-trigger="revealed"
|
|
||||||
hx-swap="outerHTML"
|
|
||||||
>
|
|
||||||
<h2>Loading more...</h2>
|
|
||||||
</div>
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
<button hx-get="/entry/{{pid}}/interactions/left" hx-target="#int-{{pid}}">Links</button>
|
|
||||||
<span>{{stats}}</span>
|
|
||||||
<button hx-get="/entry/{{pid}}/interactions/right" hx-target="#int-{{pid}}">Rechts</button>
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
<button onclick="$('#auth-visible').show()" hx-get="/auth/login" hx-target="#auth-rendertarget">Anmelden</button>
|
|
||||||
<button onclick="$('#auth-visible').show()" hx-get="/auth/newuser" hx-target="#auth-rendertarget">Account erstellen</button>
|
|
||||||
<div id="auth-visible">
|
|
||||||
<div id="auth-rendertarget"></div>
|
|
||||||
<button onclick="$('#auth-visible').hide()">Abbrechen</button>
|
|
||||||
</div>
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
{% extends "baseof.html" %}
|
|
||||||
|
|
||||||
{% block title %}Kontrollverlust{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
|
|
||||||
<div id="auth-rendertarget"></div>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<div hx-get="/entry/create" hx-trigger="load" id="entry-rendertarget"></div>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<div hx-get="/entry/feed/0" hx-trigger="load" hx-indicator="none"></div>
|
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
|
||||||
<title>My new Site!</title>
|
|
||||||
<link rel="stylesheet" href="style.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Upload new File</h1>
|
|
||||||
<span>{{ state }}</span>
|
|
||||||
<form method="post" enctype="multipart/form-data">
|
|
||||||
<input type=file name=file>
|
|
||||||
<input type=submit value=Upload>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
{% for id in imagedata %}
|
|
||||||
{{ id }}
|
|
||||||
{% endfor %}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
<div id="deleteuser-rendertarget">
|
|
||||||
<h2>Account dauerhaft löschen</h2>
|
|
||||||
<form hx-post="/user/delete" hx-target="#deleteuser-rendertarget" method="post">
|
|
||||||
<input type="password" name="pass1" placeholder="Passphrase"><br>
|
|
||||||
<button type="submit">Account Löschen!</button>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
{% for r in responses %}
|
|
||||||
{{ r }} <br>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
{% extends "baseof.html" %}
|
|
||||||
{% block content %}
|
|
||||||
|
|
||||||
Username: {{ username }} <br>
|
|
||||||
ID: {{ uid }} <br>
|
|
||||||
Profil erstellt: {{ created_at | timestamp }} <br>
|
|
||||||
|
|
||||||
<div hx-get="/user/delete" hx-trigger="load"></div>
|
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
from flask import render_template, request, make_response, redirect
|
|
||||||
import database, re, bcrypt, secrets, time
|
|
||||||
from auth import get_session
|
|
||||||
|
|
||||||
from app import app
|
|
||||||
|
|
||||||
@app.route("/u/<username>")
|
|
||||||
def userpage(username):
|
|
||||||
return "Page of " + username
|
|
||||||
|
|
||||||
@app.route("/user/info")
|
|
||||||
def userinfo():
|
|
||||||
sessioninfo = get_session(request)
|
|
||||||
|
|
||||||
if not sessioninfo:
|
|
||||||
return "Melde dich an, um Einstellungen zu ändern!"
|
|
||||||
|
|
||||||
uid = sessioninfo[0]
|
|
||||||
|
|
||||||
userdata = database.fetch("SELECT created_at, last_login, username FROM user WHERE uid=?", [uid])[0]
|
|
||||||
|
|
||||||
return render_template("user/userinfo.html", username=userdata[2], uid=uid, created_at=userdata[0])
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/user/delete")
|
|
||||||
def userdelete():
|
|
||||||
return render_template("user/delete.html")
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
import database, re, bcrypt, secrets, time
|
|
||||||
|
|
||||||
def new(username, password):
|
|
||||||
uid = secrets.randbelow(99999999)
|
|
||||||
created_at = int(time.time())
|
|
||||||
|
|
||||||
passsalt = bcrypt.gensalt()
|
|
||||||
passhash = bcrypt.hashpw(password.encode("utf8"), passsalt)
|
|
||||||
|
|
||||||
database.execute("""INSERT INTO user
|
|
||||||
(uid, created_at, username, password) VALUES
|
|
||||||
(?, ?, ?, ?)""",
|
|
||||||
(uid, created_at, username, passhash))
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
def from_uid():
|
|
||||||
pass
|
|
||||||
|
|
||||||
def id_from_username(username):
|
|
||||||
query = database.fetch("SELECT id FROM user WHERE username=?", [username], False)
|
|
||||||
if query:
|
|
||||||
return query[0]
|
|
||||||
else:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def id_from_session(session):
|
|
||||||
sessioninfo = database.fetch("""SELECT uid, expires
|
|
||||||
FROM session
|
|
||||||
WHERE value=?;""",
|
|
||||||
[sessionid], False)
|
|
||||||
|
|
||||||
if not sessioninfo:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
# if int(sessioninfo[2] > int(time.time())):
|
|
||||||
# return 0 # session expired
|
|
||||||
|
|
||||||
|
|
||||||
pass
|
|
||||||
|
|
||||||
def exists():
|
|
||||||
# count from database
|
|
||||||
pass
|
|
||||||
|
|
||||||
def authenticate(password):
|
|
||||||
# verify password with stored hash
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get_info() -> dict:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def set_username(newname):
|
|
||||||
# update username
|
|
||||||
pass
|
|
||||||
|
|
||||||
def set_password(newpass):
|
|
||||||
# update password
|
|
||||||
pass
|
|
||||||
|
|
||||||
def delete():
|
|
||||||
# delete database entry
|
|
||||||
pass
|
|
||||||
Reference in New Issue
Block a user