2828from api .db .services .connector_service import ConnectorService , SyncLogsService
2929from api .utils .api_utils import get_data_error_result , get_json_result , get_request_json , validate_request
3030from common .constants import RetCode , TaskStatus
31- from common .data_source .config import GOOGLE_DRIVE_WEB_OAUTH_REDIRECT_URI , GMAIL_WEB_OAUTH_REDIRECT_URI , DocumentSource
32- from common .data_source .google_util .constant import GOOGLE_WEB_OAUTH_POPUP_TEMPLATE , GOOGLE_SCOPES
31+ from common .data_source .config import GOOGLE_DRIVE_WEB_OAUTH_REDIRECT_URI , GMAIL_WEB_OAUTH_REDIRECT_URI , BOX_WEB_OAUTH_REDIRECT_URI , DocumentSource
32+ from common .data_source .google_util .constant import WEB_OAUTH_POPUP_TEMPLATE , GOOGLE_SCOPES
3333from common .misc_utils import get_uuid
3434from rag .utils .redis_conn import REDIS_CONN
3535from api .apps import login_required , current_user
36+ from box_sdk_gen import BoxOAuth , OAuthConfig , GetAuthorizeUrlOptions
3637
3738
3839@manager .route ("/set" , methods = ["POST" ]) # noqa: F821
@@ -117,8 +118,6 @@ def rm_connector(connector_id):
117118 return get_json_result (data = True )
118119
119120
120- GOOGLE_WEB_FLOW_STATE_PREFIX = "google_drive_web_flow_state"
121- GOOGLE_WEB_FLOW_RESULT_PREFIX = "google_drive_web_flow_result"
122121WEB_FLOW_TTL_SECS = 15 * 60
123122
124123
@@ -129,10 +128,7 @@ def _web_state_cache_key(flow_id: str, source_type: str | None = None) -> str:
129128 When source_type == "gmail", a different prefix is used so that
130129 Drive/Gmail flows don't clash in Redis.
131130 """
132- if source_type == "gmail" :
133- prefix = "gmail_web_flow_state"
134- else :
135- prefix = GOOGLE_WEB_FLOW_STATE_PREFIX
131+ prefix = f"{ source_type } _web_flow_state"
136132 return f"{ prefix } :{ flow_id } "
137133
138134
@@ -141,10 +137,7 @@ def _web_result_cache_key(flow_id: str, source_type: str | None = None) -> str:
141137
142138 Mirrors _web_state_cache_key logic for result storage.
143139 """
144- if source_type == "gmail" :
145- prefix = "gmail_web_flow_result"
146- else :
147- prefix = GOOGLE_WEB_FLOW_RESULT_PREFIX
140+ prefix = f"{ source_type } _web_flow_result"
148141 return f"{ prefix } :{ flow_id } "
149142
150143
@@ -180,7 +173,7 @@ async def _render_web_oauth_popup(flow_id: str, success: bool, message: str, sou
180173 }
181174 )
182175 # TODO(google-oauth): title/heading/message may need to reflect drive/gmail based on cached type
183- html = GOOGLE_WEB_OAUTH_POPUP_TEMPLATE .format (
176+ html = WEB_OAUTH_POPUP_TEMPLATE .format (
184177 title = f"Google { source .capitalize ()} Authorization" ,
185178 heading = "Authorization complete" if success else "Authorization failed" ,
186179 message = escaped_message ,
@@ -204,8 +197,8 @@ async def start_google_web_oauth():
204197 redirect_uri = GMAIL_WEB_OAUTH_REDIRECT_URI
205198 scopes = GOOGLE_SCOPES [DocumentSource .GMAIL ]
206199 else :
207- redirect_uri = GOOGLE_DRIVE_WEB_OAUTH_REDIRECT_URI if source == "google-drive" else GMAIL_WEB_OAUTH_REDIRECT_URI
208- scopes = GOOGLE_SCOPES [DocumentSource .GOOGLE_DRIVE if source == "google-drive" else DocumentSource . GMAIL ]
200+ redirect_uri = GOOGLE_DRIVE_WEB_OAUTH_REDIRECT_URI
201+ scopes = GOOGLE_SCOPES [DocumentSource .GOOGLE_DRIVE ]
209202
210203 if not redirect_uri :
211204 return get_json_result (
@@ -271,8 +264,6 @@ async def google_gmail_web_oauth_callback():
271264 state_id = request .args .get ("state" )
272265 error = request .args .get ("error" )
273266 source = "gmail"
274- if source != 'gmail' :
275- return await _render_web_oauth_popup ("" , False , "Invalid Google OAuth type." , source )
276267
277268 error_description = request .args .get ("error_description" ) or error
278269
@@ -313,9 +304,6 @@ async def google_gmail_web_oauth_callback():
313304 "credentials" : creds_json ,
314305 }
315306 REDIS_CONN .set_obj (_web_result_cache_key (state_id , source ), result_payload , WEB_FLOW_TTL_SECS )
316-
317- print ("\n \n " , _web_result_cache_key (state_id , source ), "\n \n " )
318-
319307 REDIS_CONN .delete (_web_state_cache_key (state_id , source ))
320308
321309 return await _render_web_oauth_popup (state_id , True , "Authorization completed successfully." , source )
@@ -326,8 +314,6 @@ async def google_drive_web_oauth_callback():
326314 state_id = request .args .get ("state" )
327315 error = request .args .get ("error" )
328316 source = "google-drive"
329- if source not in ("google-drive" , "gmail" ):
330- return await _render_web_oauth_popup ("" , False , "Invalid Google OAuth type." , source )
331317
332318 error_description = request .args .get ("error_description" ) or error
333319
@@ -391,3 +377,107 @@ async def poll_google_web_result():
391377
392378 REDIS_CONN .delete (_web_result_cache_key (flow_id , source ))
393379 return get_json_result (data = {"credentials" : result .get ("credentials" )})
380+
381+ @manager .route ("/box/oauth/web/start" , methods = ["POST" ]) # noqa: F821
382+ @login_required
383+ async def start_box_web_oauth ():
384+ req = await get_request_json ()
385+
386+ client_id = req .get ("client_id" )
387+ client_secret = req .get ("client_secret" )
388+ redirect_uri = req .get ("redirect_uri" , BOX_WEB_OAUTH_REDIRECT_URI )
389+
390+ if not client_id or not client_secret :
391+ return get_json_result (code = RetCode .ARGUMENT_ERROR , message = "Box client_id and client_secret are required." )
392+
393+ flow_id = str (uuid .uuid4 ())
394+
395+ box_auth = BoxOAuth (
396+ OAuthConfig (
397+ client_id = client_id ,
398+ client_secret = client_secret ,
399+ )
400+ )
401+
402+ auth_url = box_auth .get_authorize_url (
403+ options = GetAuthorizeUrlOptions (
404+ redirect_uri = redirect_uri ,
405+ state = flow_id ,
406+ )
407+ )
408+
409+ cache_payload = {
410+ "user_id" : current_user .id ,
411+ "auth_url" : auth_url ,
412+ "client_id" : client_id ,
413+ "client_secret" : client_secret ,
414+ "created_at" : int (time .time ()),
415+ }
416+ REDIS_CONN .set_obj (_web_state_cache_key (flow_id , "box" ), cache_payload , WEB_FLOW_TTL_SECS )
417+ return get_json_result (
418+ data = {
419+ "flow_id" : flow_id ,
420+ "authorization_url" : auth_url ,
421+ "expires_in" : WEB_FLOW_TTL_SECS ,}
422+ )
423+
424+ @manager .route ("/box/oauth/web/callback" , methods = ["GET" ]) # noqa: F821
425+ async def box_web_oauth_callback ():
426+ flow_id = request .args .get ("state" )
427+ if not flow_id :
428+ return await _render_web_oauth_popup ("" , False , "Missing OAuth parameters." , "box" )
429+
430+ code = request .args .get ("code" )
431+ if not code :
432+ return await _render_web_oauth_popup (flow_id , False , "Missing authorization code from Box." , "box" )
433+
434+ cache_payload = json .loads (REDIS_CONN .get (_web_state_cache_key (flow_id , "box" )))
435+ if not cache_payload :
436+ return get_json_result (code = RetCode .ARGUMENT_ERROR , message = "Box OAuth session expired or invalid." )
437+
438+ error = request .args .get ("error" )
439+ error_description = request .args .get ("error_description" ) or error
440+ if error :
441+ REDIS_CONN .delete (_web_state_cache_key (flow_id , "box" ))
442+ return await _render_web_oauth_popup (flow_id , False , error_description or "Authorization failed." , "box" )
443+
444+ auth = BoxOAuth (
445+ OAuthConfig (
446+ client_id = cache_payload .get ("client_id" ),
447+ client_secret = cache_payload .get ("client_secret" ),
448+ )
449+ )
450+
451+ auth .get_tokens_authorization_code_grant (code )
452+ token = auth .retrieve_token ()
453+ result_payload = {
454+ "user_id" : cache_payload .get ("user_id" ),
455+ "client_id" : cache_payload .get ("client_id" ),
456+ "client_secret" : cache_payload .get ("client_secret" ),
457+ "access_token" : token .access_token ,
458+ "refresh_token" : token .refresh_token ,
459+ }
460+
461+ REDIS_CONN .set_obj (_web_result_cache_key (flow_id , "box" ), result_payload , WEB_FLOW_TTL_SECS )
462+ REDIS_CONN .delete (_web_state_cache_key (flow_id , "box" ))
463+
464+ return await _render_web_oauth_popup (flow_id , True , "Authorization completed successfully." , "box" )
465+
466+ @manager .route ("/box/oauth/web/result" , methods = ["POST" ]) # noqa: F821
467+ @login_required
468+ @validate_request ("flow_id" )
469+ async def poll_box_web_result ():
470+ req = await get_request_json ()
471+ flow_id = req .get ("flow_id" )
472+
473+ cache_blob = REDIS_CONN .get (_web_result_cache_key (flow_id , "box" ))
474+ if not cache_blob :
475+ return get_json_result (code = RetCode .RUNNING , message = "Authorization is still pending." )
476+
477+ cache_raw = json .loads (cache_blob )
478+ if cache_raw .get ("user_id" ) != current_user .id :
479+ return get_json_result (code = RetCode .PERMISSION_ERROR , message = "You are not allowed to access this authorization result." )
480+
481+ REDIS_CONN .delete (_web_result_cache_key (flow_id , "box" ))
482+
483+ return get_json_result (data = {"credentials" : cache_raw })
0 commit comments