Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ hypr-network = { path = "crates/network", package = "network" }
hypr-notification = { path = "crates/notification", package = "notification" }
hypr-notification2 = { path = "crates/notification2", package = "notification2" }
hypr-notion = { path = "crates/notion", package = "notion" }
hypr-oauth-callback = { path = "crates/oauth-callback", package = "oauth-callback" }
hypr-oauth-client = { path = "crates/oauth-client", package = "oauth-client" }
hypr-oauth-core = { path = "crates/oauth-core", package = "oauth-core" }
hypr-oauth-providers = { path = "crates/oauth-providers", package = "oauth-providers" }
hypr-oauth-server = { path = "crates/oauth-server", package = "oauth-server" }
hypr-onnx = { path = "crates/onnx", package = "onnx" }
hypr-openai = { path = "crates/openai", package = "openai" }
hypr-pyannote-cloud = { path = "crates/pyannote-cloud", package = "pyannote-cloud" }
Expand Down Expand Up @@ -95,6 +100,7 @@ tauri-plugin-local-stt = { path = "plugins/local-stt" }
tauri-plugin-membership = { path = "plugins/membership" }
tauri-plugin-misc = { path = "plugins/misc" }
tauri-plugin-notification = { path = "plugins/notification" }
tauri-plugin-oauth2 = { path = "plugins/oauth2" }
tauri-plugin-obsidian = { path = "plugins/obsidian" }
tauri-plugin-sfx = { path = "plugins/sfx" }
tauri-plugin-sse = { path = "plugins/sse" }
Expand All @@ -105,6 +111,7 @@ tauri-plugin-tray = { path = "plugins/tray" }
tauri-plugin-windows = { path = "plugins/windows" }

