@@ -26,9 +26,11 @@ signal reset_email_sent()
2626signal token_refreshed (refreshed_user : SupabaseUser )
2727signal user_invited ()
2828signal error (supabase_error : SupabaseAuthError )
29+ signal received_auth (auth_code : String )
2930
3031const _auth_endpoint : String = "/auth/v1"
3132const _provider_endpoint : String = _auth_endpoint + "/authorize"
33+ const _provider_token_endpoint : String = _auth_endpoint + "/token"
3234const _signin_endpoint : String = _auth_endpoint + "/token?grant_type=password"
3335const _signin_otp_endpoint : String = _auth_endpoint + "/otp"
3436const _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
6066func __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
181259func 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
341419func _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\n Content-Type: text/html\r\n Content-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