MarseyWorld/files/routes/login.py

538 lines
15 KiB
Python
Raw Normal View History

2021-10-15 14:08:27 +00:00
from urllib.parse import urlencode
from files.mail import *
from files.__main__ import app, limiter
from files.helpers.const import *
import requests
valid_username_regex = re.compile("^[a-zA-Z0-9_\-]{3,25}$")
valid_password_regex = re.compile("^.{8,100}$")
@app.get("/login")
2022-01-11 22:03:01 +00:00
@auth_desired
2021-10-15 14:08:27 +00:00
def login_get(v):
2022-01-17 11:06:12 +00:00
redir = request.values.get("redirect")
if redir:
2022-01-17 11:50:03 +00:00
redir = redir.replace("/logged_out", "").strip()
2022-01-24 17:37:37 +00:00
if not redir.startswith(SITE_FULL) and not redir.startswith('/'): redir = None
2021-10-15 14:08:27 +00:00
2022-01-28 20:02:21 +00:00
if v and redir:
if redir.startswith(SITE_FULL): return redirect(redir)
elif redir.startswith('/'): return redirect(f'{SITE_FULL}{redir}')
2022-01-17 11:06:12 +00:00
return render_template("login.html", failed=False, redirect=redir)
2021-10-15 14:08:27 +00:00
def check_for_alts(current_id):
past_accs = set(session.get("history", []))
past_accs.add(current_id)
session["history"] = list(past_accs)
for past_id in session["history"]:
2021-11-16 00:18:41 +00:00
2021-11-18 20:53:47 +00:00
if past_id == MOM_ID or current_id == MOM_ID: break
2021-11-16 00:18:41 +00:00
if past_id == current_id: continue
2021-10-15 14:08:27 +00:00
2022-01-29 02:01:16 +00:00
li = [past_id, current_id]
existing = g.db.query(Alt).filter(Alt.user1.in_(li), Alt.user2.in_(li)).one_or_none()
if not existing:
new_alt = Alt(user1=past_id, user2=current_id)
g.db.add(new_alt)
g.db.flush()
2021-10-15 14:08:27 +00:00
2022-01-29 02:01:16 +00:00
otheralts = g.db.query(Alt).filter(Alt.user1.in_(li), Alt.user2.in_(li)).all()
2021-10-15 14:08:27 +00:00
for a in otheralts:
2022-01-29 02:01:16 +00:00
if a.user1 != past_id:
li = [a.user1, past_id]
existing = g.db.query(Alt).filter(Alt.user1.in_(li), Alt.user2.in_(li)).one_or_none()
if not existing:
new_alt = Alt(user1=a.user1, user2=past_id)
g.db.add(new_alt)
g.db.flush()
if a.user1 != current_id:
li = [a.user1, current_id]
existing = g.db.query(Alt).filter(Alt.user1.in_(li), Alt.user2.in_(li)).one_or_none()
if not existing:
new_alt = Alt(user1=a.user1, user2=current_id)
g.db.add(new_alt)
g.db.flush()
if a.user2 != past_id:
li = [a.user2, past_id]
existing = g.db.query(Alt).filter(Alt.user1.in_(li), Alt.user2.in_(li)).one_or_none()
if not existing:
new_alt = Alt(user1=a.user2, user2=past_id)
g.db.add(new_alt)
g.db.flush()
if a.user2 != current_id:
li = [a.user2, current_id]
existing = g.db.query(Alt).filter(Alt.user1.in_(li), Alt.user2.in_(li)).one_or_none()
if not existing:
new_alt = Alt(user1=a.user2, user2=current_id)
g.db.add(new_alt)
g.db.flush()
2021-10-15 14:08:27 +00:00
@app.post("/login")
2022-01-15 06:31:17 +00:00
@limiter.limit("1/second;6/minute;200/hour;1000/day")
2021-10-15 14:08:27 +00:00
def login_post():
2021-12-19 13:04:39 +00:00
template = ''
2021-10-15 14:08:27 +00:00
username = request.values.get("username")
if not username: abort(400)
if "@" in username:
2021-11-06 15:52:48 +00:00
account = g.db.query(User).filter(
2022-01-02 00:06:46 +00:00
User.email.ilike(username)).one_or_none()
2021-10-15 14:08:27 +00:00
else:
account = get_user(username, graceful=True)
if not account:
time.sleep(random.uniform(0, 2))
2022-01-14 12:04:35 +00:00
return render_template("login.html", failed=True)
2021-10-15 14:08:27 +00:00
if request.values.get("password"):
if not account.verifyPass(request.values.get("password")):
time.sleep(random.uniform(0, 2))
2022-01-14 12:04:35 +00:00
return render_template("login.html", failed=True)
2021-10-15 14:08:27 +00:00
if account.mfa_secret:
now = int(time.time())
hash = generate_hash(f"{account.id}+{now}+2fachallenge")
2022-01-14 12:04:35 +00:00
return render_template("login_2fa.html",
2021-10-15 14:08:27 +00:00
v=account,
time=now,
hash=hash,
redirect=request.values.get("redirect", "/")
)
elif request.values.get("2fa_token", "x"):
now = int(time.time())
if now - int(request.values.get("time")) > 600:
2022-01-24 20:26:15 +00:00
return redirect(f'{SITE_FULL}/login')
2021-10-15 14:08:27 +00:00
formhash = request.values.get("hash")
if not validate_hash(f"{account.id}+{request.values.get('time')}+2fachallenge",
formhash
):
2022-01-24 20:26:15 +00:00
return redirect(f"{SITE_FULL}/login")
2021-10-15 14:08:27 +00:00
if not account.validate_2fa(request.values.get("2fa_token", "").strip()):
hash = generate_hash(f"{account.id}+{time}+2fachallenge")
2022-01-14 12:04:35 +00:00
return render_template("login_2fa.html",
2021-10-15 14:08:27 +00:00
v=account,
time=now,
hash=hash,
failed=True,
)
else:
abort(400)
2021-12-23 19:17:29 +00:00
session.permanent = True
2021-12-21 22:28:29 +00:00
session["session_id"] = token_hex(49)
session["lo_user"] = account.id
2021-10-15 14:08:27 +00:00
session["login_nonce"] = account.login_nonce
2021-12-30 05:43:49 +00:00
if account.id != PW_ID: check_for_alts(account.id)
2021-10-15 14:08:27 +00:00
g.db.commit()
2022-01-17 11:06:12 +00:00
redir = request.values.get("redirect")
if redir:
2022-01-17 11:50:03 +00:00
redir = redir.replace("/logged_out", "").strip()
2022-01-24 17:37:37 +00:00
if not redir.startswith(SITE_FULL) and not redir.startswith('/'): redir = '/'
2022-01-17 11:06:12 +00:00
2022-01-28 20:02:21 +00:00
if redir.startswith(SITE_FULL): return redirect(redir)
if redir.startswith('/'): return redirect(f'{SITE_FULL}{redir}')
return redirect(f'{SITE_FULL}/')
2021-10-15 14:08:27 +00:00
@app.get("/me")
@app.get("/@me")
@auth_required
def me(v):
if request.headers.get("Authorization"): return v.json
else: return redirect(v.url)
@app.post("/logout")
2022-01-15 06:31:17 +00:00
@limiter.limit("1/second;30/minute;200/hour;1000/day")
2021-10-15 14:08:27 +00:00
@auth_required
def logout(v):
session.pop("session_id", None)
2021-12-21 22:28:29 +00:00
session.pop("lo_user", None)
2021-10-15 14:08:27 +00:00
return {"message": "Logout successful!"}
@app.get("/signup")
2022-01-11 22:03:01 +00:00
@auth_desired
2021-10-15 14:08:27 +00:00
def sign_up_get(v):
2022-01-28 03:22:31 +00:00
if environ.get('disable_signups'):
return {"error": "New account registration is currently closed. Please come back later."}, 403
2021-10-15 14:08:27 +00:00
2022-01-24 20:26:15 +00:00
if v: return redirect(f"{SITE_FULL}/")
2021-10-15 14:08:27 +00:00
agent = request.headers.get("User-Agent", None)
if not agent: abort(403)
ref = request.values.get("ref", None)
if ref:
2022-01-02 00:06:46 +00:00
ref_user = g.db.query(User).filter(User.username.ilike(ref)).one_or_none()
2021-10-15 14:08:27 +00:00
else:
ref_user = None
if ref_user and (ref_user.id in session.get("history", [])):
2022-01-03 10:57:43 +00:00
return render_template("sign_up_failed_ref.html")
2021-10-15 14:08:27 +00:00
now = int(time.time())
token = token_hex(16)
session["signup_token"] = token
formkey_hashstr = str(now) + token + agent
formkey = hmac.new(key=bytes(environ.get("MASTER_KEY"), "utf-16"),
msg=bytes(formkey_hashstr, "utf-16"),
digestmod='md5'
).hexdigest()
redir = request.values.get("redirect", "/").replace("/logged_out", "").strip()
2022-01-03 10:57:43 +00:00
return render_template("sign_up.html",
2021-10-15 14:08:27 +00:00
formkey=formkey,
now=now,
redirect=redir,
ref_user=ref_user,
hcaptcha=app.config["HCAPTCHA_SITEKEY"]
)
@app.post("/signup")
2022-01-15 06:31:17 +00:00
@limiter.limit("1/minute;5/day")
2022-01-11 22:03:01 +00:00
@auth_desired
2021-10-15 14:08:27 +00:00
def sign_up_post(v):
2022-01-28 03:22:31 +00:00
if environ.get('disable_signups'):
return {"error": "New account registration is currently closed. Please come back later."}, 403
2021-10-15 14:08:27 +00:00
if v: abort(403)
agent = request.headers.get("User-Agent", None)
if not agent: abort(403)
form_timestamp = request.values.get("now", '0')
form_formkey = request.values.get("formkey", "none")
submitted_token = session.get("signup_token", "")
if not submitted_token: abort(400)
correct_formkey_hashstr = form_timestamp + submitted_token + agent
correct_formkey = hmac.new(key=bytes(environ.get("MASTER_KEY"), "utf-16"),
msg=bytes(correct_formkey_hashstr, "utf-16"),
digestmod='md5'
).hexdigest()
now = int(time.time())
username = request.values.get("username").strip()
def new_signup(error):
args = {"error": error}
if request.values.get("referred_by"):
2021-11-06 15:52:48 +00:00
user = g.db.query(User).filter_by(
2022-01-02 00:06:46 +00:00
id=request.values.get("referred_by")).one_or_none()
2021-10-15 14:08:27 +00:00
if user:
args["ref"] = user.username
2022-01-24 20:26:15 +00:00
return redirect(f"{SITE_FULL}/signup?{urlencode(args)}")
2021-10-15 14:08:27 +00:00
if now - int(form_timestamp) < 5:
return new_signup("There was a problem. Please try again.")
if not hmac.compare_digest(correct_formkey, form_formkey):
return new_signup("There was a problem. Please try again.")
if not request.values.get(
"password") == request.values.get("password_confirm"):
return new_signup("Passwords did not match. Please try again.")
if not re.fullmatch(valid_username_regex, username):
return new_signup("Invalid username")
if not re.fullmatch(valid_password_regex, request.values.get("password")):
return new_signup("Password must be between 8 and 100 characters.")
2021-12-20 14:56:47 +00:00
email = request.values.get("email").strip().lower()
2021-11-23 21:03:20 +00:00
2021-10-15 14:08:27 +00:00
if not email: email = None
existing_account = get_user(username, graceful=True)
if existing_account and existing_account.reserved:
return redirect(existing_account.url)
2021-11-30 16:49:10 +00:00
if existing_account: return new_signup("An account with that username already exists.")
2021-10-15 14:08:27 +00:00
if app.config.get("HCAPTCHA_SITEKEY"):
token = request.values.get("h-captcha-response")
if not token:
return new_signup("Unable to verify captcha [1].")
data = {"secret": app.config["HCAPTCHA_SECRET"],
"response": token,
"sitekey": app.config["HCAPTCHA_SITEKEY"]}
url = "https://hcaptcha.com/siteverify"
2021-11-14 01:19:32 +00:00
x = requests.post(url, data=data, timeout=5)
2021-10-15 14:08:27 +00:00
if not x.json()["success"]:
return new_signup("Unable to verify captcha [2].")
session.pop("signup_token")
ref_id = int(request.values.get("referred_by", 0))
2022-01-02 00:05:22 +00:00
if ref_id:
ref_user = g.db.query(User).filter_by(id=ref_id).one_or_none()
if ref_user:
2022-01-19 06:20:05 +00:00
if ref_user.referral_count and not ref_user.has_badge(10):
2022-01-02 00:05:22 +00:00
new_badge = Badge(user_id=ref_user.id, badge_id=10)
g.db.add(new_badge)
2022-01-19 06:20:05 +00:00
if ref_user.referral_count > 9 and not ref_user.has_badge(11):
2022-01-02 00:05:22 +00:00
new_badge = Badge(user_id=ref_user.id, badge_id=11)
g.db.add(new_badge)
2022-01-19 06:20:05 +00:00
if ref_user.referral_count > 99 and not ref_user.has_badge(12):
2022-01-02 00:05:22 +00:00
new_badge = Badge(user_id=ref_user.id, badge_id=12)
g.db.add(new_badge)
2021-11-06 15:52:48 +00:00
id_1 = g.db.query(User.id).filter_by(id=7).count()
2021-11-14 22:35:18 +00:00
users_count = g.db.query(User.id).count()
2021-12-28 06:28:18 +00:00
if id_1 == 0 and users_count == 7: admin_level=3
2021-10-15 14:08:27 +00:00
else: admin_level=0
new_user = User(
username=username,
original_username = username,
admin_level = admin_level,
password=request.values.get("password"),
email=email,
created_utc=int(time.time()),
referred_by=ref_id or None,
2022-01-07 21:03:14 +00:00
ban_evade = int(any((x.is_banned or x.shadowbanned) and not x.unban_utc for x in g.db.query(User).filter(User.id.in_(tuple(session.get("history", [])))).all() if x)),
agendaposter = any(x.agendaposter for x in g.db.query(User).filter(User.id.in_(tuple(session.get("history", [])))).all() if x)
2021-10-15 14:08:27 +00:00
)
g.db.add(new_user)
g.db.flush()
check_for_alts(new_user.id)
if email: send_verification_email(new_user)
2021-12-28 08:17:49 +00:00
send_notification(new_user.id, WELCOME_MSG)
2021-10-15 14:08:27 +00:00
2021-12-23 19:17:29 +00:00
session.permanent = True
2021-12-21 22:28:29 +00:00
session["session_id"] = token_hex(49)
session["lo_user"] = new_user.id
2021-10-15 14:08:27 +00:00
g.db.commit()
2022-01-24 20:26:15 +00:00
return redirect(f"{SITE_FULL}/")
2021-10-15 14:08:27 +00:00
@app.get("/forgot")
def get_forgot():
2022-01-07 21:03:14 +00:00
return render_template("forgot_password.html")
2021-10-15 14:08:27 +00:00
@app.post("/forgot")
2022-01-15 06:31:17 +00:00
@limiter.limit("1/second;30/minute;200/hour;1000/day")
2021-10-15 14:08:27 +00:00
def post_forgot():
username = request.values.get("username").lstrip('@')
2022-01-01 22:06:53 +00:00
email = request.values.get("email",'').strip().lower().replace("_","\_")
2021-10-15 14:08:27 +00:00
2021-11-06 15:52:48 +00:00
user = g.db.query(User).filter(
2021-10-15 14:08:27 +00:00
User.username.ilike(username),
2022-01-02 00:06:46 +00:00
User.email.ilike(email)).one_or_none()
2021-10-15 14:08:27 +00:00
if user:
now = int(time.time())
token = generate_hash(f"{user.id}+{now}+forgot+{user.login_nonce}")
2022-01-24 17:37:37 +00:00
url = f"{SITE_FULL}/reset?id={user.id}&time={now}&token={token}"
2021-10-15 14:08:27 +00:00
send_mail(to_address=user.email,
subject="Password Reset Request",
html=render_template("email/password_reset.html",
action_url=url,
v=user)
)
2022-01-07 21:03:14 +00:00
return render_template("forgot_password.html",
2021-10-15 14:08:27 +00:00
msg="If the username and email matches an account, you will be sent a password reset email. You have ten minutes to complete the password reset process.")
@app.get("/reset")
def get_reset():
user_id = request.values.get("id")
2021-12-30 05:43:49 +00:00
2021-10-15 14:08:27 +00:00
timestamp = int(request.values.get("time",0))
token = request.values.get("token")
now = int(time.time())
if now - timestamp > 600:
2022-01-07 21:03:14 +00:00
return render_template("message.html",
2021-10-15 14:08:27 +00:00
title="Password reset link expired",
error="That password reset link has expired.")
2022-01-02 00:06:46 +00:00
user = g.db.query(User).filter_by(id=user_id).one_or_none()
2021-12-30 05:43:49 +00:00
2022-01-21 10:44:12 +00:00
if not user: abort(400)
2021-10-15 14:08:27 +00:00
if not validate_hash(f"{user_id}+{timestamp}+forgot+{user.login_nonce}", token):
abort(400)
if not user:
abort(404)
reset_token = generate_hash(f"{user.id}+{timestamp}+reset+{user.login_nonce}")
2022-01-07 21:03:14 +00:00
return render_template("reset_password.html",
2021-10-15 14:08:27 +00:00
v=user,
token=reset_token,
time=timestamp,
)
@app.post("/reset")
2022-01-15 06:31:17 +00:00
@limiter.limit("1/second;30/minute;200/hour;1000/day")
2022-01-11 22:03:01 +00:00
@auth_desired
2021-10-15 14:08:27 +00:00
def post_reset(v):
2022-01-24 20:26:15 +00:00
if v: return redirect(f'{SITE_FULL}/')
2021-10-15 14:08:27 +00:00
user_id = request.values.get("user_id")
2021-12-19 02:37:18 +00:00
2021-10-15 14:08:27 +00:00
timestamp = int(request.values.get("time"))
token = request.values.get("token")
password = request.values.get("password")
confirm_password = request.values.get("confirm_password")
now = int(time.time())
if now - timestamp > 600:
2022-01-14 12:04:35 +00:00
return render_template("message.html",
2021-10-15 14:08:27 +00:00
title="Password reset expired",
error="That password reset form has expired.")
2022-01-02 00:06:46 +00:00
user = g.db.query(User).filter_by(id=user_id).one_or_none()
2021-10-15 14:08:27 +00:00
if not validate_hash(f"{user_id}+{timestamp}+reset+{user.login_nonce}", token):
abort(400)
if not user:
abort(404)
2022-01-22 09:58:22 +00:00
if password != confirm_password:
2022-01-14 12:04:35 +00:00
return render_template("reset_password.html",
2021-10-15 14:08:27 +00:00
v=user,
token=token,
time=timestamp,
error="Passwords didn't match.")
user.passhash = hash_password(password)
g.db.add(user)
g.db.commit()
2022-01-14 12:04:35 +00:00
return render_template("message_success.html",
2021-10-15 14:08:27 +00:00
title="Password reset successful!",
message="Login normally to access your account.")
@app.get("/lost_2fa")
2022-01-11 22:03:01 +00:00
@auth_desired
2021-10-15 14:08:27 +00:00
def lost_2fa(v):
return render_template(
"lost_2fa.html",
v=v
)
@app.post("/request_2fa_disable")
2022-01-15 06:31:17 +00:00
@limiter.limit("1/second;6/minute;200/hour;1000/day")
2021-10-15 14:08:27 +00:00
def request_2fa_disable():
username=request.values.get("username")
user=get_user(username, graceful=True)
if not user or not user.email or not user.mfa_secret:
2022-01-07 21:03:14 +00:00
return render_template("message.html",
2021-10-15 14:08:27 +00:00
title="Removal request received",
message="If username, password, and email match, we will send you an email.")
2021-12-20 14:56:47 +00:00
email=request.values.get("email").strip().lower()
2021-10-15 14:08:27 +00:00
password =request.values.get("password")
if not user.verifyPass(password):
2022-01-07 21:03:14 +00:00
return render_template("message.html",
2021-10-15 14:08:27 +00:00
title="Removal request received",
message="If username, password, and email match, we will send you an email.")
valid=int(time.time())
token=generate_hash(f"{user.id}+{user.username}+disable2fa+{valid}+{user.mfa_secret}+{user.login_nonce}")
2022-01-24 17:37:37 +00:00
action_url=f"{SITE_FULL}/reset_2fa?id={user.id}&t={valid}&token={token}"
2021-10-15 14:08:27 +00:00
send_mail(to_address=user.email,
subject="2FA Removal Request",
html=render_template("email/2fa_remove.html",
action_url=action_url,
v=user)
)
2022-01-07 21:03:14 +00:00
return render_template("message.html",
2021-10-15 14:08:27 +00:00
title="Removal request received",
message="If username, password, and email match, we will send you an email.")
@app.get("/reset_2fa")
def reset_2fa():
now=int(time.time())
t=int(request.values.get("t"))
if now > t+3600*24:
2022-01-07 21:03:14 +00:00
return render_template("message.html",
2021-10-15 14:08:27 +00:00
title="Expired Link",
error="That link has expired.")
token=request.values.get("token")
uid=request.values.get("id")
user=get_account(uid)
if not validate_hash(f"{user.id}+{user.username}+disable2fa+{t}+{user.mfa_secret}+{user.login_nonce}", token):
abort(403)
user.mfa_secret=None
g.db.add(user)
g.db.commit()
2022-01-07 21:03:14 +00:00
return render_template("message_success.html",
2021-10-15 14:08:27 +00:00
title="Two-factor authentication removed.",
message="Login normally to access your account.")