Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/project.godot
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ FileManager="*uid://d2s50p717g3n"
AccountServers="*uid://bpysjoq7n0ytu"
Random="*uid://1js68qt8w0mv"
GlobalAccount="*uid://dtlb70kxvbtvn"
Events="*uid://c656spc3ppdlw"
UrlParser="*uid://budprjmmpally"
OAuth="*uid://jd7qlsley1no"

[display]

Expand Down
4 changes: 2 additions & 2 deletions src/scenes/players/player.gd
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,10 @@ func _input(event):
if Input.is_action_just_pressed("escape"):
if mouse_captured:
capture_mouse(false)
hud.set_active_state(true)
Events.emit_signal("dash_set_state", true)
else:
capture_mouse(true)
hud.set_active_state(false)
Events.emit_signal("dash_set_state", false)
return

if event is InputEventMouseMotion && mouse_captured:
Expand Down
125 changes: 92 additions & 33 deletions src/scripts/libs/account.gd
Original file line number Diff line number Diff line change
@@ -1,91 +1,133 @@
# --- License
# File: /client/src/scripts/libs/account.gd
# Project: OpenMinerva
# Created Date: 26 February 2026
# Copyright (c) 2026 OpenMinerva
# License: MIT License
# Authors: Armored Dragon
# --- License

extends Node

var http = preload("res://scripts/network/http.gd").new()
var time_lib = preload("res://scripts/libs/time.gd").new()
var oauth_lib = preload("res://scripts/libs/oauth.gd").new()

var random_lib = preload("res://scripts/utils/random.gd").new()
var rsa_lib = preload("res://scripts/crypto/rsa.gd").new()

const ACCOUNT_DATABASE_DIRECTORY: String = "user://database/accounts.bin"

# TODO: Create proper encryption of the account database
# https://github.com/OpenMinerva/client/issues/59
# This is just here for funsies for now.
const ENCRYPTION_KEY: String = "cb`$BaeGT12^)Zv{"
var stop_connection_timer = false

var active_account = {}
var _database = []

func _ready():
oauth_lib.start_server()
_load_account_database()

## Get a list of all accounts and return their information.
func get_all() -> Array:
return _database

## Adds an account to the account database.
func create(account: Dictionary) -> Dictionary:
func create(account: Dictionary, type: String) -> Dictionary:
# Make sure we are only recording data we are intending on.
var account_formatted: Dictionary = {}

if type == "oauth":
account_formatted = _create_oauth(account)

if len(account_formatted.keys()) == 0:
GlobalLogger.logs("Tried to create an account, but there was nothing to save.", 3)
return {"ok": false, "error": "No account formatted.", "id": null}

_database.append(account_formatted)

_save_account_database()

Events.emit_signal("dash_account_list_loaded")
return {"ok": true, "id": account_formatted.id}

func _create_oauth(account) -> Dictionary:
var _account_keys = rsa_lib.generate_keypair()

var _clean_account = {}
_clean_account.id = account.get("id", null)
_clean_account.username = account.get("username", null)
_clean_account.id = random_lib.random_string(6, true)
_clean_account.display_name = account.get("display_name", null)
_clean_account.account_server = account.get("account_server", null)
_clean_account.remember_me = account.get("remember_me", null)
_clean_account.local_account = account.get("local_account", null)
_clean_account.private_device_key = _account_keys.private
_clean_account.public_device_key = _account_keys.public

_database.append(_clean_account)
_save_account_database()
_clean_account.access_token = ""
_clean_account.refresh_token = ""
_clean_account.id_token = ""
_clean_account.access_token_expiry = 0

return {"ok": true, "id": account.id}
_clean_account.type = "oauth"
return _clean_account

## Removes an account from the account database.
func remove(id: String) -> Dictionary:
GlobalLogger.logs("Attempting to remove account '%s'" % id)
var target_entry = _database.find_custom(func(entry): return entry.get("id") == id)
_database.remove_at(target_entry)
_save_account_database()

Events.emit_signal("dash_account_list_loaded")

return {"ok": true}

## Sets an account as the active account.
func use(id: String) -> void:
GlobalLogger.logs("Setting active account to '%s'." % id, 1)

