from flask import Flask, render_template, request, redirect, url_for, flash, session from flask_sqlalchemy import SQLAlchemy from datetime import date import gnupg import secrets import os from werkzeug.utils import secure_filename UPLOAD_FOLDER = "static/uploads" os.makedirs(UPLOAD_FOLDER, exist_ok=True) app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://love:love@localhost:3309/lovedb' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False app.config['SECRET_KEY'] = 'random' app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER db = SQLAlchemy(app) gpg = gnupg.GPG() COUNTRIES = [ "Afghanistan","Albania","Algeria","Andorra","Angola","Antigua and Barbuda","Argentina", "Armenia","Australia","Austria","Azerbaijan","Bahamas","Bahrain","Bangladesh", "Barbados","Belarus","Belgium","Belize","Benin","Bhutan","Bolivia","Bosnia and Herzegovina", "Botswana","Brazil","Brunei","Bulgaria","Burkina Faso","Burundi","Cabo Verde","Cambodia", "Cameroon","Canada","Central African Republic","Chad","Chile","China","Colombia","Comoros", "Congo (Congo-Brazzaville)","Costa Rica","Croatia","Cuba","Cyprus","Czechia (Czech Republic)", "Democratic Republic of the Congo","Denmark","Djibouti","Dominica","Dominican Republic","Ecuador", "Egypt","El Salvador","Equatorial Guinea","Eritrea","Estonia","Eswatini (fmr. Swaziland)", "Ethiopia","Fiji","Finland","France","Gabon","Gambia","Georgia","Germany","Ghana","Greece", "Grenada","Guatemala","Guinea","Guinea-Bissau","Guyana","Haiti","Holy See","Honduras","Hungary", "Iceland","India","Indonesia","Iran","Iraq","Ireland","Israel","Italy","Jamaica","Japan","Jordan", "Kazakhstan","Kenya","Kiribati","Kuwait","Kyrgyzstan","Laos","Latvia","Lebanon","Lesotho", "Liberia","Libya","Liechtenstein","Lithuania","Luxembourg","Madagascar","Malawi","Malaysia", "Maldives","Mali","Malta","Marshall Islands","Mauritania","Mauritius","Mexico","Micronesia", "Moldova","Monaco","Mongolia","Montenegro","Morocco","Mozambique","Myanmar (Burma)","Namibia", "Nauru","Nepal","Netherlands","New Zealand","Nicaragua","Niger","Nigeria","North Korea", "North Macedonia","Norway","Oman","Pakistan","Palau","Palestine State","Panama","Papua New Guinea", "Paraguay","Peru","Philippines","Poland","Portugal","Qatar","Romania","Russia","Rwanda", "Saint Kitts and Nevis","Saint Lucia","Saint Vincent and the Grenadines","Samoa","San Marino", "Sao Tome and Principe","Saudi Arabia","Senegal","Serbia","Seychelles","Sierra Leone","Singapore", "Slovakia","Slovenia","Solomon Islands","Somalia","South Africa","South Korea","South Sudan", "Spain","Sri Lanka","Sudan","Suriname","Sweden","Switzerland","Syria","Tajikistan","Tanzania", "Thailand","Timor-Leste","Togo","Tonga","Trinidad and Tobago","Tunisia","Turkey","Turkmenistan", "Tuvalu","Uganda","Ukraine","United Arab Emirates","United Kingdom","United States","Uruguay", "Uzbekistan","Vanuatu","Venezuela","Vietnam","Yemen","Zambia","Zimbabwe" ] class User(db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(128), unique=True, nullable=False) pgp = db.Column(db.String(4096), nullable=False) firstname = db.Column(db.String(128), nullable=False) lastname = db.Column(db.String(128), nullable=False) sex = db.Column(db.Enum('male', 'female'), nullable=False) date_of_birth = db.Column(db.Date, nullable=False) profile_picture = db.Column(db.String(200), nullable=False) pictures = db.Column(db.JSON, nullable=True) country = db.Column(db.String(128), nullable=False) city = db.Column(db.String(128), nullable=True) height = db.Column(db.Float, nullable=True) weight = db.Column(db.Integer, nullable=True) race = db.Column(db.String(20), nullable=True) prefered_age_range = db.Column(db.String(20), nullable=True) likes = db.Column(db.JSON, nullable=True) dislikes = db.Column(db.JSON, nullable=True) xmpp = db.Column(db.String(128), unique=True, nullable=False) email = db.Column(db.String(128), unique=True, nullable=True) phone = db.Column(db.String(20), unique=True, nullable=True) is_verified = db.Column(db.Boolean, default=False) def calculate_age(dob: date) -> int: today = date.today() return today.year - dob.year - ((today.month, today.day) < (dob.month, dob.day)) def save_files(username: str, profile_file, pictures_files): user_folder = os.path.join(app.config['UPLOAD_FOLDER'], username) os.makedirs(user_folder, exist_ok=True) profile_filename = secure_filename(profile_file.filename) profile_path = os.path.join(user_folder, profile_filename) profile_file.save(profile_path) profile_url = f"/{profile_path.replace(os.sep, '/')}" pictures_urls = [] for pic in pictures_files: if pic.filename: filename = secure_filename(pic.filename) path = os.path.join(user_folder, filename) pic.save(path) pictures_urls.append(f"/{path.replace(os.sep, '/')}") return profile_url, pictures_urls def pgp_encrypt_and_import(pgp_key: str, message: str): result = gpg.import_keys(pgp_key) if not result.fingerprints: return None, None fingerprint = result.fingerprints[0] encrypted = gpg.encrypt(message, recipients=[fingerprint]) if not encrypted.ok: return fingerprint, None return fingerprint, str(encrypted) @app.route("/") def home(): return render_template("index.html") @app.route("/register", methods=["GET", "POST"]) def register(): if request.method == "POST": data = {key: request.form.get(key) for key in [ "username","pgp","firstname","lastname","sex","date_of_birth","country","xmpp", "email","phone","city","height","weight","race","prefered_age_range" ]} required_fields = ["username","pgp","firstname","lastname","sex","date_of_birth","country","xmpp"] if not all(data[f] for f in required_fields): flash("Please fill all required fields.") return redirect(url_for("register")) for field in ["username","xmpp","email","phone"]: if data.get(field) and User.query.filter_by(**{field:data[field]}).first(): flash(f"{field.capitalize()} already exists.") return redirect(url_for("register")) try: dob = date.fromisoformat(data["date_of_birth"]) except ValueError: flash("Invalid date format.") return redirect(url_for("register")) if calculate_age(dob) < 18: flash("You must be at least 18 years old to register.") return redirect(url_for("register")) profile_file = request.files.get("profile_picture") pictures_files = request.files.getlist("pictures") if not profile_file: flash("Profile picture is required.") return redirect(url_for("register")) profile_url, pictures_urls = save_files(data["username"], profile_file, pictures_files) random_string = secrets.token_hex(16) challenge_phrase = f"this is the unencrypted string: {random_string}" fingerprint, encrypted_msg = pgp_encrypt_and_import(data["pgp"], challenge_phrase) if not fingerprint or not encrypted_msg: flash("Invalid PGP key or encryption failed.") return redirect(url_for("register")) session["pending_user"] = {**data, "profile_url": profile_url, "pictures_urls": pictures_urls} session["pgp_expected_phrase"] = challenge_phrase return render_template("verify.html", encrypted_message=encrypted_msg) return render_template("register.html", countries=COUNTRIES) @app.route("/verify", methods=["POST"]) def verify(): expected_phrase = session.get("pgp_expected_phrase") data = session.get("pending_user") if not data or not expected_phrase: flash("Session expired.") return redirect(url_for("register")) submitted = request.form.get("decrypted_message") if not submitted: flash("You must paste the decrypted message.") return redirect(url_for("register")) if submitted.strip() != expected_phrase: flash("Verification failed. Account not created.") return redirect(url_for("register")) dob = date.fromisoformat(data["date_of_birth"]) new_user = User( username=data["username"], pgp=data["pgp"], firstname=data["firstname"], lastname=data["lastname"], sex=data["sex"], date_of_birth=dob, profile_picture=data["profile_url"], pictures=data["pictures_urls"], country=data["country"], xmpp=data["xmpp"], email=data.get("email") or None, phone=data.get("phone") or None, city=data.get("city") or None, height=float(data["height"]) if data.get("height") else None, weight=int(data["weight"]) if data.get("weight") else None, race=data.get("race") or None, prefered_age_range=data.get("prefered_age_range") or None, is_verified=True ) db.session.add(new_user) db.session.commit() session['user_id'] = new_user.id session['username'] = new_user.username session.pop("pending_user", None) session.pop("pgp_expected_phrase", None) flash("PGP verification successful! Account created.") return redirect(url_for("home")) @app.route("/login", methods=["GET","POST"]) def login(): if request.method == "POST": username = request.form.get("username") pgp_key = request.form.get("pgp") if not username or not pgp_key: flash("Please enter both username and PGP key.") return redirect(url_for("login")) user = User.query.filter_by(username=username).first() if not user: flash("User not found.") return redirect(url_for("login")) random_string = secrets.token_hex(16) challenge_phrase = f"this is the unencrypted string: {random_string}" fingerprint, encrypted_msg = pgp_encrypt_and_import(pgp_key, challenge_phrase) if not fingerprint or not encrypted_msg: flash("Invalid PGP key or encryption failed.") return redirect(url_for("login")) session["login_user_id"] = user.id session["login_expected_phrase"] = challenge_phrase return render_template("login_verify.html", encrypted_message=encrypted_msg) return render_template("login.html") @app.route("/login_verify", methods=["POST"]) def login_verify(): user_id = session.get("login_user_id") expected_phrase = session.get("login_expected_phrase") if not user_id or not expected_phrase: flash("Login session expired") return redirect(url_for("login")) submitted = request.form.get("decrypted_message") if not submitted: flash("You must paste the decrypted message") return redirect(url_for("login")) if submitted.strip() != expected_phrase: flash("Verification failed") return redirect(url_for("login")) user = User.query.get(user_id) session['user_id'] = user.id session['username'] = user.username session.pop("login_user_id", None) session.pop("login_expected_phrase", None) flash("Logged in successfully") return redirect(url_for("home")) @app.route("/logout") def logout(): session.pop('user_id', None) session.pop('username', None) flash("Logged out successfully") return redirect(url_for("home")) @app.route("/user/") def user_profile(username): user = User.query.filter_by(username=username).first_or_404() return render_template("user.html", user=user, date=date) if __name__ == "__main__": with app.app_context(): db.create_all() app.run(debug=True)