shareable upload tokens

This commit is contained in:
Q
2023-08-28 22:13:22 +03:00
parent 4629bf2976
commit 3b69ce7ae1
6 changed files with 145 additions and 8 deletions

View File

@@ -27,6 +27,9 @@ from utils.files import (
db_get_file, db_get_file,
db_delete_file, db_delete_file,
db_maintenance, db_maintenance,
validate_upload_token,
invalidate_upload_token,
new_upload_token,
) )
import logging import logging
@@ -97,8 +100,13 @@ def upload():
return "Name required", 500 return "Name required", 500
safe_filename = secure_filename(name) safe_filename = secure_filename(name)
secret = request.headers.get("Secret", "") secret = request.headers.get("Secret", "")
if secret != app.config["ACCESS_TOKEN"]: upload_token = request.headers.get("Token", False)
return "Error", 401 if upload_token:
if not validate_upload_token(upload_token):
return "Error", 401
else:
if secret != app.config["ACCESS_TOKEN"]:
return "Error", 401
max_dl = request.headers.get("Max-Downloads", app.config["DEFAULT_MAX_DL"]) max_dl = request.headers.get("Max-Downloads", app.config["DEFAULT_MAX_DL"])
expires = int(time.time()) + int(app.config["DEFAULT_EXPIRE"]) expires = int(time.time()) + int(app.config["DEFAULT_EXPIRE"])
if "Expires-days" in request.headers: if "Expires-days" in request.headers:
@@ -142,9 +150,34 @@ def upload():
app.logger.info( app.logger.info(
f"Upload: {download_url} MaxDL:{max_dl} Exp:{file_date_human(expires)}" f"Upload: {download_url} MaxDL:{max_dl} Exp:{file_date_human(expires)}"
) )
if upload_token:
invalidate_upload_token(upload_token)
return "File uploaded\n%s\n" % (download_url,), 200 return "File uploaded\n%s\n" % (download_url,), 200
@app.route("/new_token", methods=["GET"])
def upload_token():
"""
Get JSON of file details. Size, added date, download times, etc.
curl -fL -w "\n" \
-H "Expires-Days: 14" \
-H "Secret: dff789f0bbe8183d32542" \
"$FLASK_PUBLIC_URL"/new_token
"""
secret = request.headers.get("Secret", "")
if secret != app.config["ACCESS_TOKEN"]:
return "Error", 401
expires = int(time.time()) + int(app.config["DEFAULT_EXPIRE"])
if "Expires-days" in request.headers:
expires = int(time.time()) + 24 * 3600 * int(
request.headers.get("Expires-days")
)
token = new_upload_token(expires)
return token, 200
@app.route("/details/<token>/<name>", methods=["GET"]) @app.route("/details/<token>/<name>", methods=["GET"])
def details(token, name): def details(token, name):
""" """

View File

@@ -12,3 +12,11 @@ CREATE TABLE IF NOT EXISTS files (
passhash text passhash text
); );
EOF EOF
cat <<'EOF' | sqlite3 "$1"
CREATE TABLE IF NOT EXISTS upload_tokens (
token text PRIMARY KEY,
added integer NOT NULL,
expires integer NOT NULL
);
EOF

View File

