114 lines
3.5 KiB
GDScript
114 lines
3.5 KiB
GDScript
class_name ServerStore
|
||
extends RefCounted
|
||
## Gespeicherte Admin-Server des Editors (Name, Adresse, Port, https, Token).
|
||
##
|
||
## Das Token liegt NICHT im Klartext auf der Platte: die Datei ist mit einem
|
||
## aus Rechner-ID + Nutzerordner abgeleiteten Schlüssel verschlüsselt. Das hält
|
||
## Zufallsleser, Cloud-Sync und Screenshots von Ordnern raus – gegen Schadsoftware
|
||
## mit Zugriff auf deinen Benutzer schützt es nicht (dafür gibt es keine
|
||
## plattformübergreifende Lösung ohne Passwort-Manager). Tokens werden nie geloggt.
|
||
|
||
const DEFAULT_PATH := "user://admin_server.dat"
|
||
const DEFAULT_PORT := 24580
|
||
const MAX_SERVERS := 50
|
||
|
||
var path: String = DEFAULT_PATH
|
||
var servers: Array = [] # [{id, name, host, port, tls, token}]
|
||
var last_error: String = ""
|
||
|
||
|
||
func _init(p: String = DEFAULT_PATH) -> void:
|
||
path = p
|
||
|
||
|
||
func _key() -> String:
|
||
return ("svr-editor-admin|%s|%s" % [OS.get_unique_id(), OS.get_user_data_dir()]).sha256_text()
|
||
|
||
|
||
func load_store() -> void:
|
||
servers.clear()
|
||
last_error = ""
|
||
if not FileAccess.file_exists(path):
|
||
return
|
||
var fa := FileAccess.open_encrypted_with_pass(path, FileAccess.READ, _key())
|
||
if fa == null:
|
||
last_error = "Gespeicherte Server konnten nicht gelesen werden (andere Installation oder beschädigte Datei)."
|
||
return
|
||
var parsed: Variant = JSON.parse_string(fa.get_as_text())
|
||
if parsed is Array:
|
||
for e in parsed:
|
||
if e is Dictionary and str(e.get("host", "")) != "":
|
||
servers.append({
|
||
"id": str(e.get("id", _new_id())), "name": str(e.get("name", e["host"])),
|
||
"host": str(e["host"]), "port": int(e.get("port", DEFAULT_PORT)),
|
||
"tls": bool(e.get("tls", false)), "token": str(e.get("token", ""))})
|
||
|
||
|
||
func save_store() -> bool:
|
||
var fa := FileAccess.open_encrypted_with_pass(path, FileAccess.WRITE, _key())
|
||
if fa == null:
|
||
last_error = "Server-Liste konnte nicht gespeichert werden (Fehler %d)." % FileAccess.get_open_error()
|
||
return false
|
||
fa.store_string(JSON.stringify(servers))
|
||
return true
|
||
|
||
|
||
## Neuer oder aktualisierter Eintrag. Rückgabe: id ("" = Eingabe ungültig → last_error).
|
||
func upsert(entry: Dictionary) -> String:
|
||
var err := AdminClient.check_address(str(entry.get("host", "")), int(entry.get("port", 0)))
|
||
if err != "":
|
||
last_error = err
|
||
return ""
|
||
var e := {
|
||
"id": str(entry.get("id", "")), "name": str(entry.get("name", "")).strip_edges(),
|
||
"host": str(entry["host"]).strip_edges(), "port": int(entry["port"]),
|
||
"tls": bool(entry.get("tls", false)), "token": str(entry.get("token", "")).strip_edges()}
|
||
if e["name"] == "":
|
||
e["name"] = "%s:%d" % [e["host"], e["port"]]
|
||
if e["id"] == "":
|
||
if servers.size() >= MAX_SERVERS:
|
||
last_error = "Maximal %d gespeicherte Server." % MAX_SERVERS
|
||
return ""
|
||
e["id"] = _new_id()
|
||
servers.append(e)
|
||
else:
|
||
var i := index_of(e["id"])
|
||
if i < 0:
|
||
servers.append(e)
|
||
else:
|
||
servers[i] = e
|
||
save_store()
|
||
return e["id"]
|
||
|
||
|
||
func remove(id: String) -> bool:
|
||
var i := index_of(id)
|
||
if i < 0:
|
||
return false
|
||
servers.remove_at(i)
|
||
save_store()
|
||
return true
|
||
|
||
|
||
func index_of(id: String) -> int:
|
||
for i in servers.size():
|
||
if str(servers[i]["id"]) == id:
|
||
return i
|
||
return -1
|
||
|
||
|
||
func get_entry(id: String) -> Dictionary:
|
||
var i := index_of(id)
|
||
return servers[i].duplicate() if i >= 0 else {}
|
||
|
||
|
||
## Für Anzeige/Logs: nie das Token.
|
||
func public_view(id: String) -> Dictionary:
|
||
var e := get_entry(id)
|
||
e.erase("token")
|
||
e["has_token"] = str(get_entry(id).get("token", "")) != ""
|
||
return e
|
||
|
||
|
||
static func _new_id() -> String:
|
||
return "s%d_%d" % [int(Time.get_unix_time_from_system()), randi() % 100000]
|