Skip to content

Commit 7d3657e

Browse files
committed
Implement Google OAuth - WIP
1 parent 6d5e841 commit 7d3657e

File tree

1 file changed

+129
-11
lines changed

1 file changed

+129
-11
lines changed

addons/supabase/Auth/auth.gd

Lines changed: 129 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,11 @@ signal reset_email_sent()
2626
signal token_refreshed(refreshed_user: SupabaseUser)
2727
signal user_invited()
2828
signal error(supabase_error: SupabaseAuthError)
29+
signal received_auth(auth_code: String)
2930

3031
const _auth_endpoint : String = "/auth/v1"
3132
const _provider_endpoint : String = _auth_endpoint+"/authorize"
33+
const _provider_token_endpoint : String = _auth_endpoint + "/token"
3234
const _signin_endpoint : String = _auth_endpoint+"/token?grant_type=password"
3335
const _signin_otp_endpoint : String = _auth_endpoint+"/otp"
3436
const _verify_otp_endpoint : String = _auth_endpoint+"/verify"
@@ -56,6 +58,10 @@ func _init(conf : Dictionary, head : PackedStringArray) -> void:
5658
_config = conf
5759
_header = head
5860
name = "Authentication"
61+
tcp_timer.timeout.connect(_tcp_stream_timer)
62+
63+
func __get_session_header() -> PackedStringArray :
64+
return PackedStringArray([_bearer[0] % ( _auth if not _auth.is_empty() else _config.supabaseKey )])
5965

6066
func __get_session_header() -> PackedStringArray :
6167
return PackedStringArray([_bearer[0] % ( _auth if not _auth.is_empty() else _config.supabaseKey )])
@@ -168,14 +174,86 @@ func sign_in_anonymous() -> AuthTask:
168174
return auth_task
169175

170176

171-
# [ CURRENTLY UNSUPPORTED ]
172-
# Sign in with a Provider
173-
# @provider = Providers.PROVIDER
174-
func sign_in_with_provider(provider : String, grab_from_browser : bool = true, port : int = 3000) -> void:
175-
OS.shell_open(_config.supabaseUrl + _provider_endpoint + "?provider="+provider)
176-
# ! to be implemented
177-
pass
178-
177+
# This function initiates a provider login (e.g. Google) by opening the browser
178+
# and waiting on a local TCPServer for the OAuth redirect.
179+
func sign_in_with_provider(provider: String, port : int = 3000) -> void:
180+
# Build the callback URL – this must match what you set in your Supabase project.
181+
var callback_url = "http://127.0.0.1:%s" % port
182+
183+
# Start the TCPServer to listen for the redirect
184+
var err = tcp_server.listen(port, "127.0.0.1")
185+
if err != OK:
186+
error.emit("TCPServer error: " + str(err))
187+
return
188+
189+
add_child(tcp_timer)
190+
tcp_timer.start(1)
191+
192+
# Construct the URL parameters
193+
var params = {
194+
"client_id": "YOUR CLIENT ID",
195+
"redirect_uri": callback_url,
196+
"response_type": "code",
197+
"provider": provider
198+
}
199+
200+
# Build query string (using percent encoding for safety)
201+
var query_arr : Array = []
202+
for key in params.keys():
203+
query_arr.append("%s=%s" % [key, params[key].uri_encode()])
204+
var query_string = "&".join(query_arr)
205+
206+
# Build the full authorization URL.
207+
var auth_url = _config.supabaseUrl + _provider_endpoint + "?" + query_string
208+
print(auth_url)
209+
# Open the URL in the user's browser.
210+
OS.shell_open(auth_url)
211+
212+
# Wait for the incoming HTTP redirect to provide the auth code.
213+
var auth_code = await received_auth
214+
215+
# Exchange the authorization code for tokens.
216+
var token_fetch_task = await sign_in_with_oauth(auth_code, callback_url)
217+
await token_fetch_task.completed
218+
219+
220+
var token_data = token_fetch_task.user.dict.get("code", "")
221+
222+
if not token_data:
223+
error.emit("OAuth didn't send a code back")
224+
return
225+
226+
# Update our internal authentication state.
227+
_auth = token_data.access_token
228+
# Assume token_data also contains refresh_token and expires_in.
229+
client = SupabaseUser.new(token_data) # SupabaseUser should be defined to accept these details.
230+
_expires_in = token_data.expires_in
231+
232+
# Emit the signed_in signal and start token refresh if needed.
233+
signed_in.emit(client)
234+
refresh_token()
235+
236+
func sign_in_with_oauth(auth_code: String, callback_url: String) -> AuthTask:
237+
var payload : Dictionary = {
238+
"provider": "google",
239+
"code": auth_code,
240+
"redirect_uri": callback_url,
241+
"grant_type": "pkce",
242+
"client_id": "your google client id",
243+
"client_secret": "your google client secret"
244+
}
245+
var content = "&".join(payload.keys().map(func(name): return "%s=%s" % [name, payload[name].uri_encode()]))
246+
print(content)
247+
var auth_task : AuthTask = AuthTask.new()._setup(
248+
AuthTask.Task.SIGNIN,
249+
_config.supabaseUrl + _provider_token_endpoint,
250+
PackedStringArray([
251+
"apikey: %s"%[_config.supabaseKey],
252+
"Content-Type: application/json",
253+
]),
254+
JSON.stringify(payload))
255+
_process_task(auth_task)
256+
return auth_task
179257

180258
# If a user is logged in, this will log it out
181259
func sign_out() -> AuthTask:
@@ -339,6 +417,46 @@ func _on_task_completed(task : AuthTask) -> void:
339417

340418
# A timer used to listen through TCP on the redirect uri of the request
341419
func _tcp_stream_timer() -> void:
342-
var peer : StreamPeer = tcp_server.take_connection()
343-
# ! to be implemented
344-
pass
420+
var connection: StreamPeer = tcp_server.take_connection()
421+
if not connection:
422+
print("No connection")
423+
return
424+
# Read available data (assumes the entire HTTP request is available).
425+
var request_data = connection.get_string(connection.get_available_bytes())
426+
if not request_data:
427+
print("No data")
428+
return
429+
# Example request: "GET /callback?code=AUTHCODE&state=... HTTP/1.1"
430+
var parts = request_data.split(" ")
431+
if parts.size() <= 1:
432+
print("no parts")
433+
return
434+
var url_part = parts[1]
435+
var query_index = url_part.find("?")
436+
if query_index == -1:
437+
print("no index")
438+
print(request_data)
439+
return
440+
441+
var query_string = url_part.substr(query_index + 1, url_part.length())
442+
var query_set = {}
443+
444+
for param in query_string.split("&"):
445+
print("param ", param)
446+
var key_value = param.split("=")
447+
448+
# Other keys: state, scope="email+profile+
449+
if key_value.size() != 2:
450+
continue
451+
query_set[key_value[0]] = key_value[1]
452+
453+
if "code" in query_set:
454+
var auth_code = query_set["code"]
455+
456+
# Send a simple HTTP response back to the browser.
457+
var response_html = "<html><body>Login successful. You can close this window.</body></html>"
458+
var response = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: %s\r\n\r\n%s" % [str(response_html.length()), response_html]
459+
connection.put_data(response.to_utf8_buffer())
460+
tcp_server.stop() # Stop listening after obtaining the code.
461+
received_auth.emit(auth_code)
462+
return

0 commit comments

Comments
 (0)