2026-03-03 23:07:59 +00:00
from flask import Flask , render_template , request , redirect , url_for , flash , session
2026-02-28 11:38:48 +00:00
from flask_sqlalchemy import SQLAlchemy
2026-03-03 18:44:17 +00:00
from datetime import date
2026-03-03 23:07:59 +00:00
import gnupg
import secrets
2026-02-28 11:38:48 +00:00
2026-03-03 23:49:50 +00:00
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 "
]
2026-02-28 11:38:48 +00:00
2026-03-03 23:49:50 +00:00
app = Flask ( __name__ )
2026-03-03 18:44:17 +00:00
app . config [ ' SQLALCHEMY_DATABASE_URI ' ] = ' mysql+pymysql://love:love@localhost:3309/lovedb '
2026-02-28 11:38:48 +00:00
app . config [ ' SQLALCHEMY_TRACK_MODIFICATIONS ' ] = False
2026-03-03 23:07:59 +00:00
app . config [ ' SECRET_KEY ' ] = ' random '
2026-02-28 11:38:48 +00:00
db = SQLAlchemy ( app )
2026-03-03 23:49:50 +00:00
gpg = gnupg . GPG ( )
2026-02-28 11:38:48 +00:00
class User ( db . Model ) :
id = db . Column ( db . Integer , primary_key = True )
2026-03-03 18:44:17 +00:00
2026-03-02 01:13:05 +00:00
username = db . Column ( db . String ( 128 ) , unique = True , nullable = False )
2026-03-03 23:49:50 +00:00
pgp = db . Column ( db . String ( 4096 ) , nullable = False )
2026-02-28 11:38:48 +00:00
2026-03-03 18:44:17 +00:00
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 )
2026-02-28 11:38:48 +00:00
date_of_birth = db . Column ( db . Date , nullable = False )
2026-03-03 18:44:17 +00:00
2026-02-28 11:38:48 +00:00
profile_picture = db . Column ( db . String ( 200 ) , nullable = False )
2026-03-03 18:44:17 +00:00
pictures = db . Column ( db . JSON , nullable = True )
country = db . Column ( db . String ( 128 ) , nullable = False )
city = db . Column ( db . String ( 128 ) , nullable = True )
2026-02-28 11:38:48 +00:00
height = db . Column ( db . Float , nullable = True )
weight = db . Column ( db . Integer , nullable = True )
2026-03-03 18:44:17 +00:00
race = db . Column ( db . String ( 20 ) , nullable = True )
2026-02-28 11:38:48 +00:00
prefered_age_range = db . Column ( db . String ( 20 ) , nullable = True )
2026-03-03 18:44:17 +00:00
likes = db . Column ( db . JSON , nullable = True )
dislikes = db . Column ( db . JSON , nullable = True )
2026-02-28 11:38:48 +00:00
2026-03-02 01:13:05 +00:00
xmpp = db . Column ( db . String ( 128 ) , unique = True , nullable = False )
email = db . Column ( db . String ( 128 ) , unique = True , nullable = True )
2026-02-28 11:38:48 +00:00
phone = db . Column ( db . String ( 20 ) , unique = True , nullable = True )
is_verified = db . Column ( db . Boolean , default = False )
@app.route ( " / " )
def home ( ) :
return render_template ( " index.html " )
2026-03-03 23:07:59 +00:00
@app.route ( " /register " , methods = [ " GET " , " POST " ] )
2026-03-03 19:05:11 +00:00
def register ( ) :
2026-03-03 23:07:59 +00:00
if request . method == " POST " :
username = request . form . get ( " username " )
pgp = request . form . get ( " pgp " )
firstname = request . form . get ( " firstname " )
lastname = request . form . get ( " lastname " )
sex = request . form . get ( " sex " )
date_of_birth = request . form . get ( " date_of_birth " )
profile_picture = request . form . get ( " profile_picture " )
country = request . form . get ( " country " )
xmpp = request . form . get ( " xmpp " )
email = request . form . get ( " email " )
phone = request . form . get ( " phone " )
city = request . form . get ( " city " )
height = request . form . get ( " height " )
weight = request . form . get ( " weight " )
race = request . form . get ( " race " )
prefered_age_range = request . form . get ( " prefered_age_range " )
2026-03-03 23:49:50 +00:00
if not all ( [ username , pgp , firstname , lastname , sex , date_of_birth , profile_picture , country , xmpp ] ) :
2026-03-03 23:07:59 +00:00
flash ( " Please fill all required fields. " )
return redirect ( url_for ( " register " ) )
if User . query . filter_by ( username = username ) . first ( ) :
flash ( " Username already exists. " )
return redirect ( url_for ( " register " ) )
if User . query . filter_by ( xmpp = xmpp ) . first ( ) :
flash ( " XMPP already exists. " )
return redirect ( url_for ( " register " ) )
if email and User . query . filter_by ( email = email ) . first ( ) :
flash ( " Email already exists. " )
return redirect ( url_for ( " register " ) )
if phone and User . query . filter_by ( phone = phone ) . first ( ) :
flash ( " Phone already exists. " )
return redirect ( url_for ( " register " ) )
try :
dob = date . fromisoformat ( date_of_birth )
except ValueError :
flash ( " Invalid date format. " )
return redirect ( url_for ( " register " ) )
today = date . today ( )
age = today . year - dob . year - ( ( today . month , today . day ) < ( dob . month , dob . day ) )
if age < 18 :
flash ( " You must be at least 18 years old to register. " )
return redirect ( url_for ( " register " ) )
session [ " pending_user " ] = {
" username " : username ,
" pgp " : pgp ,
" firstname " : firstname ,
" lastname " : lastname ,
" sex " : sex ,
" date_of_birth " : date_of_birth ,
" profile_picture " : profile_picture ,
" country " : country ,
" xmpp " : xmpp ,
" email " : email ,
" phone " : phone ,
" city " : city ,
" height " : height ,
" weight " : weight ,
" race " : race ,
" prefered_age_range " : prefered_age_range
}
import_result = gpg . import_keys ( pgp )
if not import_result . fingerprints :
2026-03-03 23:49:50 +00:00
flash ( " Invalid PGP key. " )
2026-03-03 23:07:59 +00:00
return redirect ( url_for ( " register " ) )
fingerprint = import_result . fingerprints [ 0 ]
random_string = secrets . token_hex ( 16 )
challenge_phrase = f " this is the unencrypted string: { random_string } "
2026-03-03 23:49:50 +00:00
encrypted_data = gpg . encrypt ( challenge_phrase , recipients = [ fingerprint ] )
2026-03-03 23:07:59 +00:00
if not encrypted_data . ok :
2026-03-03 23:49:50 +00:00
flash ( " Failed to encrypt challenge. " )
2026-03-03 23:07:59 +00:00
return redirect ( url_for ( " register " ) )
session [ " pgp_expected_phrase " ] = challenge_phrase
2026-03-03 23:49:50 +00:00
return render_template ( " verify.html " , encrypted_message = str ( encrypted_data ) )
2026-03-03 23:07:59 +00:00
2026-03-03 23:49:50 +00:00
return render_template ( " register.html " , countries = COUNTRIES )
2026-03-03 19:05:11 +00:00
2026-03-03 23:07:59 +00:00
@app.route ( " /verify " , methods = [ " POST " ] )
def verify ( ) :
expected_phrase = session . get ( " pgp_expected_phrase " )
data = session . get ( " pending_user " )
2026-03-03 23:27:33 +00:00
if not data or not expected_phrase :
flash ( " Session expired. " )
2026-03-03 23:07:59 +00:00
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 " ) )
2026-03-03 23:27:33 +00:00
if submitted . strip ( ) == expected_phrase :
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_picture " ] ,
country = data [ " country " ] ,
xmpp = data [ " xmpp " ] ,
email = data [ " email " ] or None ,
phone = data [ " phone " ] or None ,
city = data [ " city " ] or None ,
height = float ( data [ " height " ] ) if data [ " height " ] else None ,
weight = int ( data [ " weight " ] ) if data [ " weight " ] else None ,
race = data [ " race " ] or None ,
prefered_age_range = data [ " prefered_age_range " ] or None ,
2026-03-03 23:49:50 +00:00
is_verified = True
2026-03-03 23:27:33 +00:00
)
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 )
2026-03-03 23:49:50 +00:00
flash ( " PGP verification successful! Account created. " )
2026-03-03 23:27:33 +00:00
return redirect ( url_for ( " home " ) )
else :
flash ( " Verification failed. Account not created. " )
2026-03-03 23:07:59 +00:00
return redirect ( url_for ( " register " ) )
2026-03-03 23:27:33 +00:00
@app.route ( " /login " , methods = [ " GET " , " POST " ] )
2026-03-03 19:05:11 +00:00
def login ( ) :
2026-03-03 23:27:33 +00:00
if request . method == " POST " :
username = request . form . get ( " username " )
pgp = request . form . get ( " pgp " )
if not username or not pgp :
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 " ) )
import_result = gpg . import_keys ( pgp )
if not import_result . fingerprints :
flash ( " Invalid PGP key. " )
return redirect ( url_for ( " login " ) )
fingerprint = import_result . fingerprints [ 0 ]
random_string = secrets . token_hex ( 16 )
challenge_phrase = f " this is the unencrypted string: { random_string } "
encrypted_data = gpg . encrypt (
challenge_phrase ,
recipients = [ fingerprint ]
)
if not encrypted_data . ok :
flash ( " Failed to encrypt challenge. " )
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 = str ( encrypted_data )
)
2026-03-03 19:05:11 +00:00
return render_template ( " login.html " )
2026-03-03 18:44:17 +00:00
2026-03-03 23:27:33 +00:00
@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 :
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 " ) )
else :
flash ( " Verification failed " )
return redirect ( url_for ( " login " ) )
2026-03-03 23:07:59 +00:00
@app.route ( " /logout " )
def logout ( ) :
session . pop ( ' user_id ' , None )
session . pop ( ' username ' , None )
flash ( " Logged out successfully " )
return redirect ( url_for ( " home " ) )
2026-03-03 23:49:50 +00:00
@app.route ( " /user/<username> " )
def user_profile ( username ) :
user = User . query . filter_by ( username = username ) . first_or_404 ( )
return render_template ( " user.html " , user = user , date = date )
2026-02-28 11:38:48 +00:00
if __name__ == " __main__ " :
with app . app_context ( ) :
db . create_all ( )
app . run ( debug = True )