# TODO: Check if account is still valid without trying to sign in.
# await authenticate_oauth(id)
var _account = _get_account_by_id(id)
active_account = _account
Events.emit_signal("dash_active_account_changed", active_account)
return

## Signs out of the active account.
func clear() -> void:
active_account = {}
return

func authenticate(id: String, remember_me: bool = false):
GlobalLogger.logs("Attempting to connect account '%s' to their account server." % id)
func update(id: String, data: Dictionary) -> void:
var target_entry = _database.find_custom(func(entry): return entry.get("id") == id)
var account = _get_account_by_id(id)

var request_data = {"username": account.username, "rememberMe": remember_me, "account_server": account.account_server}
var _database_keys = account.keys()
var _data_keys = data.keys()

oauth_lib.start_oauth_process(request_data.account_server + "/oauth/authorize")
_try_check_connection()
for key in _data_keys:
if key not in _database_keys:
GlobalLogger.logs("Tried to update an invalid key in an account, '%s'." % key)
continue

func _try_check_connection():
if oauth_lib.auth_server_url:
stop_connection_timer = true
return
account[key] = data[key]

if stop_connection_timer == true:
return
_database[target_entry] = account
_save_account_database()
return

var timer = get_tree().create_timer(2.0)
timer.connect("timeout", oauth_lib.check_connection)
await timer.timeout
_try_check_connection()
func authenticate_oauth(id: String, remember_me: bool = false) -> void:
# TODO: Error checks
GlobalLogger.logs("Attempting to connect account '%s' using oauth." % id)
var account = _get_account_by_id(id)

# TODO: Check if account is still valid without trying to sign in.
var oauth_tokens = await OAuth.authenticate(account.account_server + "/oauth/authorize")

update(id, oauth_tokens)

## Save the current account database we have in memory to the disk.
func _save_account_database() -> void:
GlobalLogger.logs("Saving account database to disk.")
DirAccess.open("user://").make_dir_recursive("user://database")

# var file = FileAccess.open_encrypted(ACCOUNT_DATABASE_DIRECTORY, FileAccess.WRITE, ENCRYPTION_KEY.sha256_buffer())
var file = FileAccess.open(ACCOUNT_DATABASE_DIRECTORY, FileAccess.WRITE)

if file:
file.store_var(_database) # Serializes variable to binary
file.close()
Expand All @@ -95,12 +137,11 @@ func _save_account_database() -> void:
func _load_account_database() -> Array:
GlobalLogger.logs("Loading the local account database.", 1)

var account_file_exists = await FileAccess.file_exists(ACCOUNT_DATABASE_DIRECTORY)
var account_file_exists = FileAccess.file_exists(ACCOUNT_DATABASE_DIRECTORY)
if account_file_exists == false:
GlobalLogger.logs("Account database does not exist, creating one now.", 1)
await _save_account_database()
_save_account_database()

# var file = FileAccess.open_encrypted(ACCOUNT_DATABASE_DIRECTORY, FileAccess.READ, ENCRYPTION_KEY.sha256_buffer())
var file = FileAccess.open(ACCOUNT_DATABASE_DIRECTORY, FileAccess.READ)

var account_data
Expand Down Expand Up @@ -157,3 +198,21 @@ func _handle_response(response: Dictionary) -> Dictionary:
response_data.body = JSON.parse_string(response.get("body"))

return response_data

# DEV: Upload public key to the server.
# func test_upload_public_key_to_server():
# GlobalLogger.logs("Registering the device public key to the account server.")
# var body = {
# "public_key": active_account.public_device_key
# }
# var url_parts = UrlParser.deconstruct(active_account.account_server)
# if url_parts.ok == false:
# GlobalLogger.logs("Unhandled error registering the public device key to the account server. '%s'" % url_parts.error, 3)
# return
# url_parts = url_parts.data

# print(url_parts)

# var public_key_response = await http.req(HTTPClient.Method.METHOD_POST, url_parts.host, "/api/v1/device_key", url_parts.port, ["Accept: application/json", "Content-Type: application/json", "authorization: Bearer %s" % oauth_lib.access_token], JSON.stringify(body))
# print(public_key_response)
# return
Loading