Initial Commit
Initial commit of Code Base.
This commit is contained in:
parent
293b1213e1
commit
c11a4ebbc2
653 changed files with 36893 additions and 1 deletions
245
addons/twitcher/lib/oOuch/oauth_token_handler.gd
Normal file
245
addons/twitcher/lib/oOuch/oauth_token_handler.gd
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
@icon("./security-icon.svg")
|
||||
@tool
|
||||
extends Node
|
||||
|
||||
class_name OAuthTokenHandler
|
||||
|
||||
const OAuthHTTPClient = preload("res://addons/twitcher/lib/http/buffered_http_client.gd")
|
||||
const OAuthDeviceCodeResponse = preload("./oauth_device_code_response.gd")
|
||||
|
||||
## Handles refreshing and resolving access and refresh tokens.
|
||||
|
||||
const HEADERS = {
|
||||
"Accept": "*/*",
|
||||
"Content-Type": "application/x-www-form-urlencoded"
|
||||
}
|
||||
const SECONDS_TO_CHECK_EARLIER = 60
|
||||
|
||||
## Called when new access token is available
|
||||
signal token_resolved(tokens: OAuthToken)
|
||||
|
||||
## Called when token can't be refreshed cause auth was removed or refresh token expired
|
||||
signal unauthenticated()
|
||||
|
||||
## Where to get the tokens from
|
||||
@export var oauth_setting: OAuthSetting
|
||||
|
||||
## Holds the current set of tokens
|
||||
@export var token: OAuthToken: set = _update_token
|
||||
|
||||
## Client to request new tokens
|
||||
var _http_client : OAuthHTTPClient
|
||||
|
||||
## Is currently requesting tokens
|
||||
var _requesting_token: bool = false
|
||||
|
||||
## Timer to refresh tokens
|
||||
var _expiration_check_timer: Timer
|
||||
|
||||
func _ready() -> void:
|
||||
_http_client = OAuthHTTPClient.new()
|
||||
_http_client.name = "OAuthTokenClient"
|
||||
add_child(_http_client)
|
||||
|
||||
_expiration_check_timer = Timer.new()
|
||||
_expiration_check_timer.name = "ExpirationCheck"
|
||||
_expiration_check_timer.timeout.connect(refresh_tokens)
|
||||
add_child(_expiration_check_timer)
|
||||
update_expiration_check()
|
||||
|
||||
|
||||
func _enter_tree() -> void:
|
||||
if not is_instance_valid(token):
|
||||
token = OAuthToken.new()
|
||||
else:
|
||||
token.changed.connect(update_expiration_check)
|
||||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
if is_instance_valid(token):
|
||||
token.changed.disconnect(update_expiration_check)
|
||||
|
||||
|
||||
func _update_token(val: OAuthToken) -> void:
|
||||
if is_instance_valid(token) and is_inside_tree():
|
||||
token.changed.disconnect(update_expiration_check)
|
||||
token = val
|
||||
if is_instance_valid(token) and is_inside_tree():
|
||||
token.changed.connect(update_expiration_check)
|
||||
|
||||
|
||||
func update_expiration_check() -> void:
|
||||
var current_time = Time.get_unix_time_from_system()
|
||||
var expiration = token.get_expiration()
|
||||
if expiration == 0:
|
||||
_expiration_check_timer.stop()
|
||||
return
|
||||
_expiration_check_timer.start(expiration - current_time - SECONDS_TO_CHECK_EARLIER)
|
||||
logDebug("Refresh token (%s) in %s seconds" % [token._identifier, roundf(_expiration_check_timer.wait_time)])
|
||||
|
||||
|
||||
## Checks if tokens expires and starts refreshing it. (called often hold footprintt small)
|
||||
func _check_token_refresh() -> void:
|
||||
if _requesting_token: return
|
||||
|
||||
if token_needs_refresh():
|
||||
logInfo("Token (%s) needs refresh" % token._identifier)
|
||||
refresh_tokens()
|
||||
|
||||
|
||||
## Requests the tokens
|
||||
func request_token(grant_type: String, auth_code: String = ""):
|
||||
if _requesting_token: return
|
||||
_requesting_token = true
|
||||
logInfo("Request token (for %s) via '%s'" % [token._identifier, grant_type])
|
||||
var request_params = [
|
||||
"grant_type=%s" % grant_type,
|
||||
"client_id=%s" % oauth_setting.client_id,
|
||||
"client_secret=%s" % oauth_setting.get_client_secret()
|
||||
]
|
||||
|
||||
if auth_code != "":
|
||||
request_params.append("code=%s" % auth_code)
|
||||
if grant_type == "authorization_code":
|
||||
request_params.append("&redirect_uri=%s" % oauth_setting.redirect_url)
|
||||
|
||||
var request_body = "&".join(request_params)
|
||||
var request = _http_client.request(oauth_setting.token_url, HTTPClient.METHOD_POST, HEADERS, request_body)
|
||||
await _handle_token_request(request)
|
||||
_requesting_token = false
|
||||
|
||||
|
||||
func request_device_token(device_code_repsonse: OAuthDeviceCodeResponse, scopes: String, grant_type: String = "urn:ietf:params:oauth:grant-type:device_code") -> void:
|
||||
if _requesting_token: return
|
||||
_requesting_token = true
|
||||
logInfo("request token (for %s) via urn:ietf:params:oauth:grant-type:device_code" % token._identifier)
|
||||
var parameters = [
|
||||
"client_id=%s" % oauth_setting.client_id,
|
||||
"grant_type=%s" % grant_type,
|
||||
"device_code=%s" % device_code_repsonse.device_code,
|
||||
"scopes=%s" % scopes
|
||||
]
|
||||
|
||||
var request_body = "&".join(parameters)
|
||||
|
||||
# Time when the code is expired and we don't poll anymore
|
||||
var expire_data = Time.get_unix_time_from_system() + device_code_repsonse.expires_in
|
||||
|
||||
while expire_data > Time.get_unix_time_from_system():
|
||||
var request = _http_client.request(oauth_setting.token_url, HTTPClient.METHOD_POST, HEADERS, request_body)
|
||||
var response = await _http_client.wait_for_request(request)
|
||||
var response_string: String = response.response_data.get_string_from_utf8()
|
||||
var response_data = JSON.parse_string(response_string)
|
||||
if response.response_code == 200:
|
||||
_update_tokens_from_response(response_data)
|
||||
_requesting_token = false
|
||||
return
|
||||
elif response.response_code == 400 && response_string.contains("authorization_pending"):
|
||||
# Awaits for this amount of time until retry
|
||||
await get_tree().create_timer(device_code_repsonse.interval, true, false, true).timeout
|
||||
elif response.response_code == 400:
|
||||
unauthenticated.emit()
|
||||
_requesting_token = false
|
||||
return
|
||||
|
||||
# Handle Timeout
|
||||
unauthenticated.emit()
|
||||
_requesting_token = false
|
||||
|
||||
|
||||
## Uses the refresh token if possible to refresh all tokens
|
||||
func refresh_tokens() -> void:
|
||||
if not oauth_setting.is_valid():
|
||||
logDebug("Try to refresh token (%s) but oauth settings are invalid. Can't refresh token." % token._identifier)
|
||||
return
|
||||
|
||||
if _requesting_token: return
|
||||
_requesting_token = true
|
||||
logInfo("use refresh (%s) token" % token._identifier)
|
||||
if token.has_refresh_token():
|
||||
var request_body = "client_id=%s&client_secret=%s&refresh_token=%s&grant_type=refresh_token" % [oauth_setting.client_id, oauth_setting.get_client_secret(), token.get_refresh_token()]
|
||||
var request = _http_client.request(oauth_setting.token_url, HTTPClient.METHOD_POST, HEADERS, request_body)
|
||||
if await _handle_token_request(request):
|
||||
logInfo("token (%s) got refreshed" % token._identifier)
|
||||
else:
|
||||
unauthenticated.emit()
|
||||
else:
|
||||
unauthenticated.emit()
|
||||
_requesting_token = false
|
||||
|
||||
|
||||
## Gets information from the response and update values returns true when success otherwise false
|
||||
func _handle_token_request(request: OAuthHTTPClient.RequestData) -> bool:
|
||||
var response = await _http_client.wait_for_request(request)
|
||||
var response_string = response.response_data.get_string_from_utf8()
|
||||
var result = JSON.parse_string(response_string)
|
||||
if response.response_code == 200:
|
||||
_update_tokens_from_response(result)
|
||||
return true
|
||||
else:
|
||||
# Reset expiration cause token wasn't refreshed correctly.
|
||||
token.invalidate()
|
||||
logError("token (for %s) could not be fetched ResponseCode %s / Body %s" % [token._identifier, response.response_code, response_string])
|
||||
return false
|
||||
|
||||
|
||||
func _update_tokens_from_response(result: Dictionary):
|
||||
var scopes: Array[String] = []
|
||||
for scope in result.get("scope", []): scopes.append(scope)
|
||||
|
||||
update_tokens(result["access_token"], \
|
||||
result.get("refresh_token", ""), \
|
||||
result.get("expires_in", -1), \
|
||||
scopes)
|
||||
|
||||
|
||||
## Updates the token. Result is the response data of an token request.
|
||||
func update_tokens(access_token: String, refresh_token: String = "", expires_in: int = -1, scopes: Array[String] = []):
|
||||
token.update_values(access_token, refresh_token, expires_in, scopes)
|
||||
token_resolved.emit(token)
|
||||
logInfo("token (%s) resolved" % token._identifier)
|
||||
|
||||
|
||||
func get_token_expiration() -> String:
|
||||
return Time.get_datetime_string_from_unix_time(token._expire_date)
|
||||
|
||||
|
||||
## Checks if the token are valud
|
||||
func is_token_valid() -> bool:
|
||||
return token.is_token_valid()
|
||||
|
||||
|
||||
## Checks if the token is expired and can be refreshed
|
||||
func token_needs_refresh() -> bool:
|
||||
return !token.is_token_valid() && token.has_refresh_token()
|
||||
|
||||
|
||||
func get_access_token() -> String: return await token.get_access_token()
|
||||
|
||||
|
||||
func has_refresh_token() -> bool: return token.has_refresh_token()
|
||||
|
||||
|
||||
func get_scopes() -> PackedStringArray: return token.get_scopes()
|
||||
|
||||
|
||||
# === LOGGER ===
|
||||
static var logger: Dictionary = {}
|
||||
|
||||
|
||||
static func set_logger(error: Callable, info: Callable, debug: Callable) -> void:
|
||||
logger.debug = debug
|
||||
logger.info = info
|
||||
logger.error = error
|
||||
|
||||
|
||||
static func logDebug(text: String) -> void:
|
||||
if logger.has("debug"): logger.debug.call(text)
|
||||
|
||||
|
||||
static func logInfo(text: String) -> void:
|
||||
if logger.has("info"): logger.info.call(text)
|
||||
|
||||
|
||||
static func logError(text: String) -> void:
|
||||
if logger.has("error"): logger.error.call(text)
|
||||
Loading…
Add table
Add a link
Reference in a new issue