@@ -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,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
6063func __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
181256func 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
341416func _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\n Content-Type: text/html\r\n Content-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