add web
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
{% extends '__layout__.html' %}
|
||||
{% block title %}Signup{% endblock %}
|
||||
{% block head %}
|
||||
<style>
|
||||
.auth-container{height:100vh;display:flex;align-items:center;justify-content:center;background:rgba(15,15,15,.5);backdrop-filter:blur(10px)}
|
||||
.auth-box{background:rgba(23,23,23,.7);border-radius:8px;box-shadow:0 8px 32px rgba(0,0,0,.2);padding:25px;width:100%;max-width:420px;border:1px solid rgba(255,255,255,.05)}
|
||||
.auth-box h1{font-weight:700;margin-bottom:5px;color:#fff}
|
||||
.auth-box p{color:#aaa;margin-bottom:20px}
|
||||
.auth-box input{background:rgba(30,30,30,.6);border:1px solid rgba(255,255,255,.1);color:#fff;padding:12px 15px;border-radius:6px;margin-bottom:12px;backdrop-filter:blur(5px);transition:all .2s; width: 100%; display: block;}
|
||||
.auth-box input:focus{border-color:rgba(255,255,255,.3);box-shadow:0 0 0 2px rgba(255,255,255,.1)}
|
||||
.auth-box button{width:100%;padding:12px;border-radius:6px;background:linear-gradient(45deg,#6b46c1,#9f7aea);border:none;color:#fff;font-weight:600;margin-top:5px;transition:all .3s}
|
||||
.auth-box button:hover{transform:translateY(-2px);box-shadow:0 4px 12px rgba(107,70,193,.3)}
|
||||
.auth-box button:disabled{opacity:.6;cursor:not-allowed}
|
||||
.auth-box .links{text-align:center;margin-top:15px;color:#aaa}
|
||||
.auth-box .links a{color:#9f7aea;text-decoration:none;transition:color .2s}
|
||||
.auth-box .links a:hover{color:#b794f4;text-decoration:underline}
|
||||
.auth-image{display:none}
|
||||
@media(min-width:992px){
|
||||
.auth-container{padding:0 30px}
|
||||
.auth-box{margin-right:20px}
|
||||
.auth-image{display:block;max-width:45%;border-radius:8px;overflow:hidden;box-shadow:0 8px 32px rgba(0,0,0,.3)}
|
||||
.auth-image img{width:100%;height:100%;object-fit:cover}
|
||||
}
|
||||
.alert{border-radius:6px;padding:10px;margin-bottom:15px;text-align:center}
|
||||
.alert-danger{background:rgba(220,38,38,.1);border:1px solid rgba(220,38,38,.3);color:#ef4444}
|
||||
#checkboxTerms{display:flex;align-items:flex-start;margin-bottom:15px}
|
||||
#checkboxTerms input{margin:5px 10px 0 0;width:16px;height:16px}
|
||||
#checkboxTerms p{margin:0;font-size:0.9rem}
|
||||
.cf-turnstile{margin:0 auto 15px;display:table}
|
||||
body {
|
||||
overflow: hidden !important;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.navbar,
|
||||
.sidebar,
|
||||
#websitewidemessage,
|
||||
.subnav-bar,
|
||||
#footer {
|
||||
display: none !important;
|
||||
}
|
||||
.auth-container {
|
||||
min-height: 100vh;
|
||||
height: 100vh;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
|
||||
<div class="auth-container">
|
||||
<div class="auth-box">
|
||||
<center><h1 class="title-logo-lander">VORTEXI</h1>
|
||||
<p>relive childhood memories with <b>freedom</b></p></center>
|
||||
{% with messages = get_flashed_messages() %}
|
||||
{% if messages %}
|
||||
<div>
|
||||
{% for message in messages %}
|
||||
<div class="alert border border-danger p-2 text-center messagealerts">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
<form action="/signup" method="post">
|
||||
<input type="text" id="username" name="username" placeholder="Username" required>
|
||||
<input type="password" name="password" id="password" placeholder="Password" required>
|
||||
<div id="checkboxTerms">
|
||||
<input name="agreetoTermsandPrivacy" type="checkbox" id="agreeCheckbox" required>
|
||||
<label for="agreeCheckbox">
|
||||
<p>I agree to the <a href="/terms">Terms of Service</a> and <a href="/privacy">Privacy Policy</a></p>
|
||||
</label>
|
||||
</div>
|
||||
<div class="cf-turnstile" data-theme="dark" data-sitekey="{{turnstilekey}}"></div>
|
||||
{% if SignupEnabled: %}<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>{%endif%}
|
||||
<button type="submit" {% if not SignupEnabled: %}disabled{% endif %}>Signup</button>
|
||||
</form>
|
||||
<div class="links">
|
||||
<p>Already have an account? <a href="/login">Login</a> • <a href="https://discord.gg/Bv7kYbnMYR">Discord</a></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="auth-image">
|
||||
<img src="/static/img/LoginImage2.png" alt="Signup Image">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,265 @@
|
||||
from flask import Blueprint, render_template, request, redirect, url_for, session, flash, redirect, make_response, abort
|
||||
from app.models.user import User
|
||||
from app.models.invite_key import InviteKey
|
||||
from app.models.usereconomy import UserEconomy
|
||||
from app.models.user_avatar import UserAvatar
|
||||
from app.models.login_records import LoginRecord
|
||||
from app.util.textfilter import FilterText, TextNotAllowedException
|
||||
from app.models.past_usernames import PastUsername
|
||||
from app.pages.login.login import CreateLoginRecord
|
||||
from app.extensions import db, limiter, csrf, redis_controller, get_remote_address
|
||||
from app.pages.avatar.avatar import AllowedBodyColors
|
||||
from app.routes.thumbnailer import TakeUserThumbnail
|
||||
import logging
|
||||
import hashlib
|
||||
from datetime import datetime
|
||||
from app.util import websiteFeatures, auth, turnstile, redislock
|
||||
from app.services import invitekeys, proxydetection
|
||||
from sqlalchemy import func
|
||||
import random
|
||||
from datetime import datetime, timedelta
|
||||
from config import Config
|
||||
import string
|
||||
# ariez was here, 25th september 2025. first time this source has been touched in 4 months
|
||||
# hopefully mapleai will live on, mapleai.de
|
||||
|
||||
config = Config()
|
||||
signup = Blueprint("signup", __name__, template_folder="pages")
|
||||
|
||||
@signup.route("/signup", methods=["GET"])
|
||||
@limiter.exempt()
|
||||
def signup_page():
|
||||
if auth.isAuthenticated():
|
||||
return redirect("/home")
|
||||
SignupEnabled = True
|
||||
if not websiteFeatures.GetWebsiteFeature("WebSignup"):
|
||||
flash("Signups are temporarily disabled")
|
||||
SignupEnabled = False
|
||||
return render_template("signup/signup.html", SignupEnabled=SignupEnabled)
|
||||
|
||||
def RateLimitReached(e):
|
||||
flash("You are being rate limited. Please try again later.")
|
||||
return redirect("/signup")
|
||||
|
||||
def isUsernameAllowed( username : str ):
|
||||
if len(username) < 3 or len(username) > 20:
|
||||
return False, "Usernames must be between 3 and 20 characters long"
|
||||
|
||||
if username[0] == "_" or username[-1] == "_":
|
||||
return False, "Usernames must not start or end with an underscore"
|
||||
|
||||
if username.count("_") > 1:
|
||||
return False, "Usernames must not contain more than one underscore"
|
||||
|
||||
if username.count(" ") > 0:
|
||||
return False, "Usernames must not contain spaces"
|
||||
|
||||
allowedCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"
|
||||
hasLetter = False
|
||||
for char in username:
|
||||
if char not in allowedCharacters:
|
||||
return False, "Usernames must only contain letters, numbers and underscores"
|
||||
if char in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ":
|
||||
hasLetter = True
|
||||
|
||||
if not hasLetter:
|
||||
return False, "Usernames must contain at least one letter"
|
||||
|
||||
return True, "Allowed"
|
||||
|
||||
@signup.route("/signup", methods=["POST"])
|
||||
@limiter.limit("10/minute", on_breach=RateLimitReached)
|
||||
def signup_post():
|
||||
if auth.isAuthenticated():
|
||||
return redirect("/home")
|
||||
username = request.form.get("username", default=None, type=str)
|
||||
password = request.form.get("password", default=None, type=str)
|
||||
agreedtoTerms = request.form.get("agreetoTermsandPrivacy", default="off", type=str) == "on"
|
||||
#invitekey = request.form.get("invite-key", default=None, type=str)
|
||||
|
||||
if not websiteFeatures.GetWebsiteFeature("WebSignup"):
|
||||
flash("Signups are temporarily disabled")
|
||||
return redirect("/signup")
|
||||
if 'cf-turnstile-response' not in request.form or request.form.get('cf-turnstile-response') == '':
|
||||
flash("Please complete the captcha", "error")
|
||||
return redirect("/signup")
|
||||
|
||||
if username is None or password is None: #or invitekey is None:
|
||||
flash("Please fill in all fields")
|
||||
return redirect("/signup")
|
||||
|
||||
if not agreedtoTerms:
|
||||
flash("You must agree to the Terms of service and privacy policy")
|
||||
return redirect("/signup")
|
||||
|
||||
allowed, reason = isUsernameAllowed(username)
|
||||
if not allowed:
|
||||
flash(reason)
|
||||
return redirect("/signup")
|
||||
|
||||
if len(password) < 8:
|
||||
flash("Password must be at least 8 characters long")
|
||||
return redirect("/signup")
|
||||
|
||||
try:
|
||||
FilterText( Text = username, ThrowException = True, UseExtendedBadWords=True)
|
||||
except TextNotAllowedException:
|
||||
flash("Username is not friendly for Vortexi")
|
||||
return redirect("/signup")
|
||||
|
||||
RequestAddressRisk : int = proxydetection.fetch_address_risk( get_remote_address() )
|
||||
# if RequestAddressRisk == 1:
|
||||
# flash("Please disable your VPN or proxy and try again")
|
||||
# return redirect("/signup")
|
||||
|
||||
if RequestAddressRisk == 3:
|
||||
flash("Error 255, please report in our discord.") # We want to be as vague as possible
|
||||
return redirect("/signup")
|
||||
redisResultIP = redis_controller.get("signupcooldown:" + get_remote_address())
|
||||
redisResultToken = None
|
||||
if request.cookies.get("t", default=None, type=str) is not None:
|
||||
redisResultToken = redis_controller.get("signupcooldown:token:" + str(request.cookies.get(key="t", default=None, type=str)))
|
||||
|
||||
if redisResultIP is not None or redisResultToken is not None:
|
||||
if redisResultIP is None:
|
||||
redis_controller.set("signupcooldown:" + get_remote_address(), "1", ex=60*60*24*7)
|
||||
if redisResultToken is None and request.cookies.get("t", default=None, type=str) is not None:
|
||||
redis_controller.set("signupcooldown:token:" + str(request.cookies.get(key="t", default=None, type=str)), "1", ex=60*60*24*7)
|
||||
|
||||
flash("You have recently made an account in the past 7 days.")
|
||||
return redirect("/signup")
|
||||
|
||||
SessionToken = request.cookies.get(key="t", default=None, type=str)
|
||||
if SessionToken is None:
|
||||
flash("An issue occured while creating your account. Please try again later.")
|
||||
return redirect("/signup")
|
||||
if len(SessionToken) != 128:
|
||||
flash("An issue occured while creating your account. Please try again later.")
|
||||
return redirect("/signup")
|
||||
for char in SessionToken:
|
||||
if char not in (string.ascii_letters + string.digits):
|
||||
flash("An issue occured while creating your account. Please try again later.")
|
||||
return redirect("/signup")
|
||||
|
||||
UserIPHash = hashlib.md5(get_remote_address().encode("utf-8")).hexdigest()
|
||||
LoginRecords : list[LoginRecord] = LoginRecord.query.filter(LoginRecord.ip == UserIPHash).distinct(LoginRecord.userid).all()
|
||||
for record in LoginRecords:
|
||||
if record.User.accountstatus != 1:
|
||||
if record.session_token != SessionToken:
|
||||
NewLoginRecord = LoginRecord(
|
||||
userid = record.userid,
|
||||
ip = UserIPHash,
|
||||
session_token = SessionToken,
|
||||
useragent = request.headers.get("User-Agent"),
|
||||
timestamp = datetime.utcnow()
|
||||
)
|
||||
db.session.add(NewLoginRecord)
|
||||
db.session.commit()
|
||||
|
||||
flash("An issue occured while creating your account. Please try again later.")
|
||||
return redirect("/signup")
|
||||
LoginRecords : list[LoginRecord] = LoginRecord.query.filter(LoginRecord.session_token == SessionToken).distinct(LoginRecord.userid).all()
|
||||
for record in LoginRecords:
|
||||
if record.User.accountstatus != 1:
|
||||
NewLoginRecord = LoginRecord(
|
||||
userid = record.userid,
|
||||
ip = UserIPHash,
|
||||
session_token = SessionToken,
|
||||
useragent = request.headers.get("User-Agent"),
|
||||
timestamp = datetime.utcnow()
|
||||
)
|
||||
db.session.add(NewLoginRecord)
|
||||
db.session.commit()
|
||||
|
||||
flash("An issue occured while creating your account. Please try again later.")
|
||||
return redirect("/signup")
|
||||
|
||||
UserSignupLock = redislock.acquire_lock("UserSignupLock", acquire_timeout = 20, lock_timeout=1)
|
||||
if not UserSignupLock:
|
||||
flash("An issue occured while creating your account. Please try again later.")
|
||||
return redirect("/signup")
|
||||
|
||||
user = User.query.filter(func.lower(User.username) == func.lower(username)).first()
|
||||
if user is not None:
|
||||
redislock.release_lock("UserSignupLock", UserSignupLock)
|
||||
flash("Username already taken")
|
||||
return redirect("/signup")
|
||||
|
||||
pastUsername = PastUsername.query.filter(func.lower(PastUsername.username) == func.lower(username)).first()
|
||||
if pastUsername is not None:
|
||||
redislock.release_lock("UserSignupLock", UserSignupLock)
|
||||
flash("Username already taken")
|
||||
return redirect("/signup")
|
||||
|
||||
# try:
|
||||
# inviteKey : InviteKey = invitekeys.GetInviteKey(invitekey)
|
||||
# except invitekeys.InviteExceptions.InvalidInviteKey:
|
||||
# redislock.release_lock("UserSignupLock", UserSignupLock)
|
||||
# flash("Invalid invite key")
|
||||
# return redirect("/signup")
|
||||
# if inviteKey.used_by is not None:
|
||||
# redislock.release_lock("UserSignupLock", UserSignupLock)
|
||||
# flash("Invite key already used")
|
||||
# return redirect("/signup")
|
||||
# InviteKeyCreator : User = User.query.filter_by(id=inviteKey.created_by).first()
|
||||
# if InviteKeyCreator is not None:
|
||||
# if InviteKeyCreator.accountstatus != 1:
|
||||
# redislock.release_lock("UserSignupLock", UserSignupLock)
|
||||
# flash("Invite key creator's account is banned")
|
||||
# return redirect("/signup")
|
||||
|
||||
if not turnstile.VerifyToken(request.form.get('cf-turnstile-response')):
|
||||
redislock.release_lock("UserSignupLock", UserSignupLock)
|
||||
flash("Invalid captcha", "error")
|
||||
|
||||
if request.cookies.get(key="t", default=None, type=str) is None:
|
||||
abort(400)
|
||||
|
||||
redis_controller.set("signupcooldown:" + get_remote_address(), "1", ex=60*60*24*7)
|
||||
if request.cookies.get("t", default=None, type=str) is not None:
|
||||
redis_controller.set("signupcooldown:token:" + str(request.cookies.get(key="t", default=None, type=str)), "1", ex=60*60*24*7)
|
||||
|
||||
NewRegisteredUser : User = User(
|
||||
username = username,
|
||||
password = "",
|
||||
created = datetime.utcnow(),
|
||||
lastonline = datetime.utcnow(),
|
||||
)
|
||||
|
||||
db.session.add(NewRegisteredUser)
|
||||
db.session.commit()
|
||||
|
||||
auth.SetPassword(NewRegisteredUser, password)
|
||||
|
||||
userEconomy = UserEconomy(
|
||||
userid = NewRegisteredUser.id,
|
||||
robux = 0,
|
||||
tix = 0
|
||||
)
|
||||
db.session.add(userEconomy)
|
||||
|
||||
userAvatar : UserAvatar = UserAvatar(
|
||||
user_id=NewRegisteredUser.id
|
||||
)
|
||||
userAvatar.torso_color_id = random.choice(AllowedBodyColors)
|
||||
db.session.add(userAvatar)
|
||||
|
||||
#invitekeys.UseInviteKey(invitekey, NewRegisteredUser)
|
||||
|
||||
db.session.commit()
|
||||
redislock.release_lock("UserSignupLock", UserSignupLock)
|
||||
logging.info(f"New user registered: {NewRegisteredUser.username} ({NewRegisteredUser.id})")
|
||||
|
||||
TakeUserThumbnail(NewRegisteredUser.id)
|
||||
sessionToken = auth.CreateToken(userid=NewRegisteredUser.id, ip=get_remote_address())
|
||||
resp = make_response(redirect("/home"))
|
||||
resp.set_cookie(".ROBLOSECURITY", sessionToken, expires=datetime.utcnow() + timedelta(days=365), domain=f".{config.BaseDomain}")
|
||||
|
||||
if request.cookies.get("t", default=None, type=str) is not None:
|
||||
NewToken = ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(128))
|
||||
resp.set_cookie("t", NewToken, expires=datetime.utcnow() + timedelta(days=365), domain=f".{config.BaseDomain}")
|
||||
CreateLoginRecord( NewRegisteredUser.id, NewToken )
|
||||
else:
|
||||
CreateLoginRecord( NewRegisteredUser.id )
|
||||
|
||||
return resp
|
||||
Reference in New Issue
Block a user