mirror of
https://github.com/mattintech/simplefileupload-server.git
synced 2026-07-11 15:31:52 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a9d816717f | |||
| f7a584748f |
28
src/app.py
28
src/app.py
@@ -14,22 +14,30 @@ from controllers.admin_controller import (
|
|||||||
update_config, regenerate_key
|
update_config, regenerate_key
|
||||||
)
|
)
|
||||||
from views import views
|
from views import views
|
||||||
from models import init_db, migrate_from_json, migrate_client_key_to_db
|
from models import init_db, migrate_from_json, migrate_client_key_to_db, get_secret_key
|
||||||
|
|
||||||
app = Flask(__name__)
|
# Initialize database first (before creating Flask app)
|
||||||
app.config['UPLOAD_FOLDER'] = 'tmp/uploads'
|
|
||||||
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', os.urandom(24).hex())
|
|
||||||
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=24)
|
|
||||||
|
|
||||||
if not os.path.exists(app.config['UPLOAD_FOLDER']):
|
|
||||||
os.makedirs(app.config['UPLOAD_FOLDER'])
|
|
||||||
|
|
||||||
# Initialize database
|
|
||||||
log.i("Initializing database...")
|
log.i("Initializing database...")
|
||||||
init_db()
|
init_db()
|
||||||
migrate_from_json()
|
migrate_from_json()
|
||||||
migrate_client_key_to_db()
|
migrate_client_key_to_db()
|
||||||
|
|
||||||
|
# Get secret key from database
|
||||||
|
secret_key = get_secret_key()
|
||||||
|
if not secret_key:
|
||||||
|
log.e("Failed to get secret key from database!")
|
||||||
|
secret_key = os.urandom(24).hex() # Fallback only
|
||||||
|
else:
|
||||||
|
log.i(f"Loaded secret key from database (length: {len(secret_key)})")
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.config['UPLOAD_FOLDER'] = 'tmp/uploads'
|
||||||
|
app.config['SECRET_KEY'] = secret_key
|
||||||
|
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=24)
|
||||||
|
|
||||||
|
if not os.path.exists(app.config['UPLOAD_FOLDER']):
|
||||||
|
os.makedirs(app.config['UPLOAD_FOLDER'])
|
||||||
|
|
||||||
# Run cleanup on startup
|
# Run cleanup on startup
|
||||||
cleanup_expired_files()
|
cleanup_expired_files()
|
||||||
cleanup_abandoned_uploads()
|
cleanup_abandoned_uploads()
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ def setup_admin_totp():
|
|||||||
|
|
||||||
# Convert QR code to base64
|
# Convert QR code to base64
|
||||||
buffer = io.BytesIO()
|
buffer = io.BytesIO()
|
||||||
img.save(buffer, format='PNG')
|
img.save(buffer) # PyPNG doesn't use format parameter
|
||||||
qr_base64 = base64.b64encode(buffer.getvalue()).decode()
|
qr_base64 = base64.b64encode(buffer.getvalue()).decode()
|
||||||
|
|
||||||
return render_template('admin_setup_totp.html',
|
return render_template('admin_setup_totp.html',
|
||||||
@@ -113,7 +113,7 @@ def setup_admin_totp():
|
|||||||
qr.make(fit=True)
|
qr.make(fit=True)
|
||||||
img = qr.make_image(fill_color="black", back_color="white")
|
img = qr.make_image(fill_color="black", back_color="white")
|
||||||
buffer = io.BytesIO()
|
buffer = io.BytesIO()
|
||||||
img.save(buffer, format='PNG')
|
img.save(buffer)
|
||||||
qr_base64 = base64.b64encode(buffer.getvalue()).decode()
|
qr_base64 = base64.b64encode(buffer.getvalue()).decode()
|
||||||
|
|
||||||
return render_template('admin_setup_totp.html',
|
return render_template('admin_setup_totp.html',
|
||||||
@@ -137,7 +137,7 @@ def setup_admin_totp():
|
|||||||
qr.make(fit=True)
|
qr.make(fit=True)
|
||||||
img = qr.make_image(fill_color="black", back_color="white")
|
img = qr.make_image(fill_color="black", back_color="white")
|
||||||
buffer = io.BytesIO()
|
buffer = io.BytesIO()
|
||||||
img.save(buffer, format='PNG')
|
img.save(buffer)
|
||||||
qr_base64 = base64.b64encode(buffer.getvalue()).decode()
|
qr_base64 = base64.b64encode(buffer.getvalue()).decode()
|
||||||
|
|
||||||
return render_template('admin_setup_totp.html',
|
return render_template('admin_setup_totp.html',
|
||||||
@@ -287,7 +287,7 @@ def generate_config_qr(server_address, server_port, client_key):
|
|||||||
|
|
||||||
# Convert to base64
|
# Convert to base64
|
||||||
buffer = io.BytesIO()
|
buffer = io.BytesIO()
|
||||||
img.save(buffer, format='PNG')
|
img.save(buffer)
|
||||||
qr_base64 = base64.b64encode(buffer.getvalue()).decode()
|
qr_base64 = base64.b64encode(buffer.getvalue()).decode()
|
||||||
|
|
||||||
return qr_base64
|
return qr_base64
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ from .chunk_model import (
|
|||||||
from .config_model import (
|
from .config_model import (
|
||||||
get_config,
|
get_config,
|
||||||
get_client_key,
|
get_client_key,
|
||||||
|
get_secret_key,
|
||||||
init_config,
|
init_config,
|
||||||
update_server_address,
|
update_server_address,
|
||||||
update_server_port,
|
update_server_port,
|
||||||
|
|||||||
@@ -81,6 +81,9 @@ def add_chunk(upload_id, chunk_index):
|
|||||||
with get_db() as conn:
|
with get_db() as conn:
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
# Use IMMEDIATE transaction to prevent race conditions with parallel uploads
|
||||||
|
cursor.execute("BEGIN IMMEDIATE")
|
||||||
|
|
||||||
# Get current received_chunks
|
# Get current received_chunks
|
||||||
row = cursor.execute("""
|
row = cursor.execute("""
|
||||||
SELECT received_chunks FROM chunk_sessions WHERE upload_id = ?
|
SELECT received_chunks FROM chunk_sessions WHERE upload_id = ?
|
||||||
|
|||||||
@@ -10,18 +10,23 @@ def generate_client_key():
|
|||||||
return secrets.token_hex(32) # 32 bytes = 64 hex chars
|
return secrets.token_hex(32) # 32 bytes = 64 hex chars
|
||||||
|
|
||||||
|
|
||||||
|
def generate_secret_key():
|
||||||
|
"""Generate a random 64-character hex string for Flask SECRET_KEY"""
|
||||||
|
return secrets.token_hex(32) # 32 bytes = 64 hex chars
|
||||||
|
|
||||||
|
|
||||||
def get_config():
|
def get_config():
|
||||||
"""Get server configuration (singleton)
|
"""Get server configuration (singleton)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict: Configuration with client_key, server_address, server_port, timestamps
|
dict: Configuration with client_key, secret_key, server_address, server_port, timestamps
|
||||||
None: If config doesn't exist or error occurred
|
None: If config doesn't exist or error occurred
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
with get_db() as conn:
|
with get_db() as conn:
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
row = cursor.execute("""
|
row = cursor.execute("""
|
||||||
SELECT client_key, server_address, server_port, created_at, updated_at
|
SELECT client_key, secret_key, server_address, server_port, created_at, updated_at
|
||||||
FROM server_config
|
FROM server_config
|
||||||
WHERE id = 1
|
WHERE id = 1
|
||||||
""").fetchone()
|
""").fetchone()
|
||||||
@@ -45,11 +50,23 @@ def get_client_key():
|
|||||||
return config['client_key'] if config else None
|
return config['client_key'] if config else None
|
||||||
|
|
||||||
|
|
||||||
def init_config(client_key=None):
|
def get_secret_key():
|
||||||
"""Initialize server config with provided or generated key
|
"""Get the Flask secret key from database
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: The secret key
|
||||||
|
None: If config doesn't exist or error occurred
|
||||||
|
"""
|
||||||
|
config = get_config()
|
||||||
|
return config['secret_key'] if config else None
|
||||||
|
|
||||||
|
|
||||||
|
def init_config(client_key=None, secret_key=None):
|
||||||
|
"""Initialize server config with provided or generated keys
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
client_key (str, optional): Client key to use. If None, generates new key
|
client_key (str, optional): Client key to use. If None, generates new key
|
||||||
|
secret_key (str, optional): Secret key to use. If None, generates new key
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: True if successful, False otherwise
|
bool: True if successful, False otherwise
|
||||||
@@ -63,20 +80,26 @@ def init_config(client_key=None):
|
|||||||
log.i("Server config already exists, skipping initialization")
|
log.i("Server config already exists, skipping initialization")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Generate key if not provided
|
# Generate keys if not provided
|
||||||
if not client_key:
|
if not client_key:
|
||||||
client_key = generate_client_key()
|
client_key = generate_client_key()
|
||||||
log.i("Generated new client key")
|
log.i("Generated new client key")
|
||||||
else:
|
else:
|
||||||
log.i("Using provided client key from environment")
|
log.i("Using provided client key from environment")
|
||||||
|
|
||||||
|
if not secret_key:
|
||||||
|
secret_key = generate_secret_key()
|
||||||
|
log.i("Generated new secret key")
|
||||||
|
else:
|
||||||
|
log.i("Using provided secret key from environment")
|
||||||
|
|
||||||
# Insert initial config
|
# Insert initial config
|
||||||
now = datetime.now().isoformat()
|
now = datetime.now().isoformat()
|
||||||
|
|
||||||
conn.execute("""
|
conn.execute("""
|
||||||
INSERT INTO server_config (id, client_key, created_at, updated_at)
|
INSERT INTO server_config (id, client_key, secret_key, created_at, updated_at)
|
||||||
VALUES (1, ?, ?, ?)
|
VALUES (1, ?, ?, ?, ?)
|
||||||
""", (client_key, now, now))
|
""", (client_key, secret_key, now, now))
|
||||||
|
|
||||||
log.i("Server config initialized successfully")
|
log.i("Server config initialized successfully")
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -9,8 +9,7 @@ DB_DIR = 'db'
|
|||||||
DB_PATH = os.path.join(DB_DIR, 'file_server.db')
|
DB_PATH = os.path.join(DB_DIR, 'file_server.db')
|
||||||
|
|
||||||
# Ensure database directory exists
|
# Ensure database directory exists
|
||||||
if not os.path.exists(DB_DIR):
|
os.makedirs(DB_DIR, exist_ok=True)
|
||||||
os.makedirs(DB_DIR)
|
|
||||||
|
|
||||||
def get_db_connection():
|
def get_db_connection():
|
||||||
"""Get a thread-safe database connection with row factory"""
|
"""Get a thread-safe database connection with row factory"""
|
||||||
@@ -115,11 +114,13 @@ def init_db():
|
|||||||
CREATE TABLE IF NOT EXISTS server_config (
|
CREATE TABLE IF NOT EXISTS server_config (
|
||||||
id INTEGER PRIMARY KEY CHECK(id = 1),
|
id INTEGER PRIMARY KEY CHECK(id = 1),
|
||||||
client_key TEXT NOT NULL,
|
client_key TEXT NOT NULL,
|
||||||
|
secret_key TEXT,
|
||||||
server_address TEXT,
|
server_address TEXT,
|
||||||
server_port INTEGER,
|
server_port INTEGER,
|
||||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||||
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||||
CONSTRAINT client_key_length CHECK(length(client_key) = 64)
|
CONSTRAINT client_key_length CHECK(length(client_key) = 64),
|
||||||
|
CONSTRAINT secret_key_length CHECK(secret_key IS NULL OR length(secret_key) = 64)
|
||||||
)
|
)
|
||||||
""")
|
""")
|
||||||
|
|
||||||
@@ -249,6 +250,8 @@ def migrate_client_key_to_db():
|
|||||||
|
|
||||||
if existing > 0:
|
if existing > 0:
|
||||||
log.i("Server config already exists in database, skipping migration")
|
log.i("Server config already exists in database, skipping migration")
|
||||||
|
# Check if secret_key needs to be added to existing config
|
||||||
|
migrate_secret_key()
|
||||||
return
|
return
|
||||||
|
|
||||||
# Get CLIENT_KEY from environment if set
|
# Get CLIENT_KEY from environment if set
|
||||||
@@ -261,3 +264,29 @@ def migrate_client_key_to_db():
|
|||||||
else:
|
else:
|
||||||
log.i("No valid CLIENT_KEY in environment, generating new key")
|
log.i("No valid CLIENT_KEY in environment, generating new key")
|
||||||
init_config()
|
init_config()
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_secret_key():
|
||||||
|
"""Migrate existing server_config to add secret_key if missing"""
|
||||||
|
from models.config_model import generate_secret_key
|
||||||
|
|
||||||
|
try:
|
||||||
|
with get_db() as conn:
|
||||||
|
# Check if secret_key already exists
|
||||||
|
result = conn.execute("SELECT secret_key FROM server_config WHERE id = 1").fetchone()
|
||||||
|
|
||||||
|
if result and result[0]:
|
||||||
|
log.i("Secret key already exists in database")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Generate and add secret_key
|
||||||
|
secret_key = generate_secret_key()
|
||||||
|
conn.execute("""
|
||||||
|
UPDATE server_config
|
||||||
|
SET secret_key = ?
|
||||||
|
WHERE id = 1
|
||||||
|
""", (secret_key,))
|
||||||
|
|
||||||
|
log.i("Added secret key to existing server config")
|
||||||
|
except Exception as e:
|
||||||
|
log.e(f"Error migrating secret key: {e}")
|
||||||
|
|||||||
Reference in New Issue
Block a user