Server-authoritative Godot 4.6 strategy game. Includes the headless WebSocket game server, the browser client, and Nix packaging (server and web-export derivations plus a NixOS module) for deployment.
73 lines
2.1 KiB
GDScript
73 lines
2.1 KiB
GDScript
class_name LoginManager
|
|
|
|
extends RefCounted
|
|
|
|
## Per-game player credentials. There is no account system: a player picks a
|
|
## name and password the first time they join a particular game, and the same
|
|
## pair authenticates them when they come back to that game. A manager lives on
|
|
## the authoritative server for the lifetime of the game, so credentials never
|
|
## cross between games.
|
|
##
|
|
## Passwords are never kept in the clear. Each game salts and hashes them, so
|
|
## the stored value is useless outside this game.
|
|
|
|
## Name (stripped) -> salted SHA-256 hash of the password.
|
|
var _passwords: Dictionary = {}
|
|
var _salt: String
|
|
|
|
|
|
## `salt` is only supplied by tests; a real game gets a fresh random salt.
|
|
func _init(salt: String = "") -> void:
|
|
_salt = salt if not salt.is_empty() else _random_salt()
|
|
|
|
|
|
func has_player(name: String) -> bool:
|
|
return _passwords.has(_key(name))
|
|
|
|
|
|
func player_count() -> int:
|
|
return _passwords.size()
|
|
|
|
|
|
## Authenticates a joining player, registering them the first time their name is
|
|
## seen. That first join is the only "registration" in the game. Returns true
|
|
## when the player may enter.
|
|
func login(name: String, password: String) -> bool:
|
|
var key := _key(name)
|
|
if key.is_empty() or password.is_empty():
|
|
return false
|
|
if not _passwords.has(key):
|
|
_passwords[key] = _hash(password)
|
|
return true
|
|
return _passwords[key] == _hash(password)
|
|
|
|
|
|
## Explicitly sets a password for a new player. Returns false when the name is
|
|
## invalid or already taken.
|
|
func register(name: String, password: String) -> bool:
|
|
var key := _key(name)
|
|
if key.is_empty() or password.is_empty() or _passwords.has(key):
|
|
return false
|
|
_passwords[key] = _hash(password)
|
|
return true
|
|
|
|
|
|
## Checks a password for a name that is expected to exist.
|
|
func authenticate(name: String, password: String) -> bool:
|
|
var key := _key(name)
|
|
if key.is_empty() or not _passwords.has(key):
|
|
return false
|
|
return _passwords[key] == _hash(password)
|
|
|
|
|
|
func _key(name: String) -> String:
|
|
return name.strip_edges()
|
|
|
|
|
|
func _hash(password: String) -> String:
|
|
return (_salt + password).sha256_text()
|
|
|
|
|
|
func _random_salt() -> String:
|
|
return "%d:%d" % [Time.get_ticks_usec(), randi()]
|