@@ -10,7 +10,7 @@ _help() {
(w)rite Save to share, read from stdin/file/folder (w)rite Save to share, read from stdin/file/folder
(d)elete Delete an entry (d)elete Delete an entry
show Show details on file show Show details on file
upload Get URL for uploads [no arguments] upload_token Get URL for uploads ( -d expiry )
autocomplete Get Bash autocompletion script autocomplete Get Bash autocompletion script
update Update self update Update self
@@ -154,6 +154,27 @@ _maintain() {
"$MFL_ROOTURL"/maintenance "$MFL_ROOTURL"/maintenance
} }
_upload_token() {
token=$( curl -fL -s \
-H "Expires-Days: $MAXDAYS" \
-H "Secret: $MFL_TOKEN" \
"$MFL_ROOTURL"/new_token )
cat <<EOF
curl -fL -g --upload-file "file_to_upload.ext" \\
-H "Name: name_to_store_as.ext" \\
-H "Max-Downloads: 10" \\
-H "Expires-Days: 10" \\
-H "Token: $token" \\
-H "Password: lock_with_password" \\
"$MFL_ROOTURL"/upload
curl -fL -g --upload-file "file_to_upload.ext" \\
-H "Name: name_to_store_as.ext" \\
-H "Token: $token" \\
"$MFL_ROOTURL"/upload
EOF
}
_msg() { _msg() {
echo "$@" >&2 echo "$@" >&2
@@ -166,7 +187,7 @@ _get_completer() {
local curr_arg local curr_arg
curr_arg=${COMP_WORDS[COMP_CWORD]} curr_arg=${COMP_WORDS[COMP_CWORD]}
if [[ $COMP_CWORD -eq 1 ]]; then if [[ $COMP_CWORD -eq 1 ]]; then
COMPREPLY=( $(compgen -W "help autocomplete list write delete show update" -- $curr_arg ) ); COMPREPLY=( $(compgen -W "help autocomplete list write delete show update upload_token" -- $curr_arg ) );
fi fi
if [[ $COMP_CWORD -eq 2 ]]; then if [[ $COMP_CWORD -eq 2 ]]; then
case ${COMP_WORDS[$(( $COMP_CWORD - 1 ))]} in case ${COMP_WORDS[$(( $COMP_CWORD - 1 ))]} in
@@ -227,7 +248,7 @@ fi
[[ "$1" = "simplelist" ]] && { CMD=simple_list; ARG1=$CMD; } [[ "$1" = "simplelist" ]] && { CMD=simple_list; ARG1=$CMD; }
[[ "$1" = "autocomplete" ]] && { _get_completer; } [[ "$1" = "autocomplete" ]] && { _get_completer; }
[[ "$1" = "update" ]] && { _update_client; } [[ "$1" = "update" ]] && { _update_client; }
[[ "$1" = "upload" ]] && { _upload_url; } [[ "$1" = "upload_token" ]] && { _upload_token; exit; }
[[ "$1" = "self" ]] && { _self_url; } [[ "$1" = "self" ]] && { _self_url; }
[[ "$1" = "h" || "$1" = "help" ]] && CMD=help [[ "$1" = "h" || "$1" = "help" ]] && CMD=help

View File

@@ -4,7 +4,7 @@ from flask import current_app as app
import time import time
import sqlite3 import sqlite3
import shutil import shutil
from .misc import file_size_human, file_date_human, file_time_human from .misc import file_size_human, file_date_human, file_time_human, random_token
def get_db(): def get_db():
@@ -136,6 +136,28 @@ def db_maintenance():
c.executemany("DELETE FROM files WHERE token = ?", deleted_tokens) c.executemany("DELETE FROM files WHERE token = ?", deleted_tokens)
if c.rowcount > 0: if c.rowcount > 0:
db.commit() db.commit()
# === Delete upload token entries where expiry is used up ===
db, c = get_db()
rows = db.execute(
"""
select
token
from upload_tokens
where expires < ?
""",
(int(time.time()),),
)
deleted_tokens = []
for row in rows:
deleted_tokens.append((row[0],))
messages.append(f"Deleting upload_token {row[0]}")
if len(deleted_tokens) > 0:
db, c = get_db()
c.executemany("DELETE FROM upload_tokens WHERE token = ?", deleted_tokens)
if c.rowcount > 0:
db.commit()
messages.append("Maintenance done.") messages.append("Maintenance done.")
return "\n".join(messages) return "\n".join(messages)
@@ -199,3 +221,41 @@ def file_list():
def file_list_simple(): def file_list_simple():
return [f"{r[0]}/{r[1]}" for r in db_get_files()] return [f"{r[0]}/{r[1]}" for r in db_get_files()]
def new_upload_token(expires):
db, c = get_db()
token = random_token(32)
c.execute(
"""
insert into upload_tokens(token,added,expires)
values (?,?,?)
""",
(token, int(time.time()), expires),
)
if c.rowcount > 0:
db.commit()
return token
def validate_upload_token(token):
db, c = get_db()
return db.execute(
"""
SELECT 1
FROM upload_tokens WHERE token = ? AND expires > ?
""",
(token, int(time.time())),
).fetchone()
def invalidate_upload_token(token):
db, c = get_db()
c.execute(
"""
DELETE FROM upload_tokens WHERE token = ?
""",
(token,),
)
if c.rowcount > 0:
db.commit()

View File

@@ -6,8 +6,8 @@ import passlib.hash
VALID_TOKEN_CHARS = string.digits + string.ascii_letters VALID_TOKEN_CHARS = string.digits + string.ascii_letters
def random_token(): def random_token(n=8):
return "".join(secrets.choice(VALID_TOKEN_CHARS) for i in range(8)) return "".join(secrets.choice(VALID_TOKEN_CHARS) for i in range(n))
def file_date_human(num): def file_date_human(num):

View File

@@ -298,11 +298,26 @@ function t16-download-password() {
-H "Password: wrongpassword" \ -H "Password: wrongpassword" \
"$FLASK_PUBLIC_URL"/d/"$token_name" > downloaded "$FLASK_PUBLIC_URL"/d/"$token_name" > downloaded
file downloaded file downloaded
}
function t17-new-upload_token() {
token=$( curl -fL -w "\n" \
-H "Secret: $FLASK_ACCESS_TOKEN" \
-H "Expiry-days: 1" \
"$FLASK_PUBLIC_URL"/new_token )
echo $token
cat "$IMAGE" | \
curl -fL -w "\n" -F file="@-" -X POST \
-H "Name: $IMAGE" \
-H "Token: $token" \
"$FLASK_PUBLIC_URL"/upload
} }
_getlist() { _getlist() {
declare -F | awk '{ print $3 }' | grep -v ^_ declare -F | awk '{ print $3 }' | grep -v ^_
echo exit echo exit