Added Content Notice
This commit is contained in:
@@ -0,0 +1,223 @@
|
|||||||
|
from flask import render_template, request, make_response, redirect
|
||||||
|
import database, re, bcrypt, secrets, time
|
||||||
|
|
||||||
|
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.")
|
||||||
|
usernamequery = database.fetch("SELECT id FROM user WHERE username=?", [username])
|
||||||
|
if len(usernamequery) != 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(f"Passwort und Passwortbestätigung sind ungleich.")
|
||||||
|
|
||||||
|
if len(pass1) < 10:
|
||||||
|
success = False
|
||||||
|
responses.append(f"Das Passwort ist kürzer als 10 Zeichen.")
|
||||||
|
|
||||||
|
if not success:
|
||||||
|
return render_template("auth/register.html",
|
||||||
|
responses=responses,
|
||||||
|
username=username)
|
||||||
|
|
||||||
|
uid = secrets.randbelow(99999999)
|
||||||
|
created_at = int(time.time())
|
||||||
|
# emailcode = secrets.randbelow(999999)
|
||||||
|
passsalt = bcrypt.gensalt()
|
||||||
|
passhash = bcrypt.hashpw(pass1.encode("utf8"), passsalt)
|
||||||
|
|
||||||
|
database.execute("""INSERT INTO user
|
||||||
|
(uid, created_at, username, password) VALUES
|
||||||
|
(?, ?, ?, ?)""",
|
||||||
|
(uid, created_at, username, passhash))
|
||||||
|
|
||||||
|
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
|
||||||
@@ -5,6 +5,37 @@ import secrets, time, os, database
|
|||||||
from auth import get_session
|
from auth import get_session
|
||||||
from app import app
|
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'])
|
@app.route("/entry/feed/<page>", methods=['GET'])
|
||||||
def feed(page):
|
def feed(page):
|
||||||
page = int(page, 0)
|
page = int(page, 0)
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ h1 {
|
|||||||
background-color: white;
|
background-color: white;
|
||||||
display: inline-block; }
|
display: inline-block; }
|
||||||
|
|
||||||
|
p {
|
||||||
|
line-height: 1.5; }
|
||||||
|
|
||||||
span {
|
span {
|
||||||
background-color: white; }
|
background-color: white; }
|
||||||
|
|
||||||
@@ -36,6 +39,24 @@ hr {
|
|||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
margin: 0px; }
|
margin: 0px; }
|
||||||
|
|
||||||
|
#contentnotice {
|
||||||
|
background-color: white;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10pt; }
|
||||||
|
#contentnotice h1, #contentnotice h3 {
|
||||||
|
margin: 6pt; }
|
||||||
|
#contentnotice div {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 500px; }
|
||||||
|
|
||||||
.entry .meta {
|
.entry .meta {
|
||||||
color: gray;
|
color: gray;
|
||||||
margin: 2pt 4pt;
|
margin: 2pt 4pt;
|
||||||
@@ -48,6 +69,10 @@ hr {
|
|||||||
.entry img {
|
.entry img {
|
||||||
max-width: 100%; }
|
max-width: 100%; }
|
||||||
|
|
||||||
|
.entry .interactions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around; }
|
||||||
|
|
||||||
#loading {
|
#loading {
|
||||||
background-color: #fff9;
|
background-color: #fff9;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
@@ -30,6 +30,10 @@ h1 {
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
span {
|
span {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
}
|
}
|
||||||
@@ -49,6 +53,29 @@ hr {
|
|||||||
margin: 0px;
|
margin: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#contentnotice {
|
||||||
|
background-color: white;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10pt;
|
||||||
|
|
||||||
|
h1, h3 {
|
||||||
|
margin: 6pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
div {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 500px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.entry {
|
.entry {
|
||||||
.meta {
|
.meta {
|
||||||
color: gray;
|
color: gray;
|
||||||
@@ -64,6 +91,11 @@ hr {
|
|||||||
img {
|
img {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.interactions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#loading {
|
#loading {
|
||||||
|
|||||||
+11
-1
@@ -1,7 +1,6 @@
|
|||||||
$(() => {
|
$(() => {
|
||||||
// $('#auth-visible').hide()
|
// $('#auth-visible').hide()
|
||||||
|
|
||||||
|
|
||||||
// Startdatum festlegen (Beispiel: 1. Januar 2025, 00:00:00 Uhr)
|
// Startdatum festlegen (Beispiel: 1. Januar 2025, 00:00:00 Uhr)
|
||||||
const startDate = new Date('2025-12-13T12:44:00').getTime();
|
const startDate = new Date('2025-12-13T12:44:00').getTime();
|
||||||
|
|
||||||
@@ -23,4 +22,15 @@ $(() => {
|
|||||||
updateCounter();
|
updateCounter();
|
||||||
setInterval(updateCounter, 1000);
|
setInterval(updateCounter, 1000);
|
||||||
|
|
||||||
|
let agreedToWarning = document.cookie.includes("agreedToWarning=True")
|
||||||
|
if(agreedToWarning) {
|
||||||
|
$("#contentnotice").hide()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function setCookie(cname, cvalue, exdays) {
|
||||||
|
const d = new Date();
|
||||||
|
d.setTime(d.getTime() + (exdays*24*60*60*1000));
|
||||||
|
let expires = "expires="+ d.toUTCString();
|
||||||
|
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
|
||||||
|
}
|
||||||
|
|||||||
+21
-2
@@ -27,10 +27,29 @@
|
|||||||
crossorigin="anonymous"
|
crossorigin="anonymous"
|
||||||
></script>
|
></script>
|
||||||
<script src="/static/js/main.js"></script>
|
<script src="/static/js/main.js"></script>
|
||||||
|
|
||||||
<script></script>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<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 hx-get="/auth/headerbar" hx-trigger="load"></div>
|
||||||
<div class="htmx-indicator" id="loading"></div>
|
<div class="htmx-indicator" id="loading"></div>
|
||||||
<h1>Kontrollverlust</h1>
|
<h1>Kontrollverlust</h1>
|
||||||
|
|||||||
@@ -1,23 +1,29 @@
|
|||||||
{% for p in posts %}
|
{% for p in posts %}
|
||||||
|
|
||||||
<div class="entry">
|
<div class="entry">
|
||||||
<div class="meta">
|
<div class="meta">{{ p[6] }} - {{ p[3] | timestamp }} {{ p[1] }}<br /></div>
|
||||||
{{ p[6] }} - {{ p[3] | timestamp }} <br>
|
|
||||||
</div>
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<p>{{ p[4] }}</p>
|
<p>{{ p[4] }}</p>
|
||||||
</div>
|
</div>
|
||||||
{% if p[5] != 'none' %}
|
{% if p[5] != 'none' %}
|
||||||
<img src="/{{ p[5] }}">
|
<img src="/{{ p[5] }}" />
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<div
|
||||||
|
class="interactions"
|
||||||
|
id="int-{{p[1]}}"
|
||||||
|
hx-get="/entry/{{p[1]}}/interactions/none"
|
||||||
|
hx-trigger="load"
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr />
|
||||||
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
<div id="infinite-scroll-indicator"
|
<div
|
||||||
|
id="infinite-scroll-indicator"
|
||||||
hx-get="/entry/feed/{{page+1}}"
|
hx-get="/entry/feed/{{page+1}}"
|
||||||
hx-trigger="revealed"
|
hx-trigger="revealed"
|
||||||
hx-swap="outerHTML">
|
hx-swap="outerHTML"
|
||||||
|
>
|
||||||
<h2>Loading more...</h2>
|
<h2>Loading more...</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<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>
|
||||||
Reference in New Issue
Block a user