async-stream = "0.3.6"
async-trait = "0.1.88"
futures-channel = "0.3.31"
futures-core = "0.3.31"
futures-util = "0.3.31"
Expand All @@ -118,6 +125,7 @@ tokio-util = "0.7.15"
anyhow = "1"
approx = "0.5.1"
backon = "1.4.1"
base64 = "0.22.1"
bytes = "1.9.0"
cached = "0.55.1"
codes-iso-639 = "0.1.5"
Expand Down
1 change: 1 addition & 0 deletions apps/desktop/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ tauri-plugin-machine-uid = { workspace = true }
tauri-plugin-membership = { workspace = true }
tauri-plugin-misc = { workspace = true }
tauri-plugin-notification = { workspace = true }
tauri-plugin-oauth2 = { workspace = true }
tauri-plugin-obsidian = { workspace = true }
tauri-plugin-opener = { workspace = true }
tauri-plugin-sfx = { workspace = true }
Expand Down
1 change: 1 addition & 0 deletions apps/desktop/src-tauri/capabilities/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"task:default",
"membership:default",
"obsidian:default",
"oauth2:default",
{
"identifier": "opener:allow-open-url",
"allow": [{ "url": "https://**" }, { "url": "mailto:*" }, { "url": "obsidian://**" }]
Expand Down
1 change: 1 addition & 0 deletions apps/desktop/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ pub async fn main() {
.plugin(tauri_plugin_notification::init())
.plugin(tauri_plugin_windows::init())
.plugin(tauri_plugin_membership::init())
.plugin(tauri_plugin_oauth2::init())
.plugin(tauri_plugin_process::init())
.plugin(tauri_plugin_global_shortcut::Builder::new().build())
.plugin(tauri_plugin_autostart::init(
Expand Down
22 changes: 22 additions & 0 deletions crates/oauth-callback/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[package]
name = "oauth-callback"
version = "0.1.0"
edition = "2021"

[dependencies]
hypr-oauth-core = { workspace = true }
hypr-oauth-providers = { workspace = true }

tower = { workspace = true }

serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
serde_qs = { workspace = true }

base64 = { workspace = true }
thiserror = { workspace = true }
uuid = { workspace = true, features = ["v4"] }

futures-util = { workspace = true }
reqwest = { workspace = true }
tracing = { workspace = true }
1 change: 1 addition & 0 deletions crates/oauth-callback/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod service;
69 changes: 69 additions & 0 deletions crates/oauth-callback/src/service.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
use futures_util::task::Poll;
use futures_util::Future;
use hypr_oauth_core::{OAuthError, OAuthRequest, OAuthResponse};
use hypr_oauth_providers::ProviderRegistry;
use std::pin::Pin;
use std::sync::Arc;
use std::task::Context;
use tower::Service;

#[derive(Clone)]
pub struct OAuthServerService {
providers: Arc<ProviderRegistry>,
secret_store: Arc<dyn SecretStore>,
}

pub trait SecretStore: Send + Sync {
fn get_client_secret(&self, provider: &str) -> Option<String>;
}

impl OAuthServerService {
pub fn new(providers: Arc<ProviderRegistry>, secret_store: Arc<dyn SecretStore>) -> Self {
Self {
providers,
secret_store,
}
}
}

impl Service<OAuthRequest> for OAuthServerService {
type Response = OAuthResponse;
type Error = OAuthError;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;

fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}

fn call(&mut self, mut req: OAuthRequest) -> Self::Future {
let providers = self.providers.clone();
let secret_store = self.secret_store.clone();

Box::pin(async move {
let provider = providers
.get(&req.provider)
.ok_or(OAuthError::ProviderNotFound)?;

// Add client secret if needed
if provider.metadata().requires_client_secret {
req.config.client_secret = secret_store.get_client_secret(&req.provider);
}

// Generate state
let state = req
.state
.unwrap_or_else(|| uuid::Uuid::new_v4().to_string());

// Build auth URL
let auth_url = provider
.build_auth_url(&req.config, &state, req.pkce_challenge.as_deref())
.unwrap();
Comment on lines +58 to +60
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Replace unwrap() with proper error handling.

The unwrap() call can cause panics if URL building fails. Use proper error propagation instead.

-            let auth_url = provider
-                .build_auth_url(&req.config, &state, req.pkce_challenge.as_deref())
-                .unwrap();
+            let auth_url = provider
+                .build_auth_url(&req.config, &state, req.pkce_challenge.as_deref())
+                .map_err(|e| OAuthError::InvalidRequest(e.to_string()))?;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let auth_url = provider
.build_auth_url(&req.config, &state, req.pkce_challenge.as_deref())
.unwrap();
let auth_url = provider
.build_auth_url(&req.config, &state, req.pkce_challenge.as_deref())
.map_err(|e| OAuthError::InvalidRequest(e.to_string()))?;
🤖 Prompt for AI Agents
In crates/oauth-callback/src/service.rs around lines 58 to 60, replace the
unwrap() call on build_auth_url with proper error handling to avoid panics.
Modify the code to propagate the error using the ? operator or handle the error
explicitly by returning a Result type from the function. This ensures that
failures in URL building are gracefully managed instead of causing a panic.


Ok(OAuthResponse {
authorization_url: auth_url.to_string(),
state,
session_id: Some(uuid::Uuid::new_v4().to_string()),
})
})
}
}
9 changes: 9 additions & 0 deletions crates/oauth-client/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "oauth-client"
version = "0.1.0"
edition = "2021"

[dependencies]
hypr-oauth-core = { workspace = true }

Comment on lines +6 to +8
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Broken dependency name – crate will not resolve.

hypr-oauth-core does not exist in this workspace; the actual crate you added is named oauth-core (see crates/oauth-core/Cargo.toml). Compilation will fail.

-hypr-oauth-core = { workspace = true }
+oauth-core = { workspace = true }
🤖 Prompt for AI Agents
In crates/oauth-client/Cargo.toml around lines 6 to 8, the dependency name is
incorrect as it uses "hypr-oauth-core" which does not exist in the workspace.
Change the dependency name to "oauth-core" to match the actual crate name in
crates/oauth-core/Cargo.toml so that the crate resolves correctly and
compilation succeeds.

reqwest = { workspace = true }
1 change: 1 addition & 0 deletions crates/oauth-client/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub const a: i32 = 1;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Placeholder constant provides no functionality.

pub const a: i32 = 1; is unused and leaks an arbitrary public API surface. Remove it or replace with meaningful client logic; otherwise this crate serves no purpose and will fail the “no unused exports” guideline.

-pub const a: i32 = 1;
+// TODO: implement OAuth client functions (token request, refresh, etc.)
🤖 Prompt for AI Agents
In crates/oauth-client/src/lib.rs at line 1, the public constant `a` is unused
and adds unnecessary public API surface. Remove this placeholder constant or
replace it with meaningful client logic to ensure the crate has a valid exported
API and complies with the "no unused exports" guideline.

12 changes: 12 additions & 0 deletions crates/oauth-core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "oauth-core"
version = "0.1.0"
edition = "2021"

[dependencies]
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }

anyhow = { workspace = true }
async-trait = { workspace = true }
url = { workspace = true }
5 changes: 5 additions & 0 deletions crates/oauth-core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mod provider;
pub use provider::*;

mod types;
pub use types::*;
42 changes: 42 additions & 0 deletions crates/oauth-core/src/provider.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use async_trait::async_trait;
use url::Url;

use crate::types::*;

#[async_trait]
pub trait OAuthProvider: Send + Sync {
fn name(&self) -> &'static str;

fn build_auth_url(
&self,
config: &OAuthConfig,
state: &str,
pkce_challenge: Option<&str>,
) -> Result<Url, anyhow::Error>;

async fn exchange_code(
&self,
code: &str,
config: &OAuthConfig,
pkce_verifier: Option<&str>,
) -> Result<TokenResponse, anyhow::Error>;

async fn refresh_token(
&self,
refresh_token: &str,
config: &OAuthConfig,
) -> Result<TokenResponse, anyhow::Error>;

fn metadata(&self) -> ProviderMetadata {
ProviderMetadata::default()
}
}

#[derive(Debug, Clone, Default)]
pub struct ProviderMetadata {
pub supports_pkce: bool,
pub supports_refresh: bool,
pub requires_client_secret: bool,
pub auth_url: &'static str,
pub token_url: &'static str,
}
48 changes: 48 additions & 0 deletions crates/oauth-core/src/types.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

pub enum OAuthError {
ProviderNotFound,
}
Comment on lines +4 to +6
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add standard error traits for proper error handling.

The enum should implement standard error traits for better integration with Rust's error handling ecosystem.

+use std::fmt;
+
+#[derive(Debug)]
 pub enum OAuthError {
     ProviderNotFound,
 }
+
+impl fmt::Display for OAuthError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            OAuthError::ProviderNotFound => write!(f, "OAuth provider not found"),
+        }
+    }
+}
+
+impl std::error::Error for OAuthError {}

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In crates/oauth-core/src/types.rs around lines 4 to 6, the OAuthError enum lacks
implementation of standard error traits. Implement the std::error::Error trait
and also derive or implement std::fmt::Display and std::fmt::Debug for
OAuthError to enable proper error handling and integration with Rust's error
ecosystem.


#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OAuthConfig {
pub client_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub client_secret: Option<String>,
pub redirect_uri: String,
pub scopes: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OAuthRequest {
pub provider: String,
pub config: OAuthConfig,
pub state: Option<String>,
pub pkce_challenge: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OAuthResponse {
pub authorization_url: String,
pub state: String,
pub session_id: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CallbackRequest {
pub code: String,
pub state: String,
pub session_id: Option<String>,
pub pkce_verifier: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TokenResponse {
pub access_token: String,
pub refresh_token: Option<String>,
pub expires_in: Option<i64>,
pub token_type: String,
#[serde(flatten)]
pub extra: HashMap<String, serde_json::Value>,
}
13 changes: 13 additions & 0 deletions crates/oauth-providers/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "oauth-providers"
version = "0.1.0"
edition = "2021"

[dependencies]
hypr-oauth-core = { workspace = true }

anyhow = { workspace = true }
async-trait = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
url = { workspace = true }
Loading
Loading