clawdex-mobile 5.1.3-internal.4 → 5.1.3-internal.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawdex-mobile",
3
- "version": "5.1.3-internal.4",
3
+ "version": "5.1.3-internal.6",
4
4
  "description": "Private-network mobile bridge and CLI for Codex and OpenCode",
5
5
  "keywords": [
6
6
  "codex",
@@ -149,7 +149,7 @@ dependencies = [
149
149
 
150
150
  [[package]]
151
151
  name = "codex-rust-bridge"
152
- version = "5.1.3-internal.4"
152
+ version = "5.1.3-internal.6"
153
153
  dependencies = [
154
154
  "axum",
155
155
  "base64",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "codex-rust-bridge"
3
- version = "5.1.3-internal.4"
3
+ version = "5.1.3-internal.6"
4
4
  edition = "2021"
5
5
 
6
6
  [dependencies]
@@ -388,42 +388,91 @@ struct GitHubViewer {
388
388
  scopes: Vec<String>,
389
389
  }
390
390
 
391
+ #[derive(Debug, Clone)]
392
+ struct ResolvedGitHubAuthGrant {
393
+ access_token: String,
394
+ repositories: Vec<String>,
395
+ }
396
+
391
397
  async fn install_github_git_auth(
392
398
  state: &Arc<AppState>,
393
- access_token: &str,
394
- repositories: &[String],
399
+ request: GitHubAuthInstallRequest,
395
400
  ) -> Result<GitHubAuthInstallResponse, BridgeError> {
396
- let viewer = fetch_github_viewer(state, access_token).await?;
397
- if !github_token_can_be_used_for_git_auth(&viewer.scopes) {
398
- return Err(BridgeError::forbidden(
399
- "github_repo_scope_required",
400
- "GitHub repository access is required. Sign in again from the app and approve the required repository access.",
401
+ let resolved_grants = resolve_github_auth_grants(request)?;
402
+ if resolved_grants.is_empty() {
403
+ return Err(BridgeError::invalid_params(
404
+ "At least one GitHub auth grant is required",
401
405
  ));
402
406
  }
403
407
 
404
- let normalized_repositories = normalize_github_auth_repositories(repositories);
408
+ let mut login = None;
409
+ let mut scopes = Vec::new();
410
+ if let Some(first_grant) = resolved_grants.first() {
411
+ if let Ok(viewer) = fetch_github_viewer(state, &first_grant.access_token).await {
412
+ if !github_token_can_be_used_for_git_auth(&viewer.scopes) {
413
+ return Err(BridgeError::forbidden(
414
+ "github_repo_scope_required",
415
+ "GitHub repository access is required. Sign in again from the app and approve the required repository access.",
416
+ ));
417
+ }
418
+ login = Some(viewer.login);
419
+ scopes = viewer.scopes;
420
+ }
421
+ }
422
+
405
423
  let credentials_file = resolve_github_credentials_file_path()?;
406
424
  let git_config_file = resolve_github_git_config_file_path()?;
407
425
  ensure_private_parent_dir(&credentials_file).await?;
408
- write_github_credentials_file(&credentials_file, access_token, &normalized_repositories)
409
- .await?;
410
- write_github_git_config_file(
411
- &git_config_file,
412
- &credentials_file,
413
- &normalized_repositories,
414
- )
415
- .await?;
426
+ write_github_credentials_file(&credentials_file, &resolved_grants).await?;
427
+ write_github_git_config_file(&git_config_file, &credentials_file, &resolved_grants).await?;
416
428
  configure_git_credential_store(state, &credentials_file, &git_config_file).await?;
417
429
 
418
430
  Ok(GitHubAuthInstallResponse {
419
431
  installed: true,
420
432
  host: GITHUB_HOST.to_string(),
421
- login: viewer.login,
422
- scopes: viewer.scopes,
433
+ login,
434
+ scopes,
423
435
  credential_file: credentials_file.to_string_lossy().to_string(),
436
+ grants_installed: resolved_grants.len(),
424
437
  })
425
438
  }
426
439
 
440
+ fn resolve_github_auth_grants(
441
+ request: GitHubAuthInstallRequest,
442
+ ) -> Result<Vec<ResolvedGitHubAuthGrant>, BridgeError> {
443
+ let raw_grants = if let Some(grants) = request.grants {
444
+ grants
445
+ } else if let Some(access_token) = request.access_token {
446
+ vec![GitHubAuthGrantInput {
447
+ access_token,
448
+ repositories: request.repositories,
449
+ }]
450
+ } else {
451
+ Vec::new()
452
+ };
453
+
454
+ let mut grants = Vec::new();
455
+ for grant in raw_grants {
456
+ let access_token = grant.access_token.trim().to_string();
457
+ if access_token.is_empty() {
458
+ continue;
459
+ }
460
+
461
+ let repositories =
462
+ normalize_github_auth_repositories(grant.repositories.as_deref().unwrap_or(&[]));
463
+ if repositories.is_empty() {
464
+ continue;
465
+ }
466
+
467
+ grants.push(ResolvedGitHubAuthGrant {
468
+ access_token,
469
+ repositories,
470
+ });
471
+ }
472
+
473
+ Ok(grants)
474
+ }
475
+
427
476
  async fn fetch_github_viewer(
428
477
  state: &Arc<AppState>,
429
478
  access_token: &str,
@@ -565,18 +614,20 @@ async fn ensure_private_parent_dir(path: &Path) -> Result<(), BridgeError> {
565
614
 
566
615
  async fn write_github_credentials_file(
567
616
  credentials_file: &Path,
568
- access_token: &str,
569
- repositories: &[String],
617
+ grants: &[ResolvedGitHubAuthGrant],
570
618
  ) -> Result<(), BridgeError> {
571
619
  let mut content = String::new();
572
- let trimmed_access_token = access_token.trim();
573
- for repository in repositories {
574
- content.push_str(&format!(
575
- "https://x-access-token:{trimmed_access_token}@{GITHUB_HOST}/{repository}\n"
576
- ));
577
- content.push_str(&format!(
578
- "https://x-access-token:{trimmed_access_token}@{GITHUB_HOST}/{repository}.git\n"
579
- ));
620
+ for grant in grants {
621
+ for repository in &grant.repositories {
622
+ content.push_str(&format!(
623
+ "https://x-access-token:{}@{GITHUB_HOST}/{repository}\n",
624
+ grant.access_token
625
+ ));
626
+ content.push_str(&format!(
627
+ "https://x-access-token:{}@{GITHUB_HOST}/{repository}.git\n",
628
+ grant.access_token
629
+ ));
630
+ }
580
631
  }
581
632
 
582
633
  fs::write(credentials_file, content)
@@ -600,21 +651,23 @@ async fn write_github_credentials_file(
600
651
  async fn write_github_git_config_file(
601
652
  git_config_file: &Path,
602
653
  credentials_file: &Path,
603
- repositories: &[String],
654
+ grants: &[ResolvedGitHubAuthGrant],
604
655
  ) -> Result<(), BridgeError> {
605
656
  let helper_value = format!("store --file {}", credentials_file.to_string_lossy());
606
657
  let mut content = String::from(
607
658
  "[credential \"https://github.com\"]\n\tuseHttpPath = true\n[url \"https://github.com/\"]\n\tinsteadOf = git@github.com:\n\tinsteadOf = ssh://git@github.com/\n",
608
659
  );
609
660
 
610
- for repository in repositories {
611
- for context in [
612
- format!("https://{GITHUB_HOST}/{repository}"),
613
- format!("https://{GITHUB_HOST}/{repository}.git"),
614
- ] {
615
- content.push_str(&format!(
616
- "[credential \"{context}\"]\n\thelper =\n\thelper = {helper_value}\n\tusername = x-access-token\n"
617
- ));
661
+ for grant in grants {
662
+ for repository in &grant.repositories {
663
+ for context in [
664
+ format!("https://{GITHUB_HOST}/{repository}"),
665
+ format!("https://{GITHUB_HOST}/{repository}.git"),
666
+ ] {
667
+ content.push_str(&format!(
668
+ "[credential \"{context}\"]\n\thelper =\n\thelper = {helper_value}\n\tusername = x-access-token\n"
669
+ ));
670
+ }
618
671
  }
619
672
  }
620
673
 
@@ -785,7 +838,9 @@ impl AppState {
785
838
  let Some(service) = &self.github_codespaces_auth else {
786
839
  return false;
787
840
  };
788
- let Some(token) = extract_bearer_token(headers) else {
841
+ let Some(token) =
842
+ extract_auth_token(headers, query_token, self.config.allow_query_token_auth)
843
+ else {
789
844
  return false;
790
845
  };
791
846
 
@@ -793,6 +848,24 @@ impl AppState {
793
848
  }
794
849
  }
795
850
 
851
+ fn extract_auth_token<'a>(
852
+ headers: &'a HeaderMap,
853
+ query_token: Option<&'a str>,
854
+ allow_query_token_auth: bool,
855
+ ) -> Option<&'a str> {
856
+ if let Some(token) = extract_bearer_token(headers) {
857
+ return Some(token);
858
+ }
859
+
860
+ if allow_query_token_auth {
861
+ if let Some(token) = query_token.map(str::trim).filter(|token| !token.is_empty()) {
862
+ return Some(token);
863
+ }
864
+ }
865
+
866
+ None
867
+ }
868
+
796
869
  #[derive(Debug, Clone, Serialize)]
797
870
  #[serde(rename_all = "camelCase")]
798
871
  struct BrowserPreviewSessionResponse {
@@ -5549,6 +5622,14 @@ struct GitQueryRequest {
5549
5622
  #[derive(Debug, Clone, Serialize, Deserialize)]
5550
5623
  #[serde(rename_all = "camelCase")]
5551
5624
  struct GitHubAuthInstallRequest {
5625
+ access_token: Option<String>,
5626
+ repositories: Option<Vec<String>>,
5627
+ grants: Option<Vec<GitHubAuthGrantInput>>,
5628
+ }
5629
+
5630
+ #[derive(Debug, Clone, Serialize, Deserialize)]
5631
+ #[serde(rename_all = "camelCase")]
5632
+ struct GitHubAuthGrantInput {
5552
5633
  access_token: String,
5553
5634
  repositories: Option<Vec<String>>,
5554
5635
  }
@@ -5558,9 +5639,10 @@ struct GitHubAuthInstallRequest {
5558
5639
  struct GitHubAuthInstallResponse {
5559
5640
  installed: bool,
5560
5641
  host: String,
5561
- login: String,
5642
+ login: Option<String>,
5562
5643
  scopes: Vec<String>,
5563
5644
  credential_file: String,
5645
+ grants_installed: usize,
5564
5646
  }
5565
5647
 
5566
5648
  #[derive(Debug, Clone, Default, Serialize, Deserialize)]
@@ -6923,12 +7005,7 @@ async fn handle_bridge_method(
6923
7005
  let request: GitHubAuthInstallRequest =
6924
7006
  serde_json::from_value(params.unwrap_or_else(|| json!({})))
6925
7007
  .map_err(|error| BridgeError::invalid_params(&error.to_string()))?;
6926
- let result = install_github_git_auth(
6927
- state,
6928
- &request.access_token,
6929
- request.repositories.as_deref().unwrap_or(&[]),
6930
- )
6931
- .await?;
7008
+ let result = install_github_git_auth(state, request).await?;
6932
7009
  serde_json::to_value(result).map_err(|error| BridgeError::server(&error.to_string()))
6933
7010
  }
6934
7011
  "bridge/attachments/upload" => {