Skip to content

Commit d96dd56

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

File tree

1 file changed

+126
-11
lines changed

1 file changed

+126
-11
lines changed

addons/supabase/Auth/auth.gd

Lines changed: 126 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,7 @@ func _init(conf : Dictionary, head : PackedStringArray) -> void:
5658
_config = conf
5759
_header = head
5860
name = "Authentication"
61+
tcp_timer.timeout.connect(_tcp_stream_timer)
5962

6063
func __get_session_header() -> PackedStringArray :
6164
return PackedStringArray([_bearer[0] % ( _auth if not _auth.is_empty() else _config.supabaseKey )])
@@ -168,14 +171,86 @@ func sign_in_anonymous() -> AuthTask:
168171
return auth_task
169172

170173

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

180255
# If a user is logged in, this will log it out
181256
func sign_out() -> AuthTask:
@@ -339,6 +414,46 @@ func _on_task_completed(task : AuthTask) -> void:
339414

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

0 commit comments

Comments
 (0)