A Libre Multiplayer FPS Game built with Godot 4 engine and a fully open-source toolchain https://libla.st
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

118 lines
3.6 KiB

extends Node
func _validate_username(username) -> bool:
if username.is_empty():
return false
elif username.replace(" ", "") != username:
return false
elif username.rstrip("@!?<>[]{}#$%^&*()-=+|\\/,`~") != username:
return false
elif len(username) < 4:
return false
elif len(username) > 24:
return false
elif username.to_lower() in ["username", "user", "player", "login", "name"]:
return false
return true
func verify_token(username_hash: String, token: Array) -> bool:
return Database.verify_token(username_hash, token)
func generate_token(username_hash:String):
var crypto = Crypto.new()
var buf = crypto.generate_random_bytes(512 - 64)
buf.encode_float(Time.get_unix_time_from_system(), 512 - 64)
var token = Marshalls.raw_to_base64(buf)
var best_before = round(Time.get_unix_time_from_system() + (60 * 60)) # tokens expire after one hour
prints("Generated token:", token)
return [token, best_before]
func player_auth(username_hash: String, password_hash: String) -> Array:
var result = Database.retrieve(Database.Table.PLAYER_ACCOUNTS, username_hash)
var pass1 = var_to_bytes(result["password_hash"])
var pass2 = var_to_bytes(password_hash)
var crypto = Crypto.new()
var password_match = crypto.constant_time_compare(pass1, pass2)
if password_match:
print("Password hash matches")
var token = generate_token(username_hash)
Database.store_user_token(username_hash, token)
return token
print("Password hash doesn't match")
func player_login(username_hash: String) -> String:
prints("Processing login request for account", username_hash)
var result = Database.retrieve(Database.Table.PLAYER_ACCOUNTS, username_hash)
if result:
# if player exists, send the player password hash so they can send back hashed password
prints("Account exists, returning password salt:", result["password_salt"])
return result["password_salt"]
else: # othwerwise return an empty string signifying the record is not there
prints("Account not found.")
return ""
func create_player_account(username: String, password_hash: String, password_salt: String) -> int:
# lets repeat all the validation done by the client just in case
if not _validate_username(username):
#var record = PlayerAccountRecord.new(username, password_hash, password_salt)
var record ={
"username" : username,
"password_hash" : password_hash,
"password_salt" : password_salt,
"creation_time" : Time.get_unix_time_from_system(),
"tokens" : [],
"display_name" : null,
"display_color" : null,
"avatar_storage_unit" : null,
if Database.store(Database.Table.PLAYER_ACCOUNTS, username.sha256_text(), record) == OK:
print("Successfully created player account ", username)
return OK
print("Account ", username, " already exists.")
func update_avatar(username_hash: String, hash: PackedByteArray, data:PackedByteArray):
var old_avatar_hash = Database.retrieve_user_record_item(username_hash, "avatar")
# check for old avatar
if Storage.string_from_hash(hash) == old_avatar_hash:
print("Attempting to replace the avatar with an identical one!")
# store new avatar data
var err = Storage.store(hash, data, username_hash)
if err != OK:
return err
# in the database we store a reference to the data in Storage
err = Database.modify_user_record_item(username_hash, "avatar", Storage.string_from_hash(hash))
if err != OK:
return err
# in need be, delete the old avatar
if old_avatar_hash:
err = Storage.delete(Storage.hash_from_string(old_avatar_hash))
if err != OK:
return err