694 lines
25 KiB
GDScript
694 lines
25 KiB
GDScript
## @meta-authors TODO
|
|
## @meta-version 2.5
|
|
## The authentication API for Firebase.
|
|
## Documentation TODO.
|
|
@tool
|
|
class_name FirebaseAuth
|
|
extends HTTPRequest
|
|
|
|
const _API_VERSION : String = "v1"
|
|
const _INAPP_PLUGIN : String = "GodotSvc"
|
|
|
|
# Emitted for each Auth request issued.
|
|
# `result_code` -> Either `1` if auth succeeded or `error_code` if unsuccessful auth request
|
|
# `result_content` -> Either `auth_result` if auth succeeded or `error_message` if unsuccessful auth request
|
|
signal auth_request(result_code, result_content)
|
|
|
|
signal signup_succeeded(auth_result)
|
|
signal login_succeeded(auth_result)
|
|
signal login_failed(code, message)
|
|
signal signup_failed(code, message)
|
|
signal userdata_received(userdata)
|
|
signal token_exchanged(successful)
|
|
signal token_refresh_succeeded(auth_result)
|
|
signal logged_out()
|
|
|
|
const RESPONSE_SIGNUP : String = "identitytoolkit#SignupNewUserResponse"
|
|
const RESPONSE_SIGNIN : String = "identitytoolkit#VerifyPasswordResponse"
|
|
const RESPONSE_ASSERTION : String = "identitytoolkit#VerifyAssertionResponse"
|
|
const RESPONSE_USERDATA : String = "identitytoolkit#GetAccountInfoResponse"
|
|
const RESPONSE_CUSTOM_TOKEN : String = "identitytoolkit#VerifyCustomTokenResponse"
|
|
|
|
var _base_url : String = ""
|
|
var _refresh_request_base_url = ""
|
|
var _signup_request_url : String = "accounts:signUp?key=%s"
|
|
var _signin_with_oauth_request_url : String = "accounts:signInWithIdp?key=%s"
|
|
var _signin_request_url : String = "accounts:signInWithPassword?key=%s"
|
|
var _signin_custom_token_url : String = "accounts:signInWithCustomToken?key=%s"
|
|
var _userdata_request_url : String = "accounts:lookup?key=%s"
|
|
var _oobcode_request_url : String = "accounts:sendOobCode?key=%s"
|
|
var _delete_account_request_url : String = "accounts:delete?key=%s"
|
|
var _update_account_request_url : String = "accounts:update?key=%s"
|
|
|
|
var _refresh_request_url : String = "/v1/token?key=%s"
|
|
var _google_auth_request_url : String = "https://accounts.google.com/o/oauth2/v2/auth?"
|
|
|
|
var _config : Dictionary = {}
|
|
var auth : Dictionary = {}
|
|
var _needs_refresh : bool = false
|
|
var is_busy : bool = false
|
|
var has_child : bool = false
|
|
var is_oauth_login: bool = false
|
|
|
|
|
|
var tcp_server : TCPServer = TCPServer.new()
|
|
var tcp_timer : Timer = Timer.new()
|
|
var tcp_timeout : float = 0.5
|
|
|
|
var _headers : PackedStringArray = [
|
|
"Content-Type: application/json",
|
|
"Accept: application/json",
|
|
]
|
|
|
|
var requesting : int = -1
|
|
|
|
enum Requests {
|
|
NONE = -1,
|
|
EXCHANGE_TOKEN,
|
|
LOGIN_WITH_OAUTH
|
|
}
|
|
|
|
var auth_request_type : int = -1
|
|
|
|
enum Auth_Type {
|
|
NONE = -1,
|
|
LOGIN_EP,
|
|
LOGIN_ANON,
|
|
LOGIN_CT,
|
|
LOGIN_OAUTH,
|
|
SIGNUP_EP
|
|
}
|
|
|
|
var _login_request_body : Dictionary = {
|
|
"email":"",
|
|
"password":"",
|
|
"returnSecureToken": true,
|
|
}
|
|
|
|
var _oauth_login_request_body : Dictionary = {
|
|
"postBody":"",
|
|
"requestUri":"",
|
|
"returnIdpCredential":false,
|
|
"returnSecureToken":true
|
|
}
|
|
|
|
var _anonymous_login_request_body : Dictionary = {
|
|
"returnSecureToken":true
|
|
}
|
|
|
|
var _refresh_request_body : Dictionary = {
|
|
"grant_type":"refresh_token",
|
|
"refresh_token":"",
|
|
}
|
|
|
|
var _custom_token_body : Dictionary = {
|
|
"token":"",
|
|
"returnSecureToken":true
|
|
}
|
|
|
|
var _password_reset_body : Dictionary = {
|
|
"requestType":"password_reset",
|
|
"email":"",
|
|
}
|
|
|
|
|
|
var _change_email_body : Dictionary = {
|
|
"idToken":"",
|
|
"email":"",
|
|
"returnSecureToken": true,
|
|
}
|
|
|
|
|
|
var _change_password_body : Dictionary = {
|
|
"idToken":"",
|
|
"password":"",
|
|
"returnSecureToken": true,
|
|
}
|
|
|
|
|
|
var _account_verification_body : Dictionary = {
|
|
"requestType":"verify_email",
|
|
"idToken":"",
|
|
}
|
|
|
|
|
|
var _update_profile_body : Dictionary = {
|
|
"idToken":"",
|
|
"displayName":"",
|
|
"photoUrl":"",
|
|
"deleteAttribute":"",
|
|
"returnSecureToken":true
|
|
}
|
|
|
|
var link_account_body : Dictionary = {
|
|
"idToken":"",
|
|
"email":"",
|
|
"password":"",
|
|
"returnSecureToken":true
|
|
}
|
|
|
|
var _local_port : int = 8060
|
|
var _local_uri : String = "http://localhost:%s/"%_local_port
|
|
var _local_provider : AuthProvider = AuthProvider.new()
|
|
|
|
func _ready() -> void:
|
|
tcp_timer.wait_time = tcp_timeout
|
|
tcp_timer.timeout.connect(_tcp_stream_timer)
|
|
|
|
Utilities.fix_http_request(self)
|
|
if Utilities.is_web():
|
|
_local_uri += "tmp_js_export.html"
|
|
|
|
|
|
# Sets the configuration needed for the plugin to talk to Firebase
|
|
# These settings come from the Firebase.gd script automatically
|
|
func _set_config(config_json : Dictionary) -> void:
|
|
_config = config_json
|
|
_signup_request_url %= _config.apiKey
|
|
_signin_request_url %= _config.apiKey
|
|
_signin_custom_token_url %= _config.apiKey
|
|
_signin_with_oauth_request_url %= _config.apiKey
|
|
_userdata_request_url %= _config.apiKey
|
|
_refresh_request_url %= _config.apiKey
|
|
_oobcode_request_url %= _config.apiKey
|
|
_delete_account_request_url %= _config.apiKey
|
|
_update_account_request_url %= _config.apiKey
|
|
|
|
request_completed.connect(_on_FirebaseAuth_request_completed)
|
|
_check_emulating()
|
|
|
|
|
|
func _check_emulating() -> void :
|
|
## Check emulating
|
|
if not Firebase.emulating:
|
|
_base_url = "https://identitytoolkit.googleapis.com/{version}/".format({ version = _API_VERSION })
|
|
_refresh_request_base_url = "https://securetoken.googleapis.com"
|
|
else:
|
|
var port : String = _config.emulators.ports.authentication
|
|
if port == "":
|
|
Firebase._printerr("You are in 'emulated' mode, but the port for Authentication has not been configured.")
|
|
else:
|
|
_base_url = "http://localhost:{port}/identitytoolkit.googleapis.com/{version}/".format({ version = _API_VERSION ,port = port })
|
|
_refresh_request_base_url = "http://localhost:{port}/securetoken.googleapis.com".format({port = port})
|
|
|
|
|
|
# Function is used to check if the auth script is ready to process a request. Returns true if it is not currently processing
|
|
# If false it will print an error
|
|
func _is_ready() -> bool:
|
|
if is_busy:
|
|
Firebase._printerr("Firebase Auth is currently busy and cannot process this request")
|
|
return false
|
|
else:
|
|
return true
|
|
|
|
# Function cleans the URI and replaces spaces with %20
|
|
# As of right now we only replace spaces
|
|
# We may need to decide to use the uri_encode() String function
|
|
func _clean_url(_url):
|
|
_url = _url.replace(' ','%20')
|
|
return _url
|
|
|
|
# Synchronous call to check if any user is already logged in.
|
|
func is_logged_in() -> bool:
|
|
return auth != null and auth.has("idtoken")
|
|
|
|
|
|
# Called with Firebase.Auth.signup_with_email_and_password(email, password)
|
|
# You must pass in the email and password to this function for it to work correctly
|
|
func signup_with_email_and_password(email : String, password : String) -> void:
|
|
if _is_ready():
|
|
is_busy = true
|
|
_login_request_body.email = email
|
|
_login_request_body.password = password
|
|
auth_request_type = Auth_Type.SIGNUP_EP
|
|
var err = request(_base_url + _signup_request_url, _headers, HTTPClient.METHOD_POST, JSON.stringify(_login_request_body))
|
|
_login_request_body.email = ""
|
|
_login_request_body.password = ""
|
|
if err != OK:
|
|
is_busy = false
|
|
Firebase._printerr("Error signing up with password and email: %s" % err)
|
|
|
|
|
|
# Called with Firebase.Auth.anonymous_login()
|
|
# A successful request is indicated by a 200 OK HTTP status code.
|
|
# The response contains the Firebase ID token and refresh token associated with the anonymous user.
|
|
# The 'mail' field will be empty since no email is linked to an anonymous user
|
|
func login_anonymous() -> void:
|
|
if _is_ready():
|
|
is_busy = true
|
|
auth_request_type = Auth_Type.LOGIN_ANON
|
|
var err = request(_base_url + _signup_request_url, _headers, HTTPClient.METHOD_POST, JSON.stringify(_anonymous_login_request_body))
|
|
if err != OK:
|
|
is_busy = false
|
|
Firebase._printerr("Error logging in as anonymous: %s" % err)
|
|
|
|
# Called with Firebase.Auth.login_with_email_and_password(email, password)
|
|
# You must pass in the email and password to this function for it to work correctly
|
|
# If the login fails it will return an error code through the function _on_FirebaseAuth_request_completed
|
|
func login_with_email_and_password(email : String, password : String) -> void:
|
|
if _is_ready():
|
|
is_busy = true
|
|
_login_request_body.email = email
|
|
_login_request_body.password = password
|
|
auth_request_type = Auth_Type.LOGIN_EP
|
|
var err = request(_base_url + _signin_request_url, _headers, HTTPClient.METHOD_POST, JSON.stringify(_login_request_body))
|
|
_login_request_body.email = ""
|
|
_login_request_body.password = ""
|
|
if err != OK:
|
|
is_busy = false
|
|
Firebase._printerr("Error logging in with password and email: %s" % err)
|
|
|
|
# Login with a custom valid token
|
|
# The token needs to be generated using an external service/function
|
|
func login_with_custom_token(token : String) -> void:
|
|
if _is_ready():
|
|
is_busy = true
|
|
_custom_token_body.token = token
|
|
auth_request_type = Auth_Type.LOGIN_CT
|
|
var err = request(_base_url + _signin_custom_token_url, _headers, HTTPClient.METHOD_POST, JSON.stringify(_custom_token_body))
|
|
if err != OK:
|
|
is_busy = false
|
|
Firebase._printerr("Error logging in with custom token: %s" % err)
|
|
|
|
# Open a web page in browser redirecting to Google oAuth2 page for the current project
|
|
# Once given user's authorization, a token will be generated.
|
|
# NOTE** the generated token will be automatically captured and a login request will be made if the token is correct
|
|
func get_auth_localhost(provider: AuthProvider = get_GoogleProvider(), port : int = _local_port):
|
|
get_auth_with_redirect(provider)
|
|
await get_tree().create_timer(0.5).timeout
|
|
if has_child == false:
|
|
add_child(tcp_timer)
|
|
has_child = true
|
|
tcp_timer.start()
|
|
tcp_server.listen(port, "*")
|
|
|
|
|
|
func get_auth_with_redirect(provider: AuthProvider) -> void:
|
|
var url_endpoint: String = provider.redirect_uri
|
|
for key in provider.params.keys():
|
|
url_endpoint+=key+"="+provider.params[key]+"&"
|
|
url_endpoint += provider.params.redirect_type+"="+_local_uri
|
|
url_endpoint = _clean_url(url_endpoint)
|
|
if Utilities.is_web() and OS.has_feature("JavaScript"):
|
|
JavaScriptBridge.eval('window.location.replace("' + url_endpoint + '")')
|
|
elif Engine.has_singleton(_INAPP_PLUGIN) and OS.get_name() == "iOS":
|
|
#in app for ios if the iOS plugin exists
|
|
set_local_provider(provider)
|
|
Engine.get_singleton(_INAPP_PLUGIN).popup(url_endpoint)
|
|
else:
|
|
set_local_provider(provider)
|
|
OS.shell_open(url_endpoint)
|
|
|
|
|
|
# Login with Google oAuth2.
|
|
# A token is automatically obtained using an authorization code using @get_google_auth()
|
|
# @provider_id and @request_uri can be changed
|
|
func login_with_oauth(_token: String, provider: AuthProvider) -> void:
|
|
if _token:
|
|
is_oauth_login = true
|
|
var token : String = _token.uri_decode()
|
|
var is_successful: bool = true
|
|
if provider.should_exchange:
|
|
exchange_token(token, _local_uri, provider.access_token_uri, provider.get_client_id(), provider.get_client_secret())
|
|
is_successful = await self.token_exchanged
|
|
token = auth.accesstoken
|
|
if is_successful and _is_ready():
|
|
is_busy = true
|
|
_oauth_login_request_body.postBody = "access_token="+token+"&providerId="+provider.provider_id
|
|
_oauth_login_request_body.requestUri = _local_uri
|
|
requesting = Requests.LOGIN_WITH_OAUTH
|
|
auth_request_type = Auth_Type.LOGIN_OAUTH
|
|
var err = request(_base_url + _signin_with_oauth_request_url, _headers, HTTPClient.METHOD_POST, JSON.stringify(_oauth_login_request_body))
|
|
_oauth_login_request_body.postBody = ""
|
|
_oauth_login_request_body.requestUri = ""
|
|
if err != OK:
|
|
is_busy = false
|
|
Firebase._printerr("Error logging in with oauth: %s" % err)
|
|
|
|
# Exchange the authorization oAuth2 code obtained from browser with a proper access id_token
|
|
func exchange_token(code : String, redirect_uri : String, request_url: String, _client_id: String, _client_secret: String) -> void:
|
|
if _is_ready():
|
|
is_busy = true
|
|
var exchange_token_body : Dictionary = {
|
|
code = code,
|
|
redirect_uri = redirect_uri,
|
|
client_id = _client_id,
|
|
client_secret = _client_secret,
|
|
grant_type = "authorization_code",
|
|
}
|
|
requesting = Requests.EXCHANGE_TOKEN
|
|
var err = request(request_url, _headers, HTTPClient.METHOD_POST, JSON.stringify(exchange_token_body))
|
|
if err != OK:
|
|
is_busy = false
|
|
Firebase._printerr("Error exchanging tokens: %s" % err)
|
|
|
|
# Open a web page in browser redirecting to Google oAuth2 page for the current project
|
|
# Once given user's authorization, a token will be generated.
|
|
# NOTE** with this method, the authorization process will be copy-pasted
|
|
func get_google_auth_manual(provider: AuthProvider = _local_provider) -> void:
|
|
provider.params.redirect_uri = "urn:ietf:wg:oauth:2.0:oob"
|
|
get_auth_with_redirect(provider)
|
|
|
|
# A timer used to listen through TCP checked the redirect uri of the request
|
|
func _tcp_stream_timer() -> void:
|
|
var peer : StreamPeer = tcp_server.take_connection()
|
|
if peer != null:
|
|
var raw_result : String = peer.get_utf8_string(441)
|
|
if raw_result != "" and raw_result.begins_with("GET"):
|
|
tcp_timer.stop()
|
|
remove_child(tcp_timer)
|
|
has_child = false
|
|
var token : String = ""
|
|
for value in raw_result.split(" ")[1].lstrip("/?").split("&"):
|
|
var splitted: PackedStringArray = value.split("=")
|
|
if _local_provider.params.response_type in splitted[0]:
|
|
token = splitted[1]
|
|
break
|
|
|
|
if token == "":
|
|
login_failed.emit()
|
|
peer.disconnect_from_host()
|
|
tcp_server.stop()
|
|
return
|
|
|
|
var data : PackedByteArray = '<p style="text-align:center">🔥 You can close this window now. 🔥</p>'.to_ascii_buffer()
|
|
peer.put_data(("HTTP/1.1 200 OK\n").to_ascii_buffer())
|
|
peer.put_data(("Server: Godot Firebase SDK\n").to_ascii_buffer())
|
|
peer.put_data(("Content-Length: %d\n" % data.size()).to_ascii_buffer())
|
|
peer.put_data("Connection: close\n".to_ascii_buffer())
|
|
peer.put_data(("Content-Type: text/html; charset=UTF-8\n\n").to_ascii_buffer())
|
|
peer.put_data(data)
|
|
login_with_oauth(token, _local_provider)
|
|
await self.login_succeeded
|
|
peer.disconnect_from_host()
|
|
tcp_server.stop()
|
|
|
|
|
|
# Function used to logout of the system, this will also remove_at the local encrypted auth file if there is one
|
|
func logout() -> void:
|
|
auth = {}
|
|
remove_auth()
|
|
logged_out.emit()
|
|
|
|
# Checks to see if we need a hard login
|
|
func needs_login() -> bool:
|
|
var encrypted_file = FileAccess.open_encrypted_with_pass("user://user.auth", FileAccess.READ, _config.apiKey)
|
|
var err = encrypted_file == null
|
|
return err
|
|
|
|
# Function is called when requesting a manual token refresh
|
|
func manual_token_refresh(auth_data):
|
|
auth = auth_data
|
|
var refresh_token = null
|
|
auth = get_clean_keys(auth)
|
|
if auth.has("refreshtoken"):
|
|
refresh_token = auth.refreshtoken
|
|
elif auth.has("refresh_token"):
|
|
refresh_token = auth.refresh_token
|
|
_needs_refresh = true
|
|
_refresh_request_body.refresh_token = refresh_token
|
|
var err = request(_refresh_request_base_url + _refresh_request_url, _headers, HTTPClient.METHOD_POST, JSON.stringify(_refresh_request_body))
|
|
if err != OK:
|
|
is_busy = false
|
|
Firebase._printerr("Error manually refreshing token: %s" % err)
|
|
|
|
|
|
# This function is called whenever there is an authentication request to Firebase
|
|
# On an error, this function with emit the signal 'login_failed' and print the error to the console
|
|
func _on_FirebaseAuth_request_completed(result : int, response_code : int, headers : PackedStringArray, body : PackedByteArray) -> void:
|
|
var json = Utilities.get_json_data(body.get_string_from_utf8())
|
|
is_busy = false
|
|
var res
|
|
if response_code == 0:
|
|
# Mocked error results to trigger the correct signal.
|
|
# Can occur if there is no internet connection, or the service is down,
|
|
# in which case there is no json_body (and thus parsing would fail).
|
|
res = {"error": {
|
|
"code": "Connection error",
|
|
"message": "Error connecting to auth service"}}
|
|
else:
|
|
if json == null:
|
|
Firebase._printerr("Error while parsing auth body json")
|
|
auth_request.emit(ERR_PARSE_ERROR, "Error while parsing auth body json")
|
|
return
|
|
|
|
res = json
|
|
if response_code == HTTPClient.RESPONSE_OK:
|
|
if not res.has("kind"):
|
|
auth = get_clean_keys(res)
|
|
match requesting:
|
|
Requests.EXCHANGE_TOKEN:
|
|
token_exchanged.emit(true)
|
|
begin_refresh_countdown()
|
|
# Refresh token countdown
|
|
auth_request.emit(1, auth)
|
|
|
|
if _needs_refresh:
|
|
_needs_refresh = false
|
|
if not is_oauth_login: login_succeeded.emit(auth)
|
|
else:
|
|
match res.kind:
|
|
RESPONSE_SIGNUP:
|
|
auth = get_clean_keys(res)
|
|
signup_succeeded.emit(auth)
|
|
begin_refresh_countdown()
|
|
RESPONSE_SIGNIN, RESPONSE_ASSERTION, RESPONSE_CUSTOM_TOKEN:
|
|
auth = get_clean_keys(res)
|
|
login_succeeded.emit(auth)
|
|
begin_refresh_countdown()
|
|
RESPONSE_USERDATA:
|
|
var userdata = FirebaseUserData.new(res.users[0])
|
|
userdata_received.emit(userdata)
|
|
auth_request.emit(1, auth)
|
|
else:
|
|
# error message would be INVALID_EMAIL, EMAIL_NOT_FOUND, INVALID_PASSWORD, USER_DISABLED or WEAK_PASSWORD
|
|
if requesting == Requests.EXCHANGE_TOKEN:
|
|
token_exchanged.emit(false)
|
|
login_failed.emit(res.error, res.error_description)
|
|
auth_request.emit(res.error, res.error_description)
|
|
else:
|
|
var sig = signup_failed if auth_request_type == Auth_Type.SIGNUP_EP else login_failed
|
|
sig.emit(res.error.code, res.error.message)
|
|
auth_request.emit(res.error.code, res.error.message)
|
|
requesting = Requests.NONE
|
|
auth_request_type = Auth_Type.NONE
|
|
is_oauth_login = false
|
|
|
|
|
|
|
|
# Function used to save the auth data provided by Firebase into an encrypted file
|
|
# Note this does not work in HTML5 or UWP
|
|
func save_auth(auth : Dictionary) -> bool:
|
|
var encrypted_file = FileAccess.open_encrypted_with_pass("user://user.auth", FileAccess.WRITE, _config.apiKey)
|
|
var err = encrypted_file == null
|
|
if err:
|
|
Firebase._printerr("Error Opening File. Error Code: " + str(FileAccess.get_open_error()))
|
|
else:
|
|
encrypted_file.store_line(JSON.stringify(auth))
|
|
return not err
|
|
|
|
|
|
# Function used to load the auth data file that has been stored locally
|
|
# Note this does not work in HTML5 or UWP
|
|
func load_auth() -> bool:
|
|
var encrypted_file = FileAccess.open_encrypted_with_pass("user://user.auth", FileAccess.READ, _config.apiKey)
|
|
var err = encrypted_file == null
|
|
if err:
|
|
Firebase._printerr("Error Opening Firebase Auth File. Error Code: " + str(FileAccess.get_open_error()))
|
|
auth_request.emit(err, "Error Opening Firebase Auth File.")
|
|
else:
|
|
var json = JSON.new()
|
|
var json_parse_result = json.parse(encrypted_file.get_line())
|
|
if json_parse_result == OK:
|
|
var encrypted_file_data = json.data
|
|
manual_token_refresh(encrypted_file_data)
|
|
return not err
|
|
|
|
# Function used to remove_at the local encrypted auth file
|
|
func remove_auth() -> void:
|
|
if (FileAccess.file_exists("user://user.auth")):
|
|
DirAccess.remove_absolute("user://user.auth")
|
|
else:
|
|
Firebase._printerr("No encrypted auth file exists")
|
|
|
|
|
|
# Function to check if there is an encrypted auth data file
|
|
# If there is, the game will load it and refresh the token
|
|
func check_auth_file() -> bool:
|
|
if (FileAccess.file_exists("user://user.auth")):
|
|
# Will ensure "auth_request" emitted
|
|
return load_auth()
|
|
else:
|
|
Firebase._printerr("Encrypted Firebase Auth file does not exist")
|
|
auth_request.emit(ERR_DOES_NOT_EXIST, "Encrypted Firebase Auth file does not exist")
|
|
return false
|
|
|
|
|
|
# Function used to change the email account for the currently logged in user
|
|
func change_user_email(email : String) -> void:
|
|
if _is_ready():
|
|
is_busy = true
|
|
_change_email_body.email = email
|
|
_change_email_body.idToken = auth.idtoken
|
|
var err = request(_base_url + _update_account_request_url, _headers, HTTPClient.METHOD_POST, JSON.stringify(_change_email_body))
|
|
if err != OK:
|
|
is_busy = false
|
|
Firebase._printerr("Error changing user email: %s" % err)
|
|
|
|
|
|
# Function used to change the password for the currently logged in user
|
|
func change_user_password(password : String) -> void:
|
|
if _is_ready():
|
|
is_busy = true
|
|
_change_password_body.password = password
|
|
_change_password_body.idToken = auth.idtoken
|
|
var err = request(_base_url + _update_account_request_url, _headers, HTTPClient.METHOD_POST, JSON.stringify(_change_password_body))
|
|
if err != OK:
|
|
is_busy = false
|
|
Firebase._printerr("Error changing user password: %s" % err)
|
|
|
|
|
|
# User Profile handlers
|
|
func update_account(idToken : String, displayName : String, photoUrl : String, deleteAttribute : PackedStringArray, returnSecureToken : bool) -> void:
|
|
if _is_ready():
|
|
is_busy = true
|
|
_update_profile_body.idToken = idToken
|
|
_update_profile_body.displayName = displayName
|
|
_update_profile_body.photoUrl = photoUrl
|
|
_update_profile_body.deleteAttribute = deleteAttribute
|
|
_update_profile_body.returnSecureToken = returnSecureToken
|
|
var err = request(_base_url + _update_account_request_url, _headers, HTTPClient.METHOD_POST, JSON.stringify(_update_profile_body))
|
|
if err != OK:
|
|
is_busy = false
|
|
Firebase._printerr("Error updating account: %s" % err)
|
|
|
|
# Link account with Email and Password
|
|
func link_account(email : String, password : String) -> void:
|
|
if _is_ready():
|
|
is_busy = true
|
|
link_account_body.idToken = auth.idtoken
|
|
link_account_body.email = email
|
|
link_account_body.password = password
|
|
var err = request(_base_url + _update_account_request_url, _headers, HTTPClient.METHOD_POST, JSON.stringify(link_account_body))
|
|
if err != OK:
|
|
is_busy = false
|
|
Firebase._printerr("Error updating account: %s" % err)
|
|
|
|
|
|
# Function to send a account verification email
|
|
func send_account_verification_email() -> void:
|
|
if _is_ready():
|
|
is_busy = true
|
|
_account_verification_body.idToken = auth.idtoken
|
|
var err = request(_base_url + _oobcode_request_url, _headers, HTTPClient.METHOD_POST, JSON.stringify(_account_verification_body))
|
|
if err != OK:
|
|
is_busy = false
|
|
Firebase._printerr("Error sending account verification email: %s" % err)
|
|
|
|
|
|
# Function used to reset the password for a user who has forgotten in.
|
|
# This will send the users account an email with a password reset link
|
|
func send_password_reset_email(email : String) -> void:
|
|
if _is_ready():
|
|
is_busy = true
|
|
_password_reset_body.email = email
|
|
var err = request(_base_url + _oobcode_request_url, _headers, HTTPClient.METHOD_POST, JSON.stringify(_password_reset_body))
|
|
if err != OK:
|
|
is_busy = false
|
|
Firebase._printerr("Error sending password reset email: %s" % err)
|
|
|
|
|
|
# Function called to get all
|
|
func get_user_data() -> void:
|
|
if _is_ready():
|
|
is_busy = true
|
|
if not is_logged_in():
|
|
print_debug("Not logged in")
|
|
is_busy = false
|
|
return
|
|
|
|
var err = request(_base_url + _userdata_request_url, _headers, HTTPClient.METHOD_POST, JSON.stringify({"idToken":auth.idtoken}))
|
|
if err != OK:
|
|
is_busy = false
|
|
Firebase._printerr("Error getting user data: %s" % err)
|
|
|
|
|
|
# Function used to delete the account of the currently authenticated user
|
|
func delete_user_account() -> void:
|
|
if _is_ready():
|
|
is_busy = true
|
|
var err = request(_base_url + _delete_account_request_url, _headers, HTTPClient.METHOD_POST, JSON.stringify({"idToken":auth.idtoken}))
|
|
if err != OK:
|
|
is_busy = false
|
|
Firebase._printerr("Error deleting user: %s" % err)
|
|
else:
|
|
remove_auth()
|
|
|
|
|
|
# Function is called when a new token is issued to a user. The function will yield until the token has expired, and then request a new one.
|
|
func begin_refresh_countdown() -> void:
|
|
var refresh_token = null
|
|
var expires_in = 1000
|
|
auth = get_clean_keys(auth)
|
|
if auth.has("refreshtoken"):
|
|
refresh_token = auth.refreshtoken
|
|
expires_in = auth.expiresin
|
|
elif auth.has("refresh_token"):
|
|
refresh_token = auth.refresh_token
|
|
expires_in = auth.expires_in
|
|
if auth.has("userid"):
|
|
auth["localid"] = auth.userid
|
|
_needs_refresh = true
|
|
token_refresh_succeeded.emit(auth)
|
|
await get_tree().create_timer(float(expires_in)).timeout
|
|
_refresh_request_body.refresh_token = refresh_token
|
|
var err = request(_refresh_request_base_url + _refresh_request_url, _headers, HTTPClient.METHOD_POST, JSON.stringify(_refresh_request_body))
|
|
if err != OK:
|
|
is_busy = false
|
|
Firebase._printerr("Error refreshing via countdown: %s" % err)
|
|
|
|
|
|
func get_token_from_url(provider: AuthProvider):
|
|
var token_type: String = provider.params.response_type if provider.params.response_type == "code" else "access_token"
|
|
if OS.has_feature('web'):
|
|
var token = JavaScriptBridge.eval("""
|
|
var url_string = window.location.href.replaceAll('?#', '?');
|
|
var url = new URL(url_string);
|
|
url.searchParams.get('"""+token_type+"""');
|
|
""")
|
|
JavaScriptBridge.eval("""window.history.pushState({}, null, location.href.split('?')[0]);""")
|
|
return token
|
|
return null
|
|
|
|
|
|
func set_redirect_uri(redirect_uri : String) -> void:
|
|
self._local_uri = redirect_uri
|
|
|
|
func set_local_provider(provider : AuthProvider) -> void:
|
|
self._local_provider = provider
|
|
|
|
# This function is used to make all keys lowercase
|
|
# This is only used to cut down checked processing errors from Firebase
|
|
# This is due to Google have inconsistencies in the API that we are trying to fix
|
|
func get_clean_keys(auth_result : Dictionary) -> Dictionary:
|
|
var cleaned = {}
|
|
for key in auth_result.keys():
|
|
cleaned[key.replace("_", "").to_lower()] = auth_result[key]
|
|
return cleaned
|
|
|
|
# --------------------
|
|
# PROVIDERS
|
|
# --------------------
|
|
|
|
func get_GoogleProvider() -> GoogleProvider:
|
|
return GoogleProvider.new(_config.clientId, _config.clientSecret)
|
|
|
|
func get_FacebookProvider() -> FacebookProvider:
|
|
return FacebookProvider.new(_config.auth_providers.facebook_id, _config.auth_providers.facebook_secret)
|
|
|
|
func get_GitHubProvider() -> GitHubProvider:
|
|
return GitHubProvider.new(_config.auth_providers.github_id, _config.auth_providers.github_secret)
|
|
|
|
func get_TwitterProvider() -> TwitterProvider:
|
|
return TwitterProvider.new(_config.auth_providers.twitter_id, _config.auth_providers.twitter_secret)
|