This commit is contained in:
copyrighttxt
2026-07-06 13:28:00 -04:00
commit 05211f73ef
441 changed files with 94128 additions and 0 deletions
+271
View File
@@ -0,0 +1,271 @@
import hashlib
import logging
import time
import string
import secrets
import pyotp
from argon2 import PasswordHasher
from config import Config
from flask import request, redirect, jsonify, make_response, abort, g
from functools import wraps
from app.models.user import User
from app.models.placeservers import PlaceServer
from app.models.gameservers import GameServer
from app.extensions import db, redis_controller, get_remote_address
config = Config()
def ValidateToken( token : str ) -> bool:
TokenInfo = redis_controller.get("authtoken_" + token)
if TokenInfo is None:
return False
# Token Info Format
# userid|created|expiry|ip
TokenInfo = TokenInfo.split("|")
if len(TokenInfo) != 4:
redis_controller.delete("authtoken_" + token)
return False
if int(TokenInfo[2]) < int(time.time()):
redis_controller.delete("authtoken_" + token)
return False
return True
def GetAuthenticatedUser( token : str ) -> User:
AuthTokenInfo = GetTokenInfo(token)
if AuthTokenInfo is None:
return None
return User.query.filter_by(id=int(AuthTokenInfo[0])).first()
def GetTokenInfo( token : str ) -> list:
TokenInfo = redis_controller.get("authtoken_" + token)
if TokenInfo is None:
return None
# Token Info Format
# userid|created|expiry|ip
TokenInfo = TokenInfo.split("|")
if len(TokenInfo) != 4:
redis_controller.delete("authtoken_" + token)
return None
if int(TokenInfo[2]) < int(time.time()):
redis_controller.delete("authtoken_" + token)
return None
return TokenInfo
def CreateToken( userid : int , ip, expireIn : int = (60 * 60 * 24 * 31)) -> str:
Token = ''.join(secrets.choice(string.ascii_letters + string.digits) for _ in range(512))
# Token Info Format
# userid|created|expiry|ip
TokenInfo = str(userid) + "|" + str(int(time.time())) + "|" + str(int(time.time()) + expireIn) + "|" + ip
redis_controller.set("authtoken_" + Token, TokenInfo, ex = expireIn)
return Token
def isAuthenticated():
if ".ROBLOSECURITY" not in request.cookies:
return False
if not ValidateToken(request.cookies[".ROBLOSECURITY"]):
return False
return True
def authenticated_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if ".ROBLOSECURITY" not in request.cookies:
return redirect("/login")
if not ValidateToken(request.cookies[".ROBLOSECURITY"]):
RedirectResponse = make_response(redirect("/login"))
RedirectResponse.set_cookie(".ROBLOSECURITY", expires=0)
return RedirectResponse
return f(*args, **kwargs)
return decorated_function
def authenticated_required_api(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if ".ROBLOSECURITY" not in request.cookies:
return jsonify({"success": False, "message": "You are not logged in"}), 401
if not ValidateToken(request.cookies[".ROBLOSECURITY"]):
ErrorResponse = make_response(jsonify({"success": False, "message": "You are not logged in"}), 401)
ErrorResponse.set_cookie(".ROBLOSECURITY", expires=0)
return ErrorResponse
return f(*args, **kwargs)
return decorated_function
def authenticated_client_endpoint(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if request.cookies.get("Syntax-Session-Id", type = str ) is not None:
DataSections = request.cookies.get("Syntax-Session-Id", type = str )
else:
return jsonify({"success": False, 'error': 'Missing Headers'}), 401
if len(DataSections) != 9:
return jsonify({"success": False, 'error': 'Invalid Headers'}), 401
AuthToken = DataSections[8]
if not ValidateToken(AuthToken):
return jsonify({"success": False, 'error': 'Invalid Token'}), 401
PlaceServerObj : PlaceServer = PlaceServer.query.filter_by(serveruuid=str(DataSections[1])).first()
if PlaceServerObj is None:
invalidateToken(AuthToken)
return jsonify({"success": False, 'error': 'Invalid Token'}), 401
return f(*args, **kwargs)
return decorated_function
def gameserver_authenticated_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if "Roblox/" not in request.user_agent.string:
abort(404)
RemoteAddress = get_remote_address()
GameServerObj : GameServer = GameServer.query.filter_by( serverIP = RemoteAddress ).first()
if GameServerObj is None:
abort(404)
requesterAccessKey = request.headers.get( key = "AccessKey", type = str, default = "")
if "UserRequest" in requesterAccessKey:
logging.warning(f"GameServer {RemoteAddress} - UserRequest access key used for {request.url}")
abort(404)
return f(*args, **kwargs)
return decorated_function
def gameserver_accesskey_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if "AccessKey" not in request.headers:
abort(404)
RequesterAccessKey = request.headers.get( key = "AccessKey", type = str, default = "")
RemoteAddress = get_remote_address()
GameServerObj : GameServer = GameServer.query.filter_by( serverIP = RemoteAddress, accessKey = RequesterAccessKey ).first()
if GameServerObj is None:
abort(404)
return f(*args, **kwargs)
return decorated_function
def invalidateToken(token : str):
if token is not None:
redis_controller.delete("authtoken_" + token)
def Validate2FACode( userid : int, code : str ) -> bool:
from app.pages.settings.settings import generate_secret_key_from_string
"""
Validates a 2FA code for a user
:param userid: The user id to validate the code for
:param code: The code to validate
:returns: bool (True if correct, False if not)
"""
user : User = User.query.filter_by(id=userid).first()
if user is None:
return False
if user.TOTPEnabled == False:
return False
totp = pyotp.TOTP(generate_secret_key_from_string(str(user.id) + config.FLASK_SESSION_KEY))
return totp.verify(code)
def GetCurrentUser() -> User:
"""
Gets the current user from the request
:returns: User (User object if logged in, None if not)
"""
def _get_user() -> User:
if ".ROBLOSECURITY" not in request.cookies:
if "Syntax-Session-Id" not in request.cookies:
return None
else:
DataSections = request.cookies["Syntax-Session-Id"].split("|")
if len(DataSections) != 9:
return None
AuthToken = DataSections[8]
if not ValidateToken(AuthToken):
return None
PlaceServerObj : PlaceServer = PlaceServer.query.filter_by(serveruuid=str(DataSections[1])).first()
if PlaceServerObj is None:
invalidateToken(AuthToken)
return None
AuthTokenInfo = GetTokenInfo(AuthToken)
return User.query.filter_by(id=int(AuthTokenInfo[0])).first()
if not ValidateToken(request.cookies[".ROBLOSECURITY"]):
return None
AuthTokenInfo = GetTokenInfo(request.cookies[".ROBLOSECURITY"])
if AuthTokenInfo is None:
return None
return User.query.filter_by(id=int(AuthTokenInfo[0])).first()
if not hasattr(g, 'current_authenticated_user'):
g.current_authenticated_user = _get_user()
return g.current_authenticated_user
def _GetArgonSalt( UserObj : User ) -> bytes:
return (config.FLASK_SESSION_KEY + str(UserObj.id)).encode('utf-8')
def _GetPasswordHasher() -> PasswordHasher:
return PasswordHasher(
time_cost=16,
memory_cost=2**14,
parallelism=2,
hash_len=32,
salt_len=16
)
def VerifyPassword( UserObj : User, password : str ) -> bool:
"""
Verifies the password of the given user
:param UserObj: The user object to verify the password of
:param password: The password to verify
:returns: bool (True if correct, False if not)
"""
if config.SWITCH_TO_ARGON_PASSWORD_HASH: # Switch this when we migrate to argon
argon_ph = _GetPasswordHasher()
if not UserObj.password.startswith("$argon2id"): # Handle old passwords in sha512
isCorrect = hashlib.sha512(password.encode('utf-8')).hexdigest() == UserObj.password
if isCorrect:
UserObj.password = PasswordHasher().hash(password, salt = _GetArgonSalt(UserObj) )
logging.info(f"User {UserObj.username} [{UserObj.id}] migrated to argon2id password hash")
db.session.commit()
return isCorrect
try:
return argon_ph.verify(UserObj.password, password)
except:
return False
else:
return hashlib.sha512(password.encode('utf-8')).hexdigest() == UserObj.password
def SetPassword( UserObj : User, password : str ):
"""
Sets the password of the given user
:param UserObj: The user object to set the password of
:param password: The password to set
:returns: bool (True if successful, False if not)
"""
if config.SWITCH_TO_ARGON_PASSWORD_HASH: # Switch this when we migrate to argon
argon_ph = _GetPasswordHasher()
UserObj.password = argon_ph.hash(password, salt = _GetArgonSalt(UserObj) )
else:
UserObj.password = hashlib.sha512(password.encode('utf-8')).hexdigest()
db.session.commit()
return True