fix: Update LICENSES_FILE path in backup, verify, and run scripts; enhance JWT configuration and security headers in main.py
This commit is contained in:
+1
-1
@@ -2,7 +2,7 @@ import os
|
|||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
LICENSES_FILE = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "licenses.json"))
|
LICENSES_FILE = os.path.abspath(os.path.join(os.path.dirname(__file__),"licenses.json"))
|
||||||
|
|
||||||
|
|
||||||
def load_licenses() -> dict:
|
def load_licenses() -> dict:
|
||||||
|
|||||||
+60
-1
@@ -1,5 +1,7 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import sys
|
import sys
|
||||||
|
import os
|
||||||
|
from datetime import timedelta
|
||||||
from flask import Flask, render_template, request, jsonify, flash, redirect, url_for, get_flashed_messages, session, send_file
|
from flask import Flask, render_template, request, jsonify, flash, redirect, url_for, get_flashed_messages, session, send_file
|
||||||
import verify
|
import verify
|
||||||
import backup
|
import backup
|
||||||
@@ -7,6 +9,7 @@ import pyotp
|
|||||||
import qrcode
|
import qrcode
|
||||||
import json
|
import json
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
from flask_jwt_extended import JWTManager, create_access_token, jwt_required
|
||||||
|
|
||||||
def _resolve_template_dir() -> Path:
|
def _resolve_template_dir() -> Path:
|
||||||
candidates = [
|
candidates = [
|
||||||
@@ -25,6 +28,29 @@ totp_key = "Hsdfisdf4n34234dfiseLoasjfj3asnnvhxbbfgrzzuewwndcodrweokyn"
|
|||||||
|
|
||||||
app = Flask(__name__, template_folder=str(_resolve_template_dir()))
|
app = Flask(__name__, template_folder=str(_resolve_template_dir()))
|
||||||
app.secret_key = "ASDfhbsdfseiufhgildsrfrjg874368546987s6e8468f4s"
|
app.secret_key = "ASDfhbsdfseiufhgildsrfrjg874368546987s6e8468f4s"
|
||||||
|
app.config["JWT_SECRET_KEY"] = os.environ.get("JWT_SECRET_KEY", app.secret_key)
|
||||||
|
app.config["JWT_ACCESS_TOKEN_EXPIRES"] = timedelta(hours=12)
|
||||||
|
app.config["SESSION_COOKIE_HTTPONLY"] = True
|
||||||
|
app.config["SESSION_COOKIE_SAMESITE"] = "Lax"
|
||||||
|
app.config["SESSION_COOKIE_SECURE"] = os.environ.get("SESSION_COOKIE_SECURE", "0") == "1"
|
||||||
|
jwt = JWTManager(app)
|
||||||
|
|
||||||
|
|
||||||
|
@app.after_request
|
||||||
|
def apply_security_headers(response):
|
||||||
|
# Apply baseline hardening headers for all endpoints.
|
||||||
|
response.headers.setdefault("X-Content-Type-Options", "nosniff")
|
||||||
|
response.headers.setdefault("X-Frame-Options", "DENY")
|
||||||
|
response.headers.setdefault("Referrer-Policy", "strict-origin-when-cross-origin")
|
||||||
|
response.headers.setdefault("Permissions-Policy", "camera=(), microphone=(), geolocation=()")
|
||||||
|
response.headers.setdefault("Content-Security-Policy", "default-src 'self'; script-src 'self' https://cdn.jsdelivr.net 'unsafe-inline'; style-src 'self' https://cdn.jsdelivr.net 'unsafe-inline'; img-src 'self' data:; font-src 'self' https://cdn.jsdelivr.net; connect-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'")
|
||||||
|
|
||||||
|
# Emit HSTS only for HTTPS requests to avoid breaking local HTTP development.
|
||||||
|
if request.is_secure:
|
||||||
|
response.headers.setdefault("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
def _read_app_version() -> str:
|
def _read_app_version() -> str:
|
||||||
candidates = [
|
candidates = [
|
||||||
@@ -49,6 +75,25 @@ def check_totp(key):
|
|||||||
|
|
||||||
APP_VERSION = _read_app_version()
|
APP_VERSION = _read_app_version()
|
||||||
|
|
||||||
|
|
||||||
|
def _issue_access_token() -> str:
|
||||||
|
return create_access_token(identity="license-validation-client")
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/access_token', methods=['POST'])
|
||||||
|
def access_token():
|
||||||
|
data = request.get_json(silent=True) or {}
|
||||||
|
provided_totp = data.get("totp") or request.form.get("totp") or request.form.get("password")
|
||||||
|
|
||||||
|
if not provided_totp:
|
||||||
|
return jsonify({"error": "Missing 'totp' in request"}), 400
|
||||||
|
|
||||||
|
if not check_totp(str(provided_totp)):
|
||||||
|
return jsonify({"error": "Invalid credentials"}), 401
|
||||||
|
|
||||||
|
token = _issue_access_token()
|
||||||
|
return jsonify({"access_token": token, "token_type": "Bearer"}), 200
|
||||||
|
|
||||||
@app.route("/validate__information", methods=['POST'])
|
@app.route("/validate__information", methods=['POST'])
|
||||||
def validate__information():
|
def validate__information():
|
||||||
data = request.get_json(silent=True)
|
data = request.get_json(silent=True)
|
||||||
@@ -67,9 +112,14 @@ def validate__information():
|
|||||||
def login():
|
def login():
|
||||||
if 'username' in session:
|
if 'username' in session:
|
||||||
return redirect(url_for('default'))
|
return redirect(url_for('default'))
|
||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
totp = request.form['password']
|
data = request.get_json(silent=True) or {}
|
||||||
|
totp = data.get("totp") or request.form.get('password') or request.form.get('totp')
|
||||||
|
|
||||||
if not totp:
|
if not totp:
|
||||||
|
if request.is_json:
|
||||||
|
return jsonify({"error": "Missing TOTP"}), 400
|
||||||
flash('Please fill all fields', 'error')
|
flash('Please fill all fields', 'error')
|
||||||
return redirect(url_for('login'))
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
@@ -77,10 +127,18 @@ def login():
|
|||||||
|
|
||||||
if user_log:
|
if user_log:
|
||||||
session['username'] = "Whatareyoulookingfor"
|
session['username'] = "Whatareyoulookingfor"
|
||||||
|
session['access_token'] = _issue_access_token()
|
||||||
|
|
||||||
|
if request.is_json:
|
||||||
|
return jsonify({"access_token": session['access_token'], "token_type": "Bearer"}), 200
|
||||||
|
|
||||||
return redirect(url_for('default'))
|
return redirect(url_for('default'))
|
||||||
else:
|
else:
|
||||||
|
if request.is_json:
|
||||||
|
return jsonify({"error": "Invalid credentials"}), 401
|
||||||
flash('Invalid credentials', 'error')
|
flash('Invalid credentials', 'error')
|
||||||
get_flashed_messages()
|
get_flashed_messages()
|
||||||
|
|
||||||
return render_template('login.html')
|
return render_template('login.html')
|
||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
@@ -118,6 +176,7 @@ def remove_key(user_id):
|
|||||||
@app.route('/logout')
|
@app.route('/logout')
|
||||||
def logout():
|
def logout():
|
||||||
session.pop('username', None)
|
session.pop('username', None)
|
||||||
|
session.pop('access_token', None)
|
||||||
flash('Logged out successfully', 'info')
|
flash('Logged out successfully', 'info')
|
||||||
return redirect(url_for('login'))
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@ import json
|
|||||||
import os
|
import os
|
||||||
import secrets
|
import secrets
|
||||||
|
|
||||||
LICENSES_FILE = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "licenses.json"))
|
LICENSES_FILE = os.path.abspath(os.path.join(os.path.dirname(__file__), "licenses.json"))
|
||||||
|
|
||||||
|
|
||||||
def load_file() -> dict:
|
def load_file() -> dict:
|
||||||
|
|||||||
@@ -13,10 +13,5 @@
|
|||||||
"user_id": "0003",
|
"user_id": "0003",
|
||||||
"license_key": "H02JSHkh9qtnzU8D5yBVcyej98AU-R6lVhwyYfHA1uM",
|
"license_key": "H02JSHkh9qtnzU8D5yBVcyej98AU-R6lVhwyYfHA1uM",
|
||||||
"hwid_uuid": ""
|
"hwid_uuid": ""
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_id": "0004",
|
|
||||||
"license_key": "H02JSHkh9qtnzU8D5yBVcyej98AU-R6lVhwyYfHA1pM",
|
|
||||||
"hwid_uuid": "Test"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -15,7 +15,7 @@ if ! command -v patchelf >/dev/null 2>&1; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
pip install -U nuitka ordered-set zstandard flask cryptography pyotp qrcode
|
pip install -U nuitka ordered-set zstandard flask flask-jwt-extended cryptography pyotp qrcode
|
||||||
python - <<'PY'
|
python - <<'PY'
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import json
|
import json
|
||||||
|
|||||||
@@ -2,14 +2,10 @@ curl -i -X POST "http://127.0.0.1:5000/validate__information" \
|
|||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{"license":"npEDI1JVhyisQ2dg-fWTTAn5H1hg3eS80Zc-4IptWIU=","hwid":""}'
|
-d '{"license":"npEDI1JVhyisQ2dg-fWTTAn5H1hg3eS80Zc-4IptWIU=","hwid":""}'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
curl -i -X POST "http://127.0.0.1:5000/validate__information" \
|
curl -i -X POST "http://127.0.0.1:5000/validate__information" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{"license":"npEDI1JVhyisQ2dg-fWTTAn5H1hg3eS80Zc-4IptWIU=","hwid":"0c27aefd7107396933c7363efa38fb6c297aa3db119c1597952c05d947f4f27c"}'
|
-d '{"license":"npEDI1JVhyisQ2dg-fWTTAn5H1hg3eS80Zc-4IptWIU=","hwid":"0c27aefd7107396933c7363efa38fb6c297aa3db119c1597952c05d947f4f27c"}'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
curl -i -X POST "http://127.0.0.1:5000/validate__information" \
|
curl -i -X POST "http://127.0.0.1:5000/validate__information" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{"license":"npEDI1JVhyisQ2dg-fWTTAn5H1hg3eS80Zc-4IptWIU=","hwid":"0c27aefd7107396933c7363efa38fb6c297aa3db119c1597952c05d947f4f37c"}'
|
-d '{"license":"npEDI1JVhyisQ2dg-fWTTAn5H1hg3eS80Zc-4IptWIU=","hwid":"0c27aefd7107396933c7363efa38fb6c297aa3db119c1597952c05d947f4f37c"}'
|
||||||
Reference in New Issue
Block a user