From 798ace9dd4797fb603e8fc50f8eb06a1329cf634 Mon Sep 17 00:00:00 2001 From: boxbeam Date: Mon, 1 Apr 2024 15:56:00 -0400 Subject: [PATCH 01/18] feat(db): Create github provider table and DAO --- .../migrations/0022_github-provider.down.sql | 1 + .../migrations/0022_github-provider.up.sql | 8 +++ ee/tabby-db/src/github_provider.rs | 57 +++++++++++++++++++ ee/tabby-db/src/lib.rs | 1 + ee/tabby-webserver/Cargo.toml | 1 + 5 files changed, 68 insertions(+) create mode 100644 ee/tabby-db/migrations/0022_github-provider.down.sql create mode 100644 ee/tabby-db/migrations/0022_github-provider.up.sql create mode 100644 ee/tabby-db/src/github_provider.rs diff --git a/ee/tabby-db/migrations/0022_github-provider.down.sql b/ee/tabby-db/migrations/0022_github-provider.down.sql new file mode 100644 index 000000000000..5dc41e94ce3f --- /dev/null +++ b/ee/tabby-db/migrations/0022_github-provider.down.sql @@ -0,0 +1 @@ +DROP TABLE github_provider; diff --git a/ee/tabby-db/migrations/0022_github-provider.up.sql b/ee/tabby-db/migrations/0022_github-provider.up.sql new file mode 100644 index 000000000000..0ed058bc78ae --- /dev/null +++ b/ee/tabby-db/migrations/0022_github-provider.up.sql @@ -0,0 +1,8 @@ +CREATE TABLE github_provider ( + id INTEGER NOT NULL PRIMARY KEY, + name TEXT NOT NULL, + github_url TEXT NOT NULL, + application_id TEXT NOT NULL, + secret TEXT NOT NULL, + CONSTRAINT `idx_name` UNIQUE (`name`) +); diff --git a/ee/tabby-db/src/github_provider.rs b/ee/tabby-db/src/github_provider.rs new file mode 100644 index 000000000000..57ca893d3f98 --- /dev/null +++ b/ee/tabby-db/src/github_provider.rs @@ -0,0 +1,57 @@ +use anyhow::{anyhow, Result}; +use sqlx::{query, query_as}; + +use crate::DbConn; + +pub struct GithubProviderDAO { + pub name: String, + pub github_url: String, + pub application_id: String, + pub secret: String, +} + +const GITHUB_PROVIDER_ROWID: i64 = 1; + +impl DbConn { + pub async fn update_github_provider( + &self, + name: String, + github_url: String, + application_id: String, + secret: String, + ) -> Result<()> { + query!("INSERT INTO github_provider (id, name, github_url, application_id, secret) VALUES ($1, $2, $3, $4, $5) + ON CONFLICT(name) DO UPDATE SET github_url=$3, application_id=$4, secret=$5 WHERE id = $1;", + GITHUB_PROVIDER_ROWID, + name, + github_url, + application_id, + secret + ).execute(&self.pool).await?; + Ok(()) + } + + pub async fn get_github_provider(&self) -> Result { + let provider = query_as!( + GithubProviderDAO, + "SELECT name, github_url, application_id, secret FROM github_provider WHERE id = ?;", + GITHUB_PROVIDER_ROWID + ) + .fetch_one(&self.pool) + .await?; + Ok(provider) + } + + pub async fn delete_github_provider(&self) -> Result<()> { + let res = query!( + "DELETE FROM github_provider WHERE id = ?;", + GITHUB_PROVIDER_ROWID + ) + .execute(&self.pool) + .await?; + if res.rows_affected() != 1 { + return Err(anyhow!("No github provider details to delete")); + } + Ok(()) + } +} diff --git a/ee/tabby-db/src/lib.rs b/ee/tabby-db/src/lib.rs index 2fc01f669e48..aa49f5a77293 100644 --- a/ee/tabby-db/src/lib.rs +++ b/ee/tabby-db/src/lib.rs @@ -17,6 +17,7 @@ pub use users::UserDAO; pub mod cache; mod email_setting; +mod github_provider; mod invitations; mod job_runs; mod oauth_credential; diff --git a/ee/tabby-webserver/Cargo.toml b/ee/tabby-webserver/Cargo.toml index d59047df3ef4..d8d297091fc0 100644 --- a/ee/tabby-webserver/Cargo.toml +++ b/ee/tabby-webserver/Cargo.toml @@ -25,6 +25,7 @@ juniper-axum = { path = "../../crates/juniper-axum" } lazy_static.workspace = true lettre = { version = "0.11.3", features = ["tokio1", "tokio1-native-tls"] } mime_guess = "2.0.4" +octocrab = "0.37.0" pin-project = "1.1.3" querystring = "1.1.0" reqwest = { workspace = true, features = ["json"] } From db96a60b277c52a4855ef997953239650e55c3d3 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 20:03:29 +0000 Subject: [PATCH 02/18] [autofix.ci] apply automated fixes --- ee/tabby-webserver/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/ee/tabby-webserver/Cargo.toml b/ee/tabby-webserver/Cargo.toml index d8d297091fc0..d59047df3ef4 100644 --- a/ee/tabby-webserver/Cargo.toml +++ b/ee/tabby-webserver/Cargo.toml @@ -25,7 +25,6 @@ juniper-axum = { path = "../../crates/juniper-axum" } lazy_static.workspace = true lettre = { version = "0.11.3", features = ["tokio1", "tokio1-native-tls"] } mime_guess = "2.0.4" -octocrab = "0.37.0" pin-project = "1.1.3" querystring = "1.1.0" reqwest = { workspace = true, features = ["json"] } From 5638fbff00a648b12d20ef399c06d24725e36399 Mon Sep 17 00:00:00 2001 From: boxbeam Date: Tue, 2 Apr 2024 13:56:30 -0400 Subject: [PATCH 03/18] Apply suggestions --- .../migrations/0022_github-provider.down.sql | 2 +- .../migrations/0022_github-provider.up.sql | 9 ++- ee/tabby-db/src/github_provider.rs | 57 ------------------- ee/tabby-db/src/github_repository_provider.rs | 50 ++++++++++++++++ ee/tabby-db/src/lib.rs | 2 +- 5 files changed, 56 insertions(+), 64 deletions(-) delete mode 100644 ee/tabby-db/src/github_provider.rs create mode 100644 ee/tabby-db/src/github_repository_provider.rs diff --git a/ee/tabby-db/migrations/0022_github-provider.down.sql b/ee/tabby-db/migrations/0022_github-provider.down.sql index 5dc41e94ce3f..2af049ced3da 100644 --- a/ee/tabby-db/migrations/0022_github-provider.down.sql +++ b/ee/tabby-db/migrations/0022_github-provider.down.sql @@ -1 +1 @@ -DROP TABLE github_provider; +DROP TABLE github_repository_provider; diff --git a/ee/tabby-db/migrations/0022_github-provider.up.sql b/ee/tabby-db/migrations/0022_github-provider.up.sql index 0ed058bc78ae..cc0acbb62561 100644 --- a/ee/tabby-db/migrations/0022_github-provider.up.sql +++ b/ee/tabby-db/migrations/0022_github-provider.up.sql @@ -1,8 +1,7 @@ -CREATE TABLE github_provider ( - id INTEGER NOT NULL PRIMARY KEY, - name TEXT NOT NULL, - github_url TEXT NOT NULL, +CREATE TABLE github_repository_provider ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + display_name TEXT NOT NULL, application_id TEXT NOT NULL, secret TEXT NOT NULL, - CONSTRAINT `idx_name` UNIQUE (`name`) + CONSTRAINT `idx_application_id` UNIQUE (`application_id`) ); diff --git a/ee/tabby-db/src/github_provider.rs b/ee/tabby-db/src/github_provider.rs deleted file mode 100644 index 57ca893d3f98..000000000000 --- a/ee/tabby-db/src/github_provider.rs +++ /dev/null @@ -1,57 +0,0 @@ -use anyhow::{anyhow, Result}; -use sqlx::{query, query_as}; - -use crate::DbConn; - -pub struct GithubProviderDAO { - pub name: String, - pub github_url: String, - pub application_id: String, - pub secret: String, -} - -const GITHUB_PROVIDER_ROWID: i64 = 1; - -impl DbConn { - pub async fn update_github_provider( - &self, - name: String, - github_url: String, - application_id: String, - secret: String, - ) -> Result<()> { - query!("INSERT INTO github_provider (id, name, github_url, application_id, secret) VALUES ($1, $2, $3, $4, $5) - ON CONFLICT(name) DO UPDATE SET github_url=$3, application_id=$4, secret=$5 WHERE id = $1;", - GITHUB_PROVIDER_ROWID, - name, - github_url, - application_id, - secret - ).execute(&self.pool).await?; - Ok(()) - } - - pub async fn get_github_provider(&self) -> Result { - let provider = query_as!( - GithubProviderDAO, - "SELECT name, github_url, application_id, secret FROM github_provider WHERE id = ?;", - GITHUB_PROVIDER_ROWID - ) - .fetch_one(&self.pool) - .await?; - Ok(provider) - } - - pub async fn delete_github_provider(&self) -> Result<()> { - let res = query!( - "DELETE FROM github_provider WHERE id = ?;", - GITHUB_PROVIDER_ROWID - ) - .execute(&self.pool) - .await?; - if res.rows_affected() != 1 { - return Err(anyhow!("No github provider details to delete")); - } - Ok(()) - } -} diff --git a/ee/tabby-db/src/github_repository_provider.rs b/ee/tabby-db/src/github_repository_provider.rs new file mode 100644 index 000000000000..1f4bee83fe6a --- /dev/null +++ b/ee/tabby-db/src/github_repository_provider.rs @@ -0,0 +1,50 @@ +use anyhow::{anyhow, Result}; +use sqlx::{query, query_as}; + +use crate::{DbConn, SQLXResultExt}; + +pub struct GithubProviderDAO { + pub display_name: String, + pub application_id: String, + pub secret: String, +} + +impl DbConn { + pub async fn update_github_provider( + &self, + name: String, + application_id: String, + secret: String, + ) -> Result<()> { + query!("INSERT INTO github_repository_provider (display_name, application_id, secret) VALUES ($1, $2, $3);", + name, + application_id, + secret + ).execute(&self.pool).await.unique_error("Provider already exists")?; + Ok(()) + } + + pub async fn get_github_provider(&self, display_name: String) -> Result { + let provider = query_as!( + GithubProviderDAO, + "SELECT display_name, application_id, secret FROM github_repository_provider WHERE display_name = ?;", + display_name + ) + .fetch_one(&self.pool) + .await?; + Ok(provider) + } + + pub async fn delete_github_provider(&self, display_name: String) -> Result<()> { + let res = query!( + "DELETE FROM github_repository_provider WHERE display_name = ?;", + display_name + ) + .execute(&self.pool) + .await?; + if res.rows_affected() != 1 { + return Err(anyhow!("No github provider details to delete")); + } + Ok(()) + } +} diff --git a/ee/tabby-db/src/lib.rs b/ee/tabby-db/src/lib.rs index aa49f5a77293..a373384069ee 100644 --- a/ee/tabby-db/src/lib.rs +++ b/ee/tabby-db/src/lib.rs @@ -17,7 +17,7 @@ pub use users::UserDAO; pub mod cache; mod email_setting; -mod github_provider; +mod github_repository_provider; mod invitations; mod job_runs; mod oauth_credential; From 2aa6ddcaa1f6fc680e77cf19ea86a7b341af378c Mon Sep 17 00:00:00 2001 From: boxbeam Date: Wed, 3 Apr 2024 14:02:50 -0400 Subject: [PATCH 04/18] Apply suggestions, add DAO for github repositories --- .../migrations/0022_github-provider.down.sql | 1 + .../migrations/0022_github-provider.up.sql | 10 +++- ee/tabby-db/src/github_repository_provider.rs | 58 ++++++++++++++++--- 3 files changed, 60 insertions(+), 9 deletions(-) diff --git a/ee/tabby-db/migrations/0022_github-provider.down.sql b/ee/tabby-db/migrations/0022_github-provider.down.sql index 2af049ced3da..2ada5c2aaf31 100644 --- a/ee/tabby-db/migrations/0022_github-provider.down.sql +++ b/ee/tabby-db/migrations/0022_github-provider.down.sql @@ -1 +1,2 @@ DROP TABLE github_repository_provider; +DROP TABLE github_provided_repositories; diff --git a/ee/tabby-db/migrations/0022_github-provider.up.sql b/ee/tabby-db/migrations/0022_github-provider.up.sql index cc0acbb62561..465e80d59987 100644 --- a/ee/tabby-db/migrations/0022_github-provider.up.sql +++ b/ee/tabby-db/migrations/0022_github-provider.up.sql @@ -1,7 +1,15 @@ -CREATE TABLE github_repository_provider ( +CREATE TABLE github_repository_provider( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, display_name TEXT NOT NULL, application_id TEXT NOT NULL, secret TEXT NOT NULL, CONSTRAINT `idx_application_id` UNIQUE (`application_id`) ); +CREATE TABLE github_provided_repositories( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + provider_id INTEGER NOT NULL, + candidate_id TEXT NOT NULL, + name TEXT NOT NULL, + git_url TEXT NOT NULL, + CONSTRAINT `idx_provider_id_candidate_id` UNIQUE (`candidate_id`, `provider_id`) +); diff --git a/ee/tabby-db/src/github_repository_provider.rs b/ee/tabby-db/src/github_repository_provider.rs index 1f4bee83fe6a..2605fc176807 100644 --- a/ee/tabby-db/src/github_repository_provider.rs +++ b/ee/tabby-db/src/github_repository_provider.rs @@ -3,30 +3,40 @@ use sqlx::{query, query_as}; use crate::{DbConn, SQLXResultExt}; -pub struct GithubProviderDAO { +pub struct GithubRepositoryProviderDAO { pub display_name: String, pub application_id: String, pub secret: String, } +pub struct GithubProvidedRepositoryDAO { + pub provider_id: i64, + pub candidate_id: String, + pub name: String, + pub git_url: String, +} + impl DbConn { - pub async fn update_github_provider( + pub async fn create_github_provider( &self, name: String, application_id: String, secret: String, - ) -> Result<()> { - query!("INSERT INTO github_repository_provider (display_name, application_id, secret) VALUES ($1, $2, $3);", + ) -> Result { + let res = query!("INSERT INTO github_repository_provider (display_name, application_id, secret) VALUES ($1, $2, $3);", name, application_id, secret - ).execute(&self.pool).await.unique_error("Provider already exists")?; - Ok(()) + ).execute(&self.pool).await.unique_error("GitHub Application ID already exists")?; + Ok(res.last_insert_rowid()) } - pub async fn get_github_provider(&self, display_name: String) -> Result { + pub async fn get_github_provider( + &self, + display_name: String, + ) -> Result { let provider = query_as!( - GithubProviderDAO, + GithubRepositoryProviderDAO, "SELECT display_name, application_id, secret FROM github_repository_provider WHERE display_name = ?;", display_name ) @@ -47,4 +57,36 @@ impl DbConn { } Ok(()) } + + pub async fn create_github_provided_repository( + &self, + provider_id: i64, + candidate_id: String, + name: String, + git_url: String, + ) -> Result { + let res = query!( + "INSERT INTO github_provided_repositories(provider_id, candidate_id, name, git_url) VALUES (?, ?, ?, ?)", + provider_id, candidate_id, name, git_url + ).execute(&self.pool).await?; + Ok(res.last_insert_rowid()) + } + + pub async fn delete_github_provided_repository( + &self, + provider_id: i64, + candidate_id: String, + ) -> Result<()> { + let res = query!( + "DELETE FROM github_provided_repositories WHERE provider_id = ? AND candidate_id = ?", + provider_id, + candidate_id + ) + .execute(&self.pool) + .await?; + if res.rows_affected() == 0 { + return Err(anyhow!("Specified repository does not exist")); + } + Ok(()) + } } From 61769ca711fc97ae0c776cd4848678c7150798c7 Mon Sep 17 00:00:00 2001 From: boxbeam Date: Wed, 3 Apr 2024 14:19:54 -0400 Subject: [PATCH 05/18] Add list query --- ee/tabby-db/src/github_repository_provider.rs | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/ee/tabby-db/src/github_repository_provider.rs b/ee/tabby-db/src/github_repository_provider.rs index 2605fc176807..a3653030d4b1 100644 --- a/ee/tabby-db/src/github_repository_provider.rs +++ b/ee/tabby-db/src/github_repository_provider.rs @@ -1,5 +1,6 @@ use anyhow::{anyhow, Result}; -use sqlx::{query, query_as}; +use sqlx::{prelude::FromRow, query, query_as}; +use tabby_db_macros::query_paged_as; use crate::{DbConn, SQLXResultExt}; @@ -9,6 +10,7 @@ pub struct GithubRepositoryProviderDAO { pub secret: String, } +#[derive(FromRow)] pub struct GithubProvidedRepositoryDAO { pub provider_id: i64, pub candidate_id: String, @@ -89,4 +91,25 @@ impl DbConn { } Ok(()) } + + pub async fn list_github_provided_repositories( + &self, + provider_id: i64, + limit: Option, + skip_id: Option, + backwards: bool, + ) -> Result> { + let repos = query_paged_as!( + GithubProvidedRepositoryDAO, + "github_provided_repositories", + ["provider_id", "candidate_id", "git_url", "name"], + limit, + skip_id, + backwards, + Some(format!("provider_id = {provider_id}")) + ) + .fetch_all(&self.pool) + .await?; + Ok(repos) + } } From 9e7a73528a65a0b2ae39be881a56b68c9da72cbc Mon Sep 17 00:00:00 2001 From: boxbeam Date: Thu, 4 Apr 2024 13:35:47 -0400 Subject: [PATCH 06/18] Apply suggestion --- ee/tabby-db/migrations/0022_github-provider.up.sql | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ee/tabby-db/migrations/0022_github-provider.up.sql b/ee/tabby-db/migrations/0022_github-provider.up.sql index 465e80d59987..d788266291fc 100644 --- a/ee/tabby-db/migrations/0022_github-provider.up.sql +++ b/ee/tabby-db/migrations/0022_github-provider.up.sql @@ -7,9 +7,10 @@ CREATE TABLE github_repository_provider( ); CREATE TABLE github_provided_repositories( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - provider_id INTEGER NOT NULL, + github_repository_provider_id INTEGER NOT NULL, candidate_id TEXT NOT NULL, name TEXT NOT NULL, git_url TEXT NOT NULL, - CONSTRAINT `idx_provider_id_candidate_id` UNIQUE (`candidate_id`, `provider_id`) + CONSTRAINT `idx_provider_id_candidate_id` UNIQUE (`candidate_id`, `github_repository_provider_id`), + FOREIGN KEY (github_repository_provider_id) REFERENCES github_repository_provider(id) ON DELETE CASCADE ); From e2c8454b4ddc8ba3fb90bafe85df304c457dd717 Mon Sep 17 00:00:00 2001 From: boxbeam Date: Fri, 5 Apr 2024 17:26:14 -0400 Subject: [PATCH 07/18] Apply suggestions --- .../migrations/0022_github-provider.up.sql | 6 +- ee/tabby-db/src/github_repository_provider.rs | 58 +++++++++---------- 2 files changed, 31 insertions(+), 33 deletions(-) diff --git a/ee/tabby-db/migrations/0022_github-provider.up.sql b/ee/tabby-db/migrations/0022_github-provider.up.sql index d788266291fc..0960a94aab03 100644 --- a/ee/tabby-db/migrations/0022_github-provider.up.sql +++ b/ee/tabby-db/migrations/0022_github-provider.up.sql @@ -5,12 +5,14 @@ CREATE TABLE github_repository_provider( secret TEXT NOT NULL, CONSTRAINT `idx_application_id` UNIQUE (`application_id`) ); + CREATE TABLE github_provided_repositories( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, github_repository_provider_id INTEGER NOT NULL, - candidate_id TEXT NOT NULL, + -- vendor_id refers to the `node_id` field in the output of https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#list-organization-repositories + vendor_id TEXT NOT NULL, name TEXT NOT NULL, git_url TEXT NOT NULL, - CONSTRAINT `idx_provider_id_candidate_id` UNIQUE (`candidate_id`, `github_repository_provider_id`), + CONSTRAINT `idx_vendor_id` UNIQUE (`vendor_id`), FOREIGN KEY (github_repository_provider_id) REFERENCES github_repository_provider(id) ON DELETE CASCADE ); diff --git a/ee/tabby-db/src/github_repository_provider.rs b/ee/tabby-db/src/github_repository_provider.rs index a3653030d4b1..0cfce83d2b4c 100644 --- a/ee/tabby-db/src/github_repository_provider.rs +++ b/ee/tabby-db/src/github_repository_provider.rs @@ -12,8 +12,8 @@ pub struct GithubRepositoryProviderDAO { #[derive(FromRow)] pub struct GithubProvidedRepositoryDAO { - pub provider_id: i64, - pub candidate_id: String, + pub github_repository_provider_id: i64, + pub vendor_id: String, pub name: String, pub git_url: String, } @@ -33,27 +33,21 @@ impl DbConn { Ok(res.last_insert_rowid()) } - pub async fn get_github_provider( - &self, - display_name: String, - ) -> Result { + pub async fn get_github_provider(&self, id: i64) -> Result { let provider = query_as!( GithubRepositoryProviderDAO, - "SELECT display_name, application_id, secret FROM github_repository_provider WHERE display_name = ?;", - display_name + "SELECT display_name, application_id, secret FROM github_repository_provider WHERE id = ?;", + id ) .fetch_one(&self.pool) .await?; Ok(provider) } - pub async fn delete_github_provider(&self, display_name: String) -> Result<()> { - let res = query!( - "DELETE FROM github_repository_provider WHERE display_name = ?;", - display_name - ) - .execute(&self.pool) - .await?; + pub async fn delete_github_provider(&self, id: i64) -> Result<()> { + let res = query!("DELETE FROM github_repository_provider WHERE id = ?;", id) + .execute(&self.pool) + .await?; if res.rows_affected() != 1 { return Err(anyhow!("No github provider details to delete")); } @@ -68,24 +62,16 @@ impl DbConn { git_url: String, ) -> Result { let res = query!( - "INSERT INTO github_provided_repositories(provider_id, candidate_id, name, git_url) VALUES (?, ?, ?, ?)", + "INSERT INTO github_provided_repositories(github_repository_provider_id, vendor_id, name, git_url) VALUES (?, ?, ?, ?)", provider_id, candidate_id, name, git_url ).execute(&self.pool).await?; Ok(res.last_insert_rowid()) } - pub async fn delete_github_provided_repository( - &self, - provider_id: i64, - candidate_id: String, - ) -> Result<()> { - let res = query!( - "DELETE FROM github_provided_repositories WHERE provider_id = ? AND candidate_id = ?", - provider_id, - candidate_id - ) - .execute(&self.pool) - .await?; + pub async fn delete_github_provided_repository(&self, id: i64) -> Result<()> { + let res = query!("DELETE FROM github_provided_repositories WHERE id = ?", id) + .execute(&self.pool) + .await?; if res.rows_affected() == 0 { return Err(anyhow!("Specified repository does not exist")); } @@ -94,19 +80,29 @@ impl DbConn { pub async fn list_github_provided_repositories( &self, - provider_id: i64, + github_repository_provider_ids: Vec, limit: Option, skip_id: Option, backwards: bool, ) -> Result> { + let ids: Vec<_> = github_repository_provider_ids + .into_iter() + .map(|id| id.to_string()) + .collect(); + let ids = format!("({})", ids.join(", ")); let repos = query_paged_as!( GithubProvidedRepositoryDAO, "github_provided_repositories", - ["provider_id", "candidate_id", "git_url", "name"], + [ + "github_repository_provider_id", + "vendor_id", + "git_url", + "name" + ], limit, skip_id, backwards, - Some(format!("provider_id = {provider_id}")) + Some(format!("github_repository_provider_id IN {ids}")) ) .fetch_all(&self.pool) .await?; From bd9de5f789fab95f6fdccda86c1570205b79c077 Mon Sep 17 00:00:00 2001 From: boxbeam Date: Tue, 9 Apr 2024 10:01:32 -0400 Subject: [PATCH 08/18] Update db spec --- .../migrations/0022_github-provider.up.sql | 2 + .../src/schema/github_repository_provider.rs | 20 +++++++++ ee/tabby-webserver/src/schema/mod.rs | 9 +++- .../src/service/github_repository_provider.rs | 45 +++++++++++++++++++ ee/tabby-webserver/src/service/mod.rs | 13 ++++++ 5 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 ee/tabby-webserver/src/schema/github_repository_provider.rs create mode 100644 ee/tabby-webserver/src/service/github_repository_provider.rs diff --git a/ee/tabby-db/migrations/0022_github-provider.up.sql b/ee/tabby-db/migrations/0022_github-provider.up.sql index 0960a94aab03..ccf336572ab1 100644 --- a/ee/tabby-db/migrations/0022_github-provider.up.sql +++ b/ee/tabby-db/migrations/0022_github-provider.up.sql @@ -3,6 +3,7 @@ CREATE TABLE github_repository_provider( display_name TEXT NOT NULL, application_id TEXT NOT NULL, secret TEXT NOT NULL, + access_token TEXT, CONSTRAINT `idx_application_id` UNIQUE (`application_id`) ); @@ -13,6 +14,7 @@ CREATE TABLE github_provided_repositories( vendor_id TEXT NOT NULL, name TEXT NOT NULL, git_url TEXT NOT NULL, + active BOOLEAN NOT NULL DEFAULT TRUE, CONSTRAINT `idx_vendor_id` UNIQUE (`vendor_id`), FOREIGN KEY (github_repository_provider_id) REFERENCES github_repository_provider(id) ON DELETE CASCADE ); diff --git a/ee/tabby-webserver/src/schema/github_repository_provider.rs b/ee/tabby-webserver/src/schema/github_repository_provider.rs new file mode 100644 index 000000000000..1618ee9c3867 --- /dev/null +++ b/ee/tabby-webserver/src/schema/github_repository_provider.rs @@ -0,0 +1,20 @@ +use anyhow::Result; +use async_trait::async_trait; +use juniper::{GraphQLObject, ID}; + +#[derive(GraphQLObject)] +pub struct GithubRepositoryProvider { + pub display_name: String, + pub application_id: String, +} + +#[async_trait] +pub trait GithubRepositoryProviderService: Send + Sync { + async fn get_github_repository_provider(&self, id: ID) -> Result; + async fn read_github_repository_provider_secret(&self, id: ID) -> Result; + async fn set_github_repository_provider_token( + &self, + id: ID, + access_token: String, + ) -> Result<()>; +} diff --git a/ee/tabby-webserver/src/schema/mod.rs b/ee/tabby-webserver/src/schema/mod.rs index 3168b09bd8a4..3629da60c48b 100644 --- a/ee/tabby-webserver/src/schema/mod.rs +++ b/ee/tabby-webserver/src/schema/mod.rs @@ -1,6 +1,7 @@ pub mod analytic; pub mod auth; pub mod email; +pub mod github_repository_provider; pub mod job; pub mod license; pub mod repository; @@ -37,12 +38,17 @@ use self::{ }, email::{EmailService, EmailSetting, EmailSettingInput}, job::JobStats, + github_repository_provider::GithubRepositoryProviderService, license::{IsLicenseValid, LicenseInfo, LicenseService, LicenseType}, - repository::{FileEntrySearchResult, Repository, RepositoryService}, + repository::{Repository, RepositoryService}, setting::{ NetworkSetting, NetworkSettingInput, SecuritySetting, SecuritySettingInput, SettingService, }, }; +use crate::schema::{ + auth::{JWTPayload, OAuthCredential, OAuthProvider}, + job::JobStats, +}; pub trait ServiceLocator: Send + Sync { fn auth(&self) -> Arc; @@ -55,6 +61,7 @@ pub trait ServiceLocator: Send + Sync { fn setting(&self) -> Arc; fn license(&self) -> Arc; fn analytic(&self) -> Arc; + fn github_repository_provider(&self) -> Arc; } pub struct Context { diff --git a/ee/tabby-webserver/src/service/github_repository_provider.rs b/ee/tabby-webserver/src/service/github_repository_provider.rs new file mode 100644 index 000000000000..e2840efaadcd --- /dev/null +++ b/ee/tabby-webserver/src/service/github_repository_provider.rs @@ -0,0 +1,45 @@ +use anyhow::Result; +use async_trait::async_trait; +use juniper::ID; +use tabby_db::DbConn; + +use crate::schema::github_repository_provider::{ + GithubRepositoryProvider, GithubRepositoryProviderService, +}; + +use super::AsRowid; + +struct GithubRepositoryProviderServiceImpl { + db: DbConn, +} + +pub fn new_github_repository_provider_service(db: DbConn) -> impl GithubRepositoryProviderService { + GithubRepositoryProviderServiceImpl { db } +} + +#[async_trait] +impl GithubRepositoryProviderService for GithubRepositoryProviderServiceImpl { + async fn get_github_repository_provider(&self, id: ID) -> Result { + let provider = self.db.get_github_provider(id.as_rowid()? as i64).await?; + Ok(GithubRepositoryProvider { + display_name: provider.display_name, + application_id: provider.application_id, + }) + } + + async fn read_github_repository_provider_secret(&self, id: ID) -> Result { + let provider = self.db.get_github_provider(id.as_rowid()? as i64).await?; + Ok(provider.secret) + } + + async fn set_github_repository_provider_token( + &self, + id: ID, + access_token: String, + ) -> Result<()> { + self.db + .update_github_provider_token(id.as_rowid()? as i64, access_token) + .await?; + Ok(()) + } +} diff --git a/ee/tabby-webserver/src/service/mod.rs b/ee/tabby-webserver/src/service/mod.rs index b023f6fd5500..021d39e9fc9b 100644 --- a/ee/tabby-webserver/src/service/mod.rs +++ b/ee/tabby-webserver/src/service/mod.rs @@ -3,6 +3,7 @@ mod auth; mod dao; mod email; pub mod event_logger; +mod github_repository_provider; mod job; mod license; mod proxy; @@ -30,12 +31,14 @@ use tracing::{info, warn}; use self::{ analytic::new_analytic_service, auth::new_authentication_service, email::new_email_service, + github_repository_provider::new_github_repository_provider_service, license::new_license_service, }; use crate::schema::{ analytic::AnalyticService, auth::AuthenticationService, email::EmailService, + github_repository_provider::GithubRepositoryProviderService, job::JobService, license::{IsLicenseValid, LicenseService}, repository::RepositoryService, @@ -52,6 +55,7 @@ struct ServerContext { mail: Arc, auth: Arc, license: Arc, + github_repository_provider: Arc, logger: Arc, code: Arc, @@ -87,6 +91,9 @@ impl ServerContext { license.clone(), )), license, + github_repository_provider: Arc::new(new_github_repository_provider_service( + db_conn.clone(), + )), db_conn, logger, code, @@ -294,6 +301,12 @@ impl ServiceLocator for Arc { fn analytic(&self) -> Arc { new_analytic_service(self.db_conn.clone()) } + + fn github_repository_provider( + &self, + ) -> Arc { + self.github_repository_provider.clone() + } } pub async fn create_service_locator( From 9682fa2804ec235638803754c8c1551e2ef4804f Mon Sep 17 00:00:00 2001 From: boxbeam Date: Tue, 9 Apr 2024 10:04:14 -0400 Subject: [PATCH 09/18] Use schema::result --- ee/tabby-webserver/src/schema/github_repository_provider.rs | 2 +- ee/tabby-webserver/src/service/github_repository_provider.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ee/tabby-webserver/src/schema/github_repository_provider.rs b/ee/tabby-webserver/src/schema/github_repository_provider.rs index 1618ee9c3867..5379a6281b35 100644 --- a/ee/tabby-webserver/src/schema/github_repository_provider.rs +++ b/ee/tabby-webserver/src/schema/github_repository_provider.rs @@ -1,4 +1,4 @@ -use anyhow::Result; +use crate::schema::Result; use async_trait::async_trait; use juniper::{GraphQLObject, ID}; diff --git a/ee/tabby-webserver/src/service/github_repository_provider.rs b/ee/tabby-webserver/src/service/github_repository_provider.rs index e2840efaadcd..1bd5582f478a 100644 --- a/ee/tabby-webserver/src/service/github_repository_provider.rs +++ b/ee/tabby-webserver/src/service/github_repository_provider.rs @@ -1,4 +1,4 @@ -use anyhow::Result; +use crate::schema::Result; use async_trait::async_trait; use juniper::ID; use tabby_db::DbConn; From 9aaa81f242237efa0cc480705e84c06f1e9c3dcb Mon Sep 17 00:00:00 2001 From: boxbeam Date: Tue, 9 Apr 2024 10:44:54 -0400 Subject: [PATCH 10/18] Implement github provider OAuth flow --- .../migrations/0022_github-provider.up.sql | 12 -- ee/tabby-db/src/github_repository_provider.rs | 62 ++------- ee/tabby-webserver/src/handler.rs | 7 +- ee/tabby-webserver/src/oauth/github.rs | 14 +- ee/tabby-webserver/src/repositories/mod.rs | 24 +++- ee/tabby-webserver/src/repositories/oauth.rs | 120 ++++++++++++++++++ 6 files changed, 164 insertions(+), 75 deletions(-) create mode 100644 ee/tabby-webserver/src/repositories/oauth.rs diff --git a/ee/tabby-db/migrations/0022_github-provider.up.sql b/ee/tabby-db/migrations/0022_github-provider.up.sql index ccf336572ab1..0634b42cb7ac 100644 --- a/ee/tabby-db/migrations/0022_github-provider.up.sql +++ b/ee/tabby-db/migrations/0022_github-provider.up.sql @@ -6,15 +6,3 @@ CREATE TABLE github_repository_provider( access_token TEXT, CONSTRAINT `idx_application_id` UNIQUE (`application_id`) ); - -CREATE TABLE github_provided_repositories( - id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - github_repository_provider_id INTEGER NOT NULL, - -- vendor_id refers to the `node_id` field in the output of https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#list-organization-repositories - vendor_id TEXT NOT NULL, - name TEXT NOT NULL, - git_url TEXT NOT NULL, - active BOOLEAN NOT NULL DEFAULT TRUE, - CONSTRAINT `idx_vendor_id` UNIQUE (`vendor_id`), - FOREIGN KEY (github_repository_provider_id) REFERENCES github_repository_provider(id) ON DELETE CASCADE -); diff --git a/ee/tabby-db/src/github_repository_provider.rs b/ee/tabby-db/src/github_repository_provider.rs index 0cfce83d2b4c..c9d557c849e4 100644 --- a/ee/tabby-db/src/github_repository_provider.rs +++ b/ee/tabby-db/src/github_repository_provider.rs @@ -1,6 +1,5 @@ use anyhow::{anyhow, Result}; use sqlx::{prelude::FromRow, query, query_as}; -use tabby_db_macros::query_paged_as; use crate::{DbConn, SQLXResultExt}; @@ -54,58 +53,21 @@ impl DbConn { Ok(()) } - pub async fn create_github_provided_repository( - &self, - provider_id: i64, - candidate_id: String, - name: String, - git_url: String, - ) -> Result { + pub async fn update_github_provider_token(&self, id: i64, access_token: String) -> Result<()> { let res = query!( - "INSERT INTO github_provided_repositories(github_repository_provider_id, vendor_id, name, git_url) VALUES (?, ?, ?, ?)", - provider_id, candidate_id, name, git_url - ).execute(&self.pool).await?; - Ok(res.last_insert_rowid()) - } + "UPDATE github_repository_provider SET access_token = ? WHERE id = ?", + access_token, + id + ) + .execute(&self.pool) + .await?; - pub async fn delete_github_provided_repository(&self, id: i64) -> Result<()> { - let res = query!("DELETE FROM github_provided_repositories WHERE id = ?", id) - .execute(&self.pool) - .await?; - if res.rows_affected() == 0 { - return Err(anyhow!("Specified repository does not exist")); + if res.rows_affected() != 1 { + return Err(anyhow!( + "The specified Github repository provider does not exist" + )); } - Ok(()) - } - pub async fn list_github_provided_repositories( - &self, - github_repository_provider_ids: Vec, - limit: Option, - skip_id: Option, - backwards: bool, - ) -> Result> { - let ids: Vec<_> = github_repository_provider_ids - .into_iter() - .map(|id| id.to_string()) - .collect(); - let ids = format!("({})", ids.join(", ")); - let repos = query_paged_as!( - GithubProvidedRepositoryDAO, - "github_provided_repositories", - [ - "github_repository_provider_id", - "vendor_id", - "git_url", - "name" - ], - limit, - skip_id, - backwards, - Some(format!("github_repository_provider_id IN {ids}")) - ) - .fetch_all(&self.pool) - .await?; - Ok(repos) + Ok(()) } } diff --git a/ee/tabby-webserver/src/handler.rs b/ee/tabby-webserver/src/handler.rs index 31dbe01d9ded..491cfe0e3cba 100644 --- a/ee/tabby-webserver/src/handler.rs +++ b/ee/tabby-webserver/src/handler.rs @@ -82,7 +82,12 @@ impl WebserverHandle { ) .nest( "/repositories", - repositories::routes(rs.clone(), ctx.auth()), + repositories::routes( + rs.clone(), + ctx.auth(), + ctx.setting(), + ctx.github_repository_provider(), + ), ) .route( "/avatar/:id", diff --git a/ee/tabby-webserver/src/oauth/github.rs b/ee/tabby-webserver/src/oauth/github.rs index c7faefe57d8c..32d9c7e598ed 100644 --- a/ee/tabby-webserver/src/oauth/github.rs +++ b/ee/tabby-webserver/src/oauth/github.rs @@ -9,20 +9,20 @@ use crate::schema::auth::{AuthenticationService, OAuthCredential, OAuthProvider} #[derive(Debug, Deserialize)] #[allow(dead_code)] -struct GithubOAuthResponse { +pub struct GithubOAuthResponse { #[serde(default)] - access_token: String, + pub access_token: String, #[serde(default)] - scope: String, + pub scope: String, #[serde(default)] - token_type: String, + pub token_type: String, #[serde(default)] - error: String, + pub error: String, #[serde(default)] - error_description: String, + pub error_description: String, #[serde(default)] - error_uri: String, + pub error_uri: String, } #[derive(Debug, Deserialize)] diff --git a/ee/tabby-webserver/src/repositories/mod.rs b/ee/tabby-webserver/src/repositories/mod.rs index 3fd02745e521..2387bf1697e2 100644 --- a/ee/tabby-webserver/src/repositories/mod.rs +++ b/ee/tabby-webserver/src/repositories/mod.rs @@ -1,3 +1,4 @@ +mod oauth; mod resolve; use std::sync::Arc; @@ -6,7 +7,6 @@ use anyhow::Result; use axum::{ extract::{Path, State}, http::StatusCode, - middleware::from_fn_with_state, response::Response, routing, Json, Router, }; @@ -14,14 +14,27 @@ pub use resolve::RepositoryCache; use tracing::{instrument, warn}; use crate::{ - handler::require_login_middleware, repositories::resolve::{RepositoryMeta, ResolveParams}, - schema::auth::AuthenticationService, + schema::{ + auth::AuthenticationService, github_repository_provider::GithubRepositoryProviderService, + setting::SettingService, + }, }; +use self::oauth::OAuthState; + pub type ResolveState = Arc; -pub fn routes(rs: Arc, auth: Arc) -> Router { +pub fn routes( + rs: Arc, + auth: Arc, + settings: Arc, + github_repository_provider: Arc, +) -> Router { + let oauth_state = OAuthState { + settings, + github_repository_provider, + }; Router::new() .route("/resolve", routing::get(resolve)) .route("/resolve/", routing::get(resolve)) @@ -32,8 +45,9 @@ pub fn routes(rs: Arc, auth: Arc) -> Ro .route("/:name/meta/", routing::get(meta)) .route("/:name/meta/*path", routing::get(meta)) .with_state(rs.clone()) + .nest("/oauth", oauth::routes(oauth_state)) .fallback(not_found) - .layer(from_fn_with_state(auth, require_login_middleware)) + // .layer(from_fn_with_state(auth, require_login_middleware)) } async fn not_found() -> StatusCode { diff --git a/ee/tabby-webserver/src/repositories/oauth.rs b/ee/tabby-webserver/src/repositories/oauth.rs new file mode 100644 index 000000000000..8b0b974887d6 --- /dev/null +++ b/ee/tabby-webserver/src/repositories/oauth.rs @@ -0,0 +1,120 @@ +use anyhow::Result; +use std::sync::Arc; + +use axum::{ + extract::{Path, Query, State}, + response::Redirect, + routing, Router, +}; +use hyper::StatusCode; +use juniper::ID; +use serde::Deserialize; + +use crate::{ + oauth::github::GithubOAuthResponse, + schema::{ + github_repository_provider::GithubRepositoryProviderService, setting::SettingService, + }, +}; + +#[derive(Clone)] +pub struct OAuthState { + pub settings: Arc, + pub github_repository_provider: Arc, +} + +pub fn routes(state: OAuthState) -> Router { + Router::new() + .route("/github/login/:id", routing::get(login)) + .route("/github/callback", routing::get(callback)) + .with_state(state) +} + +fn github_redirect_url(client_id: &str, redirect_uri: &str, id: &ID) -> String { + format!("https://github.com/login/oauth/authorize?client_id={client_id}&response_type=code&scope=repo&redirect_uri={redirect_uri}/repositories/oauth/github/callback&state={id}") +} + +#[derive(Deserialize)] +struct CallbackParams { + state: ID, + code: String, +} + +macro_rules! log_error { + ($val:expr) => { + $val.map_err(|e| { + tracing::error!("{e}"); + StatusCode::INTERNAL_SERVER_ERROR + }) + }; +} + +async fn exchange_access_token( + state: &OAuthState, + params: &CallbackParams, +) -> Result { + let client = reqwest::Client::new(); + + let client_id = state + .github_repository_provider + .get_github_repository_provider(params.state.clone()) + .await? + .application_id; + + let secret = state + .github_repository_provider + .read_github_repository_provider_secret(params.state.clone()) + .await?; + + Ok(client + .post("https://github.com/login/oauth/access_token") + .header(reqwest::header::ACCEPT, "application/json") + .form(&[ + ("client_id", &client_id), + ("client_secret", &secret), + ("code", ¶ms.code), + ]) + .send() + .await? + .json() + .await?) +} + +async fn callback( + State(state): State, + Query(params): Query, +) -> Result { + let network_setting = log_error!(state.settings.read_network_setting().await)?; + let external_url = network_setting.external_url; + + let response = log_error!(exchange_access_token(&state, ¶ms).await)?; + dbg!(&response); + log_error!( + state + .github_repository_provider + .set_github_repository_provider_token(params.state, response.access_token) + .await + )?; + + Ok(Redirect::permanent(&external_url)) +} + +async fn login( + State(state): State, + Path(id): Path, +) -> Result { + let network_setting = log_error!(state.settings.read_network_setting().await)?; + let external_url = network_setting.external_url; + let client_id = log_error!( + state + .github_repository_provider + .get_github_repository_provider(id.clone()) + .await + )? + .application_id; + Ok(Redirect::temporary(&github_redirect_url( + &client_id, + &external_url, + &id, + ))) +} From 0623d090c3e6fc73876ede0bc382649e657a2c58 Mon Sep 17 00:00:00 2001 From: boxbeam Date: Tue, 9 Apr 2024 10:54:53 -0400 Subject: [PATCH 11/18] Resolve conflicts --- ee/tabby-db/schema.sqlite | Bin 131072 -> 139264 bytes ee/tabby-webserver/src/schema/mod.rs | 8 +++----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/ee/tabby-db/schema.sqlite b/ee/tabby-db/schema.sqlite index b11dc9144c9d766a36cf6b06ecfb83501bc25064..e7846cb398f045f19946a01ebdd383f102b0d513 100644 GIT binary patch delta 1164 zcmZ|OZ%7ki90%}wym>dzZFiMz?vLK7g|2Cy)3Su3K}LyWk;2sKHr?fx%Z<7Ih-6|? zgdx^X8d0I`MS(9Oy=qYr)r;yy(SL&ez6c^MdQ}t{*`pEeARq2H_&mSo`#g8|Jl);e z?pxXcUY|?MHrr_7GgdoKkHtt21I;;7=wZdh1!-xUkw%Wvv><702}J$7n~K7b(4K%# zjyOeUS&@jZl|o5b#j?_hva<83c`fYVlhatm*9-Fl{!8`vbAN-e9;QqM%xnUbWQye0*j4al3bR}a{g~bMLAafnoy_By8}s~N zGuxB7#w?QI76bH{^OIS@T>st%|L+@{L*PIH?D25n1{L+)dCPjtehO%<{v z3xBFJL$EDNwUWb_-K|w{ElU;h6?6Tj49Lk=Of?Uy8`#PIqyMdK zZkF5IrD&*CZky>Z1W{*ns1iIv-Np@@>neo;k2>08XY6_fbx#$?2e|lv7+=GUtBdkL#|A|vC{ib@hdDu<|1PrP zDl!D=_A69H5Q|=V&`HFAh)!haWiU|jpp$_h?&4{lGFD%PVDfmp!-pT=_l5UL^K7Za zRM|_Hx4WAJ zG*WPA3HE88XE@{Vlnr+^&y|as!AT1Xob%-2GGM*aY%}uVsdCCHr_eQh3$`iVSD-?- z!d6fS9pqp83Nu%K%*>6NClHVP*!B$`NdjVJn5P5gEIo(R@_9cNMG;N!zc_Mwym)D% zcwu7m!2S6HR^ceR&-gfv>am)~&{mqJ57)LGN3UiB8#Q4VB@hP&AAxhN*i`rxCd)ei z)A^EZRF(?A3+4DF?icrzyWFbcH-4!l$piBkRRe2Ole$Mn{w*tW3ccVqOV-xTqHorp dJQ_o0SIaa48~31~iAv2>y&}{rM!i=c`!5N^&9eXi diff --git a/ee/tabby-webserver/src/schema/mod.rs b/ee/tabby-webserver/src/schema/mod.rs index 3629da60c48b..e8932155b92b 100644 --- a/ee/tabby-webserver/src/schema/mod.rs +++ b/ee/tabby-webserver/src/schema/mod.rs @@ -30,6 +30,8 @@ use tracing::error; use validator::{Validate, ValidationErrors}; use worker::{Worker, WorkerService}; +use crate::schema::repository::FileEntrySearchResult; + use self::{ analytic::{AnalyticService, CompletionStats}, auth::{ @@ -37,18 +39,14 @@ use self::{ RequestInvitationInput, RequestPasswordResetEmailInput, UpdateOAuthCredentialInput, }, email::{EmailService, EmailSetting, EmailSettingInput}, - job::JobStats, github_repository_provider::GithubRepositoryProviderService, + job::JobStats, license::{IsLicenseValid, LicenseInfo, LicenseService, LicenseType}, repository::{Repository, RepositoryService}, setting::{ NetworkSetting, NetworkSettingInput, SecuritySetting, SecuritySettingInput, SettingService, }, }; -use crate::schema::{ - auth::{JWTPayload, OAuthCredential, OAuthProvider}, - job::JobStats, -}; pub trait ServiceLocator: Send + Sync { fn auth(&self) -> Arc; From 83a31e4f92175c09f33449f14fdb0114a1520bfc Mon Sep 17 00:00:00 2001 From: boxbeam Date: Tue, 9 Apr 2024 10:58:05 -0400 Subject: [PATCH 12/18] Fixup --- ee/tabby-db/migrations/0022_github-provider.down.sql | 1 - ee/tabby-webserver/src/repositories/mod.rs | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ee/tabby-db/migrations/0022_github-provider.down.sql b/ee/tabby-db/migrations/0022_github-provider.down.sql index 2ada5c2aaf31..2af049ced3da 100644 --- a/ee/tabby-db/migrations/0022_github-provider.down.sql +++ b/ee/tabby-db/migrations/0022_github-provider.down.sql @@ -1,2 +1 @@ DROP TABLE github_repository_provider; -DROP TABLE github_provided_repositories; diff --git a/ee/tabby-webserver/src/repositories/mod.rs b/ee/tabby-webserver/src/repositories/mod.rs index 2387bf1697e2..82dcf3aec578 100644 --- a/ee/tabby-webserver/src/repositories/mod.rs +++ b/ee/tabby-webserver/src/repositories/mod.rs @@ -7,6 +7,7 @@ use anyhow::Result; use axum::{ extract::{Path, State}, http::StatusCode, + middleware::from_fn_with_state, response::Response, routing, Json, Router, }; @@ -14,6 +15,7 @@ pub use resolve::RepositoryCache; use tracing::{instrument, warn}; use crate::{ + handler::require_login_middleware, repositories::resolve::{RepositoryMeta, ResolveParams}, schema::{ auth::AuthenticationService, github_repository_provider::GithubRepositoryProviderService, @@ -47,7 +49,7 @@ pub fn routes( .with_state(rs.clone()) .nest("/oauth", oauth::routes(oauth_state)) .fallback(not_found) - // .layer(from_fn_with_state(auth, require_login_middleware)) + .layer(from_fn_with_state(auth, require_login_middleware)) } async fn not_found() -> StatusCode { From 86d2973c7ed6033deae549877bfaeff35ce4ed26 Mon Sep 17 00:00:00 2001 From: boxbeam Date: Tue, 9 Apr 2024 11:51:20 -0400 Subject: [PATCH 13/18] Apply suggestions, add GraphQL endpoints --- ee/tabby-db/src/github_repository_provider.rs | 45 +++++-- ee/tabby-db/src/lib.rs | 1 + ee/tabby-webserver/src/handler.rs | 13 +- ee/tabby-webserver/src/integrations.rs | 34 +++++ ee/tabby-webserver/src/integrations/github.rs | 119 +++++++++++++++++ ee/tabby-webserver/src/lib.rs | 1 + ee/tabby-webserver/src/repositories/mod.rs | 20 +-- ee/tabby-webserver/src/repositories/oauth.rs | 120 ------------------ .../src/schema/github_repository_provider.rs | 33 ++++- ee/tabby-webserver/src/schema/mod.rs | 26 +++- ee/tabby-webserver/src/service/dao.rs | 15 ++- .../src/service/github_repository_provider.rs | 29 ++++- 12 files changed, 289 insertions(+), 167 deletions(-) create mode 100644 ee/tabby-webserver/src/integrations.rs create mode 100644 ee/tabby-webserver/src/integrations/github.rs delete mode 100644 ee/tabby-webserver/src/repositories/oauth.rs diff --git a/ee/tabby-db/src/github_repository_provider.rs b/ee/tabby-db/src/github_repository_provider.rs index c9d557c849e4..b2741d238650 100644 --- a/ee/tabby-db/src/github_repository_provider.rs +++ b/ee/tabby-db/src/github_repository_provider.rs @@ -1,20 +1,16 @@ use anyhow::{anyhow, Result}; use sqlx::{prelude::FromRow, query, query_as}; +use tabby_db_macros::query_paged_as; use crate::{DbConn, SQLXResultExt}; +#[derive(FromRow)] pub struct GithubRepositoryProviderDAO { + pub id: i64, pub display_name: String, pub application_id: String, pub secret: String, -} - -#[derive(FromRow)] -pub struct GithubProvidedRepositoryDAO { - pub github_repository_provider_id: i64, - pub vendor_id: String, - pub name: String, - pub git_url: String, + pub access_token: Option, } impl DbConn { @@ -35,7 +31,7 @@ impl DbConn { pub async fn get_github_provider(&self, id: i64) -> Result { let provider = query_as!( GithubRepositoryProviderDAO, - "SELECT display_name, application_id, secret FROM github_repository_provider WHERE id = ?;", + "SELECT id, display_name, application_id, secret, access_token FROM github_repository_provider WHERE id = ?;", id ) .fetch_one(&self.pool) @@ -53,7 +49,11 @@ impl DbConn { Ok(()) } - pub async fn update_github_provider_token(&self, id: i64, access_token: String) -> Result<()> { + pub async fn update_github_provider_access_token( + &self, + id: i64, + access_token: String, + ) -> Result<()> { let res = query!( "UPDATE github_repository_provider SET access_token = ? WHERE id = ?", access_token, @@ -70,4 +70,29 @@ impl DbConn { Ok(()) } + + pub async fn list_github_repository_providers( + &self, + limit: Option, + skip_id: Option, + backwards: bool, + ) -> Result> { + let providers = query_paged_as!( + GithubRepositoryProviderDAO, + "github_repository_provider", + [ + "id", + "display_name", + "application_id", + "secret", + "access_token" + ], + limit, + skip_id, + backwards + ) + .fetch_all(&self.pool) + .await?; + Ok(providers) + } } diff --git a/ee/tabby-db/src/lib.rs b/ee/tabby-db/src/lib.rs index a373384069ee..5c4c4422b47c 100644 --- a/ee/tabby-db/src/lib.rs +++ b/ee/tabby-db/src/lib.rs @@ -4,6 +4,7 @@ use anyhow::anyhow; use cache::Cache; use chrono::{DateTime, NaiveDateTime, Utc}; pub use email_setting::EmailSettingDAO; +pub use github_repository_provider::GithubRepositoryProviderDAO; pub use invitations::InvitationDAO; pub use job_runs::JobRunDAO; pub use oauth_credential::OAuthCredentialDAO; diff --git a/ee/tabby-webserver/src/handler.rs b/ee/tabby-webserver/src/handler.rs index 491cfe0e3cba..60ae0bb2e90c 100644 --- a/ee/tabby-webserver/src/handler.rs +++ b/ee/tabby-webserver/src/handler.rs @@ -19,7 +19,7 @@ use tabby_db::DbConn; use tracing::{error, warn}; use crate::{ - cron, hub, oauth, + cron, hub, integrations, oauth, repositories::{self, RepositoryCache}, schema::{auth::AuthenticationService, create_schema, Schema, ServiceLocator}, service::{create_service_locator, event_logger::create_event_logger}, @@ -82,12 +82,11 @@ impl WebserverHandle { ) .nest( "/repositories", - repositories::routes( - rs.clone(), - ctx.auth(), - ctx.setting(), - ctx.github_repository_provider(), - ), + repositories::routes(rs.clone(), ctx.auth()), + ) + .nest( + "/integrations", + integrations::routes(ctx.setting(), ctx.github_repository_provider()), ) .route( "/avatar/:id", diff --git a/ee/tabby-webserver/src/integrations.rs b/ee/tabby-webserver/src/integrations.rs new file mode 100644 index 000000000000..2a7ee1aba93f --- /dev/null +++ b/ee/tabby-webserver/src/integrations.rs @@ -0,0 +1,34 @@ +use std::sync::Arc; + +use axum::Router; +use juniper::ID; +use serde::Deserialize; + +use crate::schema::{ + github_repository_provider::GithubRepositoryProviderService, setting::SettingService, +}; + +mod github; + +#[derive(Clone)] +struct IntegrationsState { + pub settings: Arc, + pub github_repository_provider: Arc, +} + +pub fn routes( + settings: Arc, + github_repository_provider: Arc, +) -> Router { + let state = IntegrationsState { + settings, + github_repository_provider, + }; + Router::new().nest("/github", github::routes(state)) +} + +#[derive(Deserialize)] +struct CallbackParams { + state: ID, + code: String, +} diff --git a/ee/tabby-webserver/src/integrations/github.rs b/ee/tabby-webserver/src/integrations/github.rs new file mode 100644 index 000000000000..5485428d1c2b --- /dev/null +++ b/ee/tabby-webserver/src/integrations/github.rs @@ -0,0 +1,119 @@ +use anyhow::Result; +use axum::{ + extract::{Path, Query, State}, + response::Redirect, + routing, Router, +}; +use hyper::StatusCode; +use juniper::ID; +use tracing::error; + +use crate::oauth::github::GithubOAuthResponse; + +use super::{CallbackParams, IntegrationsState}; + +pub fn routes(state: IntegrationsState) -> Router { + Router::new() + .route("/connect/:id", routing::get(connect)) + .route("/callback", routing::get(callback)) + .with_state(state) +} + +fn github_redirect_url(client_id: &str, redirect_uri: &str, id: &ID) -> String { + format!("https://github.com/login/oauth/authorize?client_id={client_id}&response_type=code&scope=repo&redirect_uri={redirect_uri}/integrations/github/callback&state={id}") +} + +async fn exchange_access_token( + state: &IntegrationsState, + params: &CallbackParams, +) -> Result { + let client = reqwest::Client::new(); + + let client_id = state + .github_repository_provider + .get_github_repository_provider(params.state.clone()) + .await? + .application_id; + + let secret = state + .github_repository_provider + .read_github_repository_provider_secret(params.state.clone()) + .await?; + + Ok(client + .post("https://github.com/login/oauth/access_token") + .header(reqwest::header::ACCEPT, "application/json") + .form(&[ + ("client_id", &client_id), + ("client_secret", &secret), + ("code", ¶ms.code), + ]) + .send() + .await? + .json() + .await?) +} + +async fn callback( + State(state): State, + Query(params): Query, +) -> Result { + let network_setting = match state.settings.read_network_setting().await { + Ok(setting) => setting, + Err(e) => { + error!("Failed to read network setting: {e}"); + return Err(StatusCode::INTERNAL_SERVER_ERROR); + } + }; + let external_url = network_setting.external_url; + + let response = match exchange_access_token(&state, ¶ms).await { + Ok(response) => response, + Err(e) => { + error!("Failed to exchange access token: {e}"); + return Err(StatusCode::INTERNAL_SERVER_ERROR); + } + }; + + if let Err(e) = state + .github_repository_provider + .update_github_repository_provider_access_token(params.state, response.access_token) + .await + { + error!("Failed to update github access token: {e}"); + return Err(StatusCode::INTERNAL_SERVER_ERROR); + } + + Ok(Redirect::permanent(&external_url)) +} + +async fn connect( + State(state): State, + Path(id): Path, +) -> Result { + let network_setting = match state.settings.read_network_setting().await { + Ok(setting) => setting, + Err(e) => { + error!("Failed to read network setting: {e}"); + return Err(StatusCode::INTERNAL_SERVER_ERROR); + } + }; + let external_url = network_setting.external_url; + let provider = match state + .github_repository_provider + .get_github_repository_provider(id.clone()) + .await + { + Ok(provider) => provider, + Err(e) => { + error!("Github repository provider not found: {e}"); + return Err(StatusCode::NOT_FOUND); + } + }; + + Ok(Redirect::temporary(&github_redirect_url( + &provider.application_id, + &external_url, + &id, + ))) +} diff --git a/ee/tabby-webserver/src/lib.rs b/ee/tabby-webserver/src/lib.rs index d20b7d053cdc..f2c0ee9f51dc 100644 --- a/ee/tabby-webserver/src/lib.rs +++ b/ee/tabby-webserver/src/lib.rs @@ -3,6 +3,7 @@ mod cron; mod handler; mod hub; +mod integrations; mod oauth; mod repositories; mod schema; diff --git a/ee/tabby-webserver/src/repositories/mod.rs b/ee/tabby-webserver/src/repositories/mod.rs index 82dcf3aec578..3fd02745e521 100644 --- a/ee/tabby-webserver/src/repositories/mod.rs +++ b/ee/tabby-webserver/src/repositories/mod.rs @@ -1,4 +1,3 @@ -mod oauth; mod resolve; use std::sync::Arc; @@ -17,26 +16,12 @@ use tracing::{instrument, warn}; use crate::{ handler::require_login_middleware, repositories::resolve::{RepositoryMeta, ResolveParams}, - schema::{ - auth::AuthenticationService, github_repository_provider::GithubRepositoryProviderService, - setting::SettingService, - }, + schema::auth::AuthenticationService, }; -use self::oauth::OAuthState; - pub type ResolveState = Arc; -pub fn routes( - rs: Arc, - auth: Arc, - settings: Arc, - github_repository_provider: Arc, -) -> Router { - let oauth_state = OAuthState { - settings, - github_repository_provider, - }; +pub fn routes(rs: Arc, auth: Arc) -> Router { Router::new() .route("/resolve", routing::get(resolve)) .route("/resolve/", routing::get(resolve)) @@ -47,7 +32,6 @@ pub fn routes( .route("/:name/meta/", routing::get(meta)) .route("/:name/meta/*path", routing::get(meta)) .with_state(rs.clone()) - .nest("/oauth", oauth::routes(oauth_state)) .fallback(not_found) .layer(from_fn_with_state(auth, require_login_middleware)) } diff --git a/ee/tabby-webserver/src/repositories/oauth.rs b/ee/tabby-webserver/src/repositories/oauth.rs deleted file mode 100644 index 8b0b974887d6..000000000000 --- a/ee/tabby-webserver/src/repositories/oauth.rs +++ /dev/null @@ -1,120 +0,0 @@ -use anyhow::Result; -use std::sync::Arc; - -use axum::{ - extract::{Path, Query, State}, - response::Redirect, - routing, Router, -}; -use hyper::StatusCode; -use juniper::ID; -use serde::Deserialize; - -use crate::{ - oauth::github::GithubOAuthResponse, - schema::{ - github_repository_provider::GithubRepositoryProviderService, setting::SettingService, - }, -}; - -#[derive(Clone)] -pub struct OAuthState { - pub settings: Arc, - pub github_repository_provider: Arc, -} - -pub fn routes(state: OAuthState) -> Router { - Router::new() - .route("/github/login/:id", routing::get(login)) - .route("/github/callback", routing::get(callback)) - .with_state(state) -} - -fn github_redirect_url(client_id: &str, redirect_uri: &str, id: &ID) -> String { - format!("https://github.com/login/oauth/authorize?client_id={client_id}&response_type=code&scope=repo&redirect_uri={redirect_uri}/repositories/oauth/github/callback&state={id}") -} - -#[derive(Deserialize)] -struct CallbackParams { - state: ID, - code: String, -} - -macro_rules! log_error { - ($val:expr) => { - $val.map_err(|e| { - tracing::error!("{e}"); - StatusCode::INTERNAL_SERVER_ERROR - }) - }; -} - -async fn exchange_access_token( - state: &OAuthState, - params: &CallbackParams, -) -> Result { - let client = reqwest::Client::new(); - - let client_id = state - .github_repository_provider - .get_github_repository_provider(params.state.clone()) - .await? - .application_id; - - let secret = state - .github_repository_provider - .read_github_repository_provider_secret(params.state.clone()) - .await?; - - Ok(client - .post("https://github.com/login/oauth/access_token") - .header(reqwest::header::ACCEPT, "application/json") - .form(&[ - ("client_id", &client_id), - ("client_secret", &secret), - ("code", ¶ms.code), - ]) - .send() - .await? - .json() - .await?) -} - -async fn callback( - State(state): State, - Query(params): Query, -) -> Result { - let network_setting = log_error!(state.settings.read_network_setting().await)?; - let external_url = network_setting.external_url; - - let response = log_error!(exchange_access_token(&state, ¶ms).await)?; - dbg!(&response); - log_error!( - state - .github_repository_provider - .set_github_repository_provider_token(params.state, response.access_token) - .await - )?; - - Ok(Redirect::permanent(&external_url)) -} - -async fn login( - State(state): State, - Path(id): Path, -) -> Result { - let network_setting = log_error!(state.settings.read_network_setting().await)?; - let external_url = network_setting.external_url; - let client_id = log_error!( - state - .github_repository_provider - .get_github_repository_provider(id.clone()) - .await - )? - .application_id; - Ok(Redirect::temporary(&github_redirect_url( - &client_id, - &external_url, - &id, - ))) -} diff --git a/ee/tabby-webserver/src/schema/github_repository_provider.rs b/ee/tabby-webserver/src/schema/github_repository_provider.rs index 5379a6281b35..b674f5cdc900 100644 --- a/ee/tabby-webserver/src/schema/github_repository_provider.rs +++ b/ee/tabby-webserver/src/schema/github_repository_provider.rs @@ -1,20 +1,49 @@ use crate::schema::Result; use async_trait::async_trait; use juniper::{GraphQLObject, ID}; +use juniper_axum::relay::NodeType; -#[derive(GraphQLObject)] +use super::Context; + +#[derive(GraphQLObject, Debug)] +#[graphql(context = Context)] pub struct GithubRepositoryProvider { + pub id: ID, pub display_name: String, pub application_id: String, } +impl NodeType for GithubRepositoryProvider { + type Cursor = String; + + fn cursor(&self) -> Self::Cursor { + self.id.to_string() + } + + fn connection_type_name() -> &'static str { + "GithubRepositoryProviderConnection" + } + + fn edge_type_name() -> &'static str { + "GithubRepositoryProviderEdge" + } +} + #[async_trait] pub trait GithubRepositoryProviderService: Send + Sync { async fn get_github_repository_provider(&self, id: ID) -> Result; async fn read_github_repository_provider_secret(&self, id: ID) -> Result; - async fn set_github_repository_provider_token( + async fn update_github_repository_provider_access_token( &self, id: ID, access_token: String, ) -> Result<()>; + + async fn list_github_repository_providers( + &self, + after: Option, + before: Option, + first: Option, + last: Option, + ) -> Result>; } diff --git a/ee/tabby-webserver/src/schema/mod.rs b/ee/tabby-webserver/src/schema/mod.rs index e8932155b92b..aa74ba206522 100644 --- a/ee/tabby-webserver/src/schema/mod.rs +++ b/ee/tabby-webserver/src/schema/mod.rs @@ -39,7 +39,7 @@ use self::{ RequestInvitationInput, RequestPasswordResetEmailInput, UpdateOAuthCredentialInput, }, email::{EmailService, EmailSetting, EmailSettingInput}, - github_repository_provider::GithubRepositoryProviderService, + github_repository_provider::{GithubRepositoryProvider, GithubRepositoryProviderService}, job::JobStats, license::{IsLicenseValid, LicenseInfo, LicenseService, LicenseType}, repository::{Repository, RepositoryService}, @@ -231,6 +231,30 @@ impl Query { .await } + async fn github_repository_providers( + ctx: &Context, + after: Option, + before: Option, + first: Option, + last: Option, + ) -> FieldResult> { + check_admin(ctx).await?; + relay::query_async( + after, + before, + first, + last, + |after, before, first, last| async move { + Ok(ctx + .locator + .github_repository_provider() + .list_github_repository_providers(after, before, first, last) + .await?) + }, + ) + .await + } + async fn job_runs( ctx: &Context, ids: Option>, diff --git a/ee/tabby-webserver/src/service/dao.rs b/ee/tabby-webserver/src/service/dao.rs index 4260f4dc6cbe..402e973540d3 100644 --- a/ee/tabby-webserver/src/service/dao.rs +++ b/ee/tabby-webserver/src/service/dao.rs @@ -2,13 +2,14 @@ use anyhow::anyhow; use hash_ids::HashIds; use lazy_static::lazy_static; use tabby_db::{ - EmailSettingDAO, InvitationDAO, JobRunDAO, OAuthCredentialDAO, RepositoryDAO, ServerSettingDAO, - UserDAO, + EmailSettingDAO, GithubRepositoryProviderDAO, InvitationDAO, JobRunDAO, OAuthCredentialDAO, + RepositoryDAO, ServerSettingDAO, UserDAO, }; use crate::schema::{ auth::{self, OAuthCredential, OAuthProvider}, email::{AuthMethod, EmailSetting, Encryption}, + github_repository_provider::GithubRepositoryProvider, job, repository::Repository, setting::{NetworkSetting, SecuritySetting}, @@ -119,6 +120,16 @@ impl From for NetworkSetting { } } +impl From for GithubRepositoryProvider { + fn from(value: GithubRepositoryProviderDAO) -> Self { + Self { + display_name: value.display_name, + application_id: value.application_id, + id: value.id.as_id(), + } + } +} + lazy_static! { static ref HASHER: HashIds = HashIds::builder() .with_salt("tabby-id-serializer") diff --git a/ee/tabby-webserver/src/service/github_repository_provider.rs b/ee/tabby-webserver/src/service/github_repository_provider.rs index 1bd5582f478a..802c6f19fe7e 100644 --- a/ee/tabby-webserver/src/service/github_repository_provider.rs +++ b/ee/tabby-webserver/src/service/github_repository_provider.rs @@ -1,4 +1,4 @@ -use crate::schema::Result; +use crate::{schema::Result, service::graphql_pagination_to_filter}; use async_trait::async_trait; use juniper::ID; use tabby_db::DbConn; @@ -21,10 +21,7 @@ pub fn new_github_repository_provider_service(db: DbConn) -> impl GithubReposito impl GithubRepositoryProviderService for GithubRepositoryProviderServiceImpl { async fn get_github_repository_provider(&self, id: ID) -> Result { let provider = self.db.get_github_provider(id.as_rowid()? as i64).await?; - Ok(GithubRepositoryProvider { - display_name: provider.display_name, - application_id: provider.application_id, - }) + Ok(provider.into()) } async fn read_github_repository_provider_secret(&self, id: ID) -> Result { @@ -32,14 +29,32 @@ impl GithubRepositoryProviderService for GithubRepositoryProviderServiceImpl { Ok(provider.secret) } - async fn set_github_repository_provider_token( + async fn update_github_repository_provider_access_token( &self, id: ID, access_token: String, ) -> Result<()> { self.db - .update_github_provider_token(id.as_rowid()? as i64, access_token) + .update_github_provider_access_token(id.as_rowid()? as i64, access_token) .await?; Ok(()) } + + async fn list_github_repository_providers( + &self, + after: Option, + before: Option, + first: Option, + last: Option, + ) -> Result> { + let (limit, skip_id, backwards) = graphql_pagination_to_filter(after, before, first, last)?; + let providers = self + .db + .list_github_repository_providers(limit, skip_id, backwards) + .await?; + Ok(providers + .into_iter() + .map(GithubRepositoryProvider::from) + .collect()) + } } From cc612aa42e700cbb448b4249c5b58080ac32d435 Mon Sep 17 00:00:00 2001 From: boxbeam Date: Tue, 9 Apr 2024 11:58:20 -0400 Subject: [PATCH 14/18] Restructure routes --- ee/tabby-webserver/src/handler.rs | 4 +- ee/tabby-webserver/src/integrations.rs | 35 +----------------- ee/tabby-webserver/src/integrations/github.rs | 37 ++++++++++++++++--- 3 files changed, 34 insertions(+), 42 deletions(-) diff --git a/ee/tabby-webserver/src/handler.rs b/ee/tabby-webserver/src/handler.rs index 60ae0bb2e90c..8ac832f6edb2 100644 --- a/ee/tabby-webserver/src/handler.rs +++ b/ee/tabby-webserver/src/handler.rs @@ -85,8 +85,8 @@ impl WebserverHandle { repositories::routes(rs.clone(), ctx.auth()), ) .nest( - "/integrations", - integrations::routes(ctx.setting(), ctx.github_repository_provider()), + "/integrations/github", + integrations::github::routes(ctx.setting(), ctx.github_repository_provider()), ) .route( "/avatar/:id", diff --git a/ee/tabby-webserver/src/integrations.rs b/ee/tabby-webserver/src/integrations.rs index 2a7ee1aba93f..72246d32d261 100644 --- a/ee/tabby-webserver/src/integrations.rs +++ b/ee/tabby-webserver/src/integrations.rs @@ -1,34 +1 @@ -use std::sync::Arc; - -use axum::Router; -use juniper::ID; -use serde::Deserialize; - -use crate::schema::{ - github_repository_provider::GithubRepositoryProviderService, setting::SettingService, -}; - -mod github; - -#[derive(Clone)] -struct IntegrationsState { - pub settings: Arc, - pub github_repository_provider: Arc, -} - -pub fn routes( - settings: Arc, - github_repository_provider: Arc, -) -> Router { - let state = IntegrationsState { - settings, - github_repository_provider, - }; - Router::new().nest("/github", github::routes(state)) -} - -#[derive(Deserialize)] -struct CallbackParams { - state: ID, - code: String, -} +pub mod github; diff --git a/ee/tabby-webserver/src/integrations/github.rs b/ee/tabby-webserver/src/integrations/github.rs index 5485428d1c2b..632fdcb7f6e3 100644 --- a/ee/tabby-webserver/src/integrations/github.rs +++ b/ee/tabby-webserver/src/integrations/github.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use anyhow::Result; use axum::{ extract::{Path, Query, State}, @@ -6,13 +8,36 @@ use axum::{ }; use hyper::StatusCode; use juniper::ID; +use serde::Deserialize; use tracing::error; -use crate::oauth::github::GithubOAuthResponse; +use crate::{ + oauth::github::GithubOAuthResponse, + schema::{ + github_repository_provider::GithubRepositoryProviderService, setting::SettingService, + }, +}; + +#[derive(Deserialize)] +struct CallbackParams { + state: ID, + code: String, +} -use super::{CallbackParams, IntegrationsState}; +#[derive(Clone)] +struct IntegrationState { + pub settings: Arc, + pub github_repository_provider: Arc, +} -pub fn routes(state: IntegrationsState) -> Router { +pub fn routes( + settings: Arc, + github_repository_provider: Arc, +) -> Router { + let state = IntegrationState { + settings, + github_repository_provider, + }; Router::new() .route("/connect/:id", routing::get(connect)) .route("/callback", routing::get(callback)) @@ -24,7 +49,7 @@ fn github_redirect_url(client_id: &str, redirect_uri: &str, id: &ID) -> String { } async fn exchange_access_token( - state: &IntegrationsState, + state: &IntegrationState, params: &CallbackParams, ) -> Result { let client = reqwest::Client::new(); @@ -55,7 +80,7 @@ async fn exchange_access_token( } async fn callback( - State(state): State, + State(state): State, Query(params): Query, ) -> Result { let network_setting = match state.settings.read_network_setting().await { @@ -88,7 +113,7 @@ async fn callback( } async fn connect( - State(state): State, + State(state): State, Path(id): Path, ) -> Result { let network_setting = match state.settings.read_network_setting().await { From 5387b8cdb5fef79c4eddc939b68ac6fc587d437e Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 9 Apr 2024 17:05:34 +0000 Subject: [PATCH 15/18] [autofix.ci] apply automated fixes --- ee/tabby-webserver/graphql/schema.graphql | 17 +++++++++++++++++ .../src/schema/github_repository_provider.rs | 2 +- ee/tabby-webserver/src/schema/mod.rs | 3 +-- .../src/service/github_repository_provider.rs | 18 ++++++++++-------- 4 files changed, 29 insertions(+), 11 deletions(-) diff --git a/ee/tabby-webserver/graphql/schema.graphql b/ee/tabby-webserver/graphql/schema.graphql index 5aa4cf4d2bd8..23ef1a79eb1e 100644 --- a/ee/tabby-webserver/graphql/schema.graphql +++ b/ee/tabby-webserver/graphql/schema.graphql @@ -3,6 +3,11 @@ enum Language { PYTHON } +type GithubRepositoryProviderEdge { + node: GithubRepositoryProvider! + cursor: String! +} + type JobRun { id: ID! job: String! @@ -44,6 +49,11 @@ type LicenseInfo { expiresAt: DateTimeUtc } +type GithubRepositoryProviderConnection { + edges: [GithubRepositoryProviderEdge!]! + pageInfo: PageInfo! +} + input SecuritySettingInput { allowedRegisterDomainList: [String!]! disableClientSideTelemetry: Boolean! @@ -72,6 +82,12 @@ type ServerInfo { allowSelfSignup: Boolean! } +type GithubRepositoryProvider { + id: ID! + displayName: String! + applicationId: String! +} + input PasswordChangeInput { oldPassword: String newPassword1: String! @@ -176,6 +192,7 @@ type Query { me: User! users(after: String, before: String, first: Int, last: Int): UserConnection! invitations(after: String, before: String, first: Int, last: Int): InvitationConnection! + githubRepositoryProviders(after: String, before: String, first: Int, last: Int): GithubRepositoryProviderConnection! jobRuns(ids: [ID!], jobs: [String!], after: String, before: String, first: Int, last: Int): JobRunConnection! jobRunStats(jobs: [String!]): JobStats! emailSetting: EmailSetting diff --git a/ee/tabby-webserver/src/schema/github_repository_provider.rs b/ee/tabby-webserver/src/schema/github_repository_provider.rs index b674f5cdc900..334c62e23d5c 100644 --- a/ee/tabby-webserver/src/schema/github_repository_provider.rs +++ b/ee/tabby-webserver/src/schema/github_repository_provider.rs @@ -1,9 +1,9 @@ -use crate::schema::Result; use async_trait::async_trait; use juniper::{GraphQLObject, ID}; use juniper_axum::relay::NodeType; use super::Context; +use crate::schema::Result; #[derive(GraphQLObject, Debug)] #[graphql(context = Context)] diff --git a/ee/tabby-webserver/src/schema/mod.rs b/ee/tabby-webserver/src/schema/mod.rs index aa74ba206522..a6f658f39398 100644 --- a/ee/tabby-webserver/src/schema/mod.rs +++ b/ee/tabby-webserver/src/schema/mod.rs @@ -30,8 +30,6 @@ use tracing::error; use validator::{Validate, ValidationErrors}; use worker::{Worker, WorkerService}; -use crate::schema::repository::FileEntrySearchResult; - use self::{ analytic::{AnalyticService, CompletionStats}, auth::{ @@ -47,6 +45,7 @@ use self::{ NetworkSetting, NetworkSettingInput, SecuritySetting, SecuritySettingInput, SettingService, }, }; +use crate::schema::repository::FileEntrySearchResult; pub trait ServiceLocator: Send + Sync { fn auth(&self) -> Arc; diff --git a/ee/tabby-webserver/src/service/github_repository_provider.rs b/ee/tabby-webserver/src/service/github_repository_provider.rs index 802c6f19fe7e..5f4970b19443 100644 --- a/ee/tabby-webserver/src/service/github_repository_provider.rs +++ b/ee/tabby-webserver/src/service/github_repository_provider.rs @@ -1,13 +1,15 @@ -use crate::{schema::Result, service::graphql_pagination_to_filter}; use async_trait::async_trait; use juniper::ID; use tabby_db::DbConn; -use crate::schema::github_repository_provider::{ - GithubRepositoryProvider, GithubRepositoryProviderService, -}; - use super::AsRowid; +use crate::{ + schema::{ + github_repository_provider::{GithubRepositoryProvider, GithubRepositoryProviderService}, + Result, + }, + service::graphql_pagination_to_filter, +}; struct GithubRepositoryProviderServiceImpl { db: DbConn, @@ -20,12 +22,12 @@ pub fn new_github_repository_provider_service(db: DbConn) -> impl GithubReposito #[async_trait] impl GithubRepositoryProviderService for GithubRepositoryProviderServiceImpl { async fn get_github_repository_provider(&self, id: ID) -> Result { - let provider = self.db.get_github_provider(id.as_rowid()? as i64).await?; + let provider = self.db.get_github_provider(id.as_rowid()?).await?; Ok(provider.into()) } async fn read_github_repository_provider_secret(&self, id: ID) -> Result { - let provider = self.db.get_github_provider(id.as_rowid()? as i64).await?; + let provider = self.db.get_github_provider(id.as_rowid()?).await?; Ok(provider.secret) } @@ -35,7 +37,7 @@ impl GithubRepositoryProviderService for GithubRepositoryProviderServiceImpl { access_token: String, ) -> Result<()> { self.db - .update_github_provider_access_token(id.as_rowid()? as i64, access_token) + .update_github_provider_access_token(id.as_rowid()?, access_token) .await?; Ok(()) } From 9655a9323b5a7602735ff7679f5ee276d8506964 Mon Sep 17 00:00:00 2001 From: boxbeam Date: Tue, 9 Apr 2024 13:11:43 -0400 Subject: [PATCH 16/18] Use Url::parse_with_params to generate redirect url --- ee/tabby-webserver/src/integrations/github.rs | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/ee/tabby-webserver/src/integrations/github.rs b/ee/tabby-webserver/src/integrations/github.rs index 632fdcb7f6e3..5c08ad48e8b0 100644 --- a/ee/tabby-webserver/src/integrations/github.rs +++ b/ee/tabby-webserver/src/integrations/github.rs @@ -10,6 +10,7 @@ use hyper::StatusCode; use juniper::ID; use serde::Deserialize; use tracing::error; +use url::Url; use crate::{ oauth::github::GithubOAuthResponse, @@ -44,8 +45,20 @@ pub fn routes( .with_state(state) } -fn github_redirect_url(client_id: &str, redirect_uri: &str, id: &ID) -> String { - format!("https://github.com/login/oauth/authorize?client_id={client_id}&response_type=code&scope=repo&redirect_uri={redirect_uri}/integrations/github/callback&state={id}") +fn github_redirect_url(client_id: &str, redirect_uri: &str, id: &ID) -> Result { + Ok(Url::parse_with_params( + "https://github.com/login/oauth/authorize", + &[ + ("client_id", client_id), + ("response_type", "code"), + ("scope", "repo"), + ( + "redirect_uri", + &format!("{redirect_uri}/integrations/github/callback"), + ), + ("state", &id.to_string()), + ], + )?) } async fn exchange_access_token( @@ -136,9 +149,13 @@ async fn connect( } }; - Ok(Redirect::temporary(&github_redirect_url( - &provider.application_id, - &external_url, - &id, - ))) + let redirect = match github_redirect_url(&provider.application_id, &external_url, &id) { + Ok(redirect) => redirect, + Err(e) => { + error!("Failed to generate callback URL: {e}"); + return Err(StatusCode::INTERNAL_SERVER_ERROR); + } + }; + + Ok(Redirect::temporary(redirect.as_str())) } From 9810feb26203c39fda01dcb0edec98ace651be72 Mon Sep 17 00:00:00 2001 From: boxbeam Date: Tue, 9 Apr 2024 13:29:32 -0400 Subject: [PATCH 17/18] Apply suggestions --- ee/tabby-webserver/src/integrations/github.rs | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/ee/tabby-webserver/src/integrations/github.rs b/ee/tabby-webserver/src/integrations/github.rs index 5c08ad48e8b0..ecc6960769c0 100644 --- a/ee/tabby-webserver/src/integrations/github.rs +++ b/ee/tabby-webserver/src/integrations/github.rs @@ -45,7 +45,7 @@ pub fn routes( .with_state(state) } -fn github_redirect_url(client_id: &str, redirect_uri: &str, id: &ID) -> Result { +fn get_authorize_url(client_id: &str, redirect_uri: &str, id: &ID) -> Result { Ok(Url::parse_with_params( "https://github.com/login/oauth/authorize", &[ @@ -96,15 +96,6 @@ async fn callback( State(state): State, Query(params): Query, ) -> Result { - let network_setting = match state.settings.read_network_setting().await { - Ok(setting) => setting, - Err(e) => { - error!("Failed to read network setting: {e}"); - return Err(StatusCode::INTERNAL_SERVER_ERROR); - } - }; - let external_url = network_setting.external_url; - let response = match exchange_access_token(&state, ¶ms).await { Ok(response) => response, Err(e) => { @@ -122,7 +113,7 @@ async fn callback( return Err(StatusCode::INTERNAL_SERVER_ERROR); } - Ok(Redirect::permanent(&external_url)) + Ok(Redirect::permanent("/")) } async fn connect( @@ -149,7 +140,7 @@ async fn connect( } }; - let redirect = match github_redirect_url(&provider.application_id, &external_url, &id) { + let redirect = match get_authorize_url(&provider.application_id, &external_url, &id) { Ok(redirect) => redirect, Err(e) => { error!("Failed to generate callback URL: {e}"); From 7d6900849905470c9322341200c9a601239d9b3b Mon Sep 17 00:00:00 2001 From: boxbeam Date: Tue, 9 Apr 2024 13:32:55 -0400 Subject: [PATCH 18/18] Make redirect temporary --- ee/tabby-webserver/src/integrations/github.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ee/tabby-webserver/src/integrations/github.rs b/ee/tabby-webserver/src/integrations/github.rs index ecc6960769c0..a0dd22e42d70 100644 --- a/ee/tabby-webserver/src/integrations/github.rs +++ b/ee/tabby-webserver/src/integrations/github.rs @@ -113,7 +113,7 @@ async fn callback( return Err(StatusCode::INTERNAL_SERVER_ERROR); } - Ok(Redirect::permanent("/")) + Ok(Redirect::temporary("/")) } async fn connect(