fuck you i aint commiting with actual messages
This commit is contained in:
parent
af461666f2
commit
2616417011
38
app.py
38
app.py
|
@ -5,16 +5,28 @@ import os
|
||||||
import hashlib
|
import hashlib
|
||||||
import base64
|
import base64
|
||||||
import dotenv
|
import dotenv
|
||||||
|
from flask_limiter import Limiter
|
||||||
|
from flask_limiter.util import get_remote_address
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
dotenv.load_dotenv()
|
dotenv.load_dotenv()
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.secret_key = os.getenv("SECRET")
|
app.secret_key = os.getenv("SECRET")
|
||||||
|
|
||||||
USERS_FILE = "users.txt"
|
USERS_FILE = "users.txt"
|
||||||
DATA_DIR = "notes"
|
DATA_DIR = "notes"
|
||||||
os.makedirs(DATA_DIR, exist_ok=True)
|
os.makedirs(DATA_DIR, exist_ok=True)
|
||||||
|
|
||||||
|
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 # 16 KB
|
||||||
|
|
||||||
|
limiter = Limiter(key_func=get_remote_address)
|
||||||
|
limiter.init_app(app)
|
||||||
|
|
||||||
|
@app.errorhandler(413)
|
||||||
|
def payload_too_large(e):
|
||||||
|
return "someone call caseoh, he's got competition — I think his number is 413?", 413
|
||||||
|
|
||||||
|
|
||||||
# === UTILS ===
|
# === UTILS ===
|
||||||
|
|
||||||
|
@ -37,6 +49,7 @@ def get_key_for_user(user, password):
|
||||||
# === ROUTES ===
|
# === ROUTES ===
|
||||||
|
|
||||||
@app.route("/api/<note>", methods=["GET", "POST"])
|
@app.route("/api/<note>", methods=["GET", "POST"])
|
||||||
|
@limiter.limit("100 per hour", per_method=True, key_func=get_remote_address)
|
||||||
def api(note):
|
def api(note):
|
||||||
user = get_user()
|
user = get_user()
|
||||||
key = session.get("key")
|
key = session.get("key")
|
||||||
|
@ -49,22 +62,35 @@ def api(note):
|
||||||
if request.method == "GET":
|
if request.method == "GET":
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
return "", 200
|
return "", 200
|
||||||
with open(path, "rb") as f:
|
try:
|
||||||
try:
|
with open(path, "rb") as f:
|
||||||
return fernet.decrypt(f.read()).decode(), 200
|
return fernet.decrypt(f.read()).decode(), 200
|
||||||
except Exception:
|
except Exception:
|
||||||
return "Corrupted note or invalid key", 500
|
return "Corrupted note or invalid key", 500
|
||||||
|
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
if not data or "content" not in data:
|
if not data or "content" not in data:
|
||||||
return "Bad request", 400
|
return "Bad request", 400
|
||||||
|
|
||||||
|
# Check if this is a new note
|
||||||
|
is_new = not os.path.exists(path)
|
||||||
|
|
||||||
|
if is_new:
|
||||||
|
now = time.time()
|
||||||
|
record = session.setdefault("note_creations", [])
|
||||||
|
# Clean up old timestamps
|
||||||
|
record = [t for t in record if now - t < 3600]
|
||||||
|
if len(record) >= 10:
|
||||||
|
return "Slow down, Picasso — max 10 new notes per hour", 429
|
||||||
|
record.append(now)
|
||||||
|
session["note_creations"] = record
|
||||||
|
|
||||||
ciphertext = fernet.encrypt(data["content"].encode())
|
ciphertext = fernet.encrypt(data["content"].encode())
|
||||||
with open(path, "wb") as f:
|
with open(path, "wb") as f:
|
||||||
f.write(ciphertext)
|
f.write(ciphertext)
|
||||||
return jsonify({"status": "saved"})
|
return jsonify({"status": "saved"})
|
||||||
|
|
||||||
|
|
||||||
@app.route("/n/<note>")
|
@app.route("/n/<note>")
|
||||||
@app.route("/notes/<note>")
|
@app.route("/notes/<note>")
|
||||||
def serve_note(note):
|
def serve_note(note):
|
||||||
|
|
|
@ -1,11 +1,19 @@
|
||||||
const textarea = document.getElementById("editor");
|
const textarea = document.getElementById("editor");
|
||||||
const status = document.getElementById("status");
|
const status = document.getElementById("status");
|
||||||
|
const error = document.getElementById("error");
|
||||||
|
|
||||||
fetch("/api/" + noteName)
|
fetch("/api/" + noteName)
|
||||||
.then(res => res.ok ? res.text() : "")
|
.then(res => {
|
||||||
|
if (!res.ok) throw new Error("Failed to load note. Status: " + res.status);
|
||||||
|
return res.text();
|
||||||
|
})
|
||||||
.then(text => {
|
.then(text => {
|
||||||
textarea.value = text;
|
textarea.value = text;
|
||||||
status.textContent = "Loaded";
|
status.textContent = "Loaded";
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
error.textContent = "Error loading note: " + err.message;
|
||||||
|
status.textContent = "Load failed";
|
||||||
});
|
});
|
||||||
|
|
||||||
let timeout;
|
let timeout;
|
||||||
|
@ -21,17 +29,21 @@ textarea.addEventListener("input", () => {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ content: text })
|
body: JSON.stringify({ content: text })
|
||||||
}).then(res => {
|
})
|
||||||
|
.then(async res => {
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
last = text;
|
last = text;
|
||||||
status.textContent = "Saved";
|
status.textContent = "Saved";
|
||||||
} else {
|
} else {
|
||||||
status.textContent = "Save failed";
|
const msg = await res.text();
|
||||||
|
status.textContent = "Save failed: " + msg;
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
status.textContent = "Save failed: " + err.message;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
status.textContent = "No changes";
|
status.textContent = "No changes";
|
||||||
}
|
}
|
||||||
}, 500);
|
}, 500);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<div id="error"></div>
|
||||||
<textarea id="editor" placeholder="Type here!"></textarea>
|
<textarea id="editor" placeholder="Type here!"></textarea>
|
||||||
<div id="status">Loading...</div>
|
<div id="status">Loading...</div>
|
||||||
<script>
|
<script>
|
||||||
|
|
Loading…
Reference in a new issue