@icon("./security-icon.svg") @tool extends Resource ## Used to store and load token's and to exchange them through the code. ## Try to avoid debugging this object cause it leaks your access and refresh tokens ## Hint never store the token value as string in your code to reduce the chance ## to leak the tokens always use the getter. class_name OAuthToken static var CRYPTO: Crypto = Crypto.new() ## Key for encryption purpose to save the tokens @export var _crypto_key_provider: CryptoKeyProvider = preload("res://addons/twitcher/lib/oOuch/default_key_provider.tres") ## Unique identifier to store multiple tokens within one config file @export var _identifier: String = "Auth-%s" % randi_range(0, 10000) ## Storage where the tokens should be saved encrypted (multiple secrets can be put in the same file see _identifier) @export var _cache_path: String = "user://auth.conf": set(val): _cache_path = val _load_tokens() var _scopes: PackedStringArray = [] var _expire_date: int var _config_file: ConfigFile = ConfigFile.new() var _access_token: String = "": set(val): _access_token = val if val != "": authorized.emit() var _refresh_token: String = "" ## Called when the token was resolved / accesstoken got refreshed signal authorized func update_values(access_token: String, refresh_token: String, expire_in: int, scopes: Array[String]): _expire_date = roundi(Time.get_unix_time_from_system() + expire_in) _access_token = access_token _refresh_token = refresh_token _scopes = scopes _persist_tokens() emit_changed() ## Persists the tokesn with the expire date func _persist_tokens(): var encrypted_access_token = _crypto_key_provider.encrypt(_access_token.to_utf8_buffer()) var encrypted_refresh_token = _crypto_key_provider.encrypt(_refresh_token.to_utf8_buffer()) _config_file.load(_cache_path) _config_file.set_value(_identifier, "expire_date", _expire_date) _config_file.set_value(_identifier, "access_token", Marshalls.raw_to_base64(encrypted_access_token)) _config_file.set_value(_identifier, "refresh_token", Marshalls.raw_to_base64(encrypted_refresh_token)) _config_file.set_value(_identifier, "scopes", ",".join(_scopes)) var err = _config_file.save(_cache_path) if err != OK: push_error("Couldn't save tokens cause of ", error_string(err)) ## Loads the tokens and returns the information if the file got created func _load_tokens() -> bool: var status = _config_file.load(_cache_path) if status == OK && _config_file.has_section(_identifier): _expire_date = _config_file.get_value(_identifier, "expire_date", 0) var encrypted_access_token: PackedByteArray = Marshalls.base64_to_raw(_config_file.get_value(_identifier, "access_token")) var encrypted_refresh_token: PackedByteArray = Marshalls.base64_to_raw(_config_file.get_value(_identifier, "refresh_token")) _access_token = _crypto_key_provider.decrypt(encrypted_access_token).get_string_from_utf8() _refresh_token = _crypto_key_provider.decrypt(encrypted_refresh_token).get_string_from_utf8() _scopes = _config_file.get_value(_identifier, "scopes", "").split(",", false) emit_changed() return true return false func remove_tokens() -> void: var status = _config_file.load(_cache_path) if status == OK && _config_file.has_section(_identifier): _access_token = "" _refresh_token = "" _expire_date = 0 _scopes.clear() _config_file.erase_section(_identifier) var err = _config_file.save(_cache_path) if err != OK: push_error("Couldn't save tokens cause of ", error_string(err)) emit_changed() print("%s got revoked" % _identifier) else: print("%s not found" % _identifier) func get_refresh_token() -> String: return _refresh_token func get_access_token() -> String: if not is_token_valid(): await authorized return _access_token func get_scopes() -> PackedStringArray: return _scopes ## The unix timestamp when the token is expiring func get_expiration() -> int: return _expire_date func get_expiration_readable() -> String: if _expire_date == 0: return "Not available" return Time.get_datetime_string_from_unix_time(_expire_date, true) func invalidate() -> void: _expire_date = 0 _refresh_token = "" _access_token = "" _scopes = [] emit_changed() ## Does this accesstoken has a refresh token func has_refresh_token() -> bool: return _refresh_token != "" && _refresh_token != null ## Checks if the access token is still valid func is_token_valid() -> bool: var current_time = Time.get_unix_time_from_system() return current_time < _expire_date ## Get all token names within a config file static func get_identifiers(cache_file: String) -> PackedStringArray: var _config_file: ConfigFile = ConfigFile.new() var status = _config_file.load(cache_file) if status != OK: return [] return _config_file.get_sections()