Skip to content
Merged
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
40 changes: 34 additions & 6 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ hypr-am2 = { path = "crates/am2", package = "am2" }
hypr-analytics = { path = "crates/analytics", package = "analytics" }
hypr-api-auth = { path = "crates/api-auth", package = "api-auth" }
hypr-api-calendar = { path = "crates/api-calendar", package = "api-calendar" }
hypr-api-env = { path = "crates/api-env", package = "api-env" }
hypr-api-nango = { path = "crates/api-nango", package = "api-nango" }
hypr-api-subscription = { path = "crates/api-subscription", package = "api-subscription" }
hypr-api-sync = { path = "crates/api-sync", package = "api-sync" }
Expand Down
14 changes: 7 additions & 7 deletions apps/ai/src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::collections::BTreeMap;

use axum::{extract::Request, middleware::Next, response::Response};

use hypr_api_auth::Claims;
use hypr_api_auth::AuthContext;
pub use hypr_api_auth::{AuthState, require_auth};

const DEVICE_FINGERPRINT_HEADER: &str = "x-device-fingerprint";
Expand All @@ -14,21 +14,21 @@ pub async fn sentry_and_analytics(mut request: Request, next: Next) -> Response
.and_then(|h| h.to_str().ok())
.map(String::from);

if let Some(claims) = request.extensions().get::<Claims>() {
if let Some(auth) = request.extensions().get::<AuthContext>() {
sentry::configure_scope(|scope| {
scope.set_user(Some(sentry::User {
id: device_fingerprint.clone(),
email: claims.email.clone(),
username: Some(claims.sub.clone()),
email: auth.claims.email.clone(),
username: Some(auth.claims.sub.clone()),
..Default::default()
}));
scope.set_tag("user.id", &claims.sub);
scope.set_tag("user.id", &auth.claims.sub);

let mut ctx = BTreeMap::new();
ctx.insert(
"entitlements".into(),
sentry::protocol::Value::Array(
claims
auth.claims
.entitlements
.iter()
.map(|e| sentry::protocol::Value::String(e.clone()))
Expand All @@ -38,7 +38,7 @@ pub async fn sentry_and_analytics(mut request: Request, next: Next) -> Response
scope.set_context("user_claims", sentry::protocol::Context::Other(ctx));
});

let user_id = claims.sub.clone();
let user_id = auth.claims.sub.clone();
request
.extensions_mut()
.insert(hypr_analytics::AuthenticatedUserId(user_id));
Expand Down
2 changes: 1 addition & 1 deletion apps/ai/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ fn app() -> Router {
let llm_config =
hypr_llm_proxy::LlmProxyConfig::new(&env.llm).with_analytics(analytics.clone());
let stt_config = hypr_transcribe_proxy::SttProxyConfig::new(&env.stt).with_analytics(analytics);
let auth_state = AuthState::new(&env.supabase_url, "hyprnote_pro");
let auth_state = AuthState::new(&env.supabase_url).with_required_entitlement("hyprnote_pro");

let protected_routes = Router::new()
.merge(hypr_transcribe_proxy::listen_router(stt_config.clone()))
Expand Down
45 changes: 33 additions & 12 deletions crates/api-auth/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,30 @@ use hypr_supabase_auth::{Error as SupabaseAuthError, SupabaseAuth};

pub use hypr_supabase_auth::Claims;

#[derive(Clone)]
pub struct AuthContext {
pub token: String,
pub claims: Claims,
}

#[derive(Clone)]
pub struct AuthState {
inner: SupabaseAuth,
required_entitlement: String,
required_entitlement: Option<String>,
}

impl AuthState {
pub fn new(supabase_url: &str, required_entitlement: impl Into<String>) -> Self {
pub fn new(supabase_url: &str) -> Self {
Self {
inner: SupabaseAuth::new(supabase_url),
required_entitlement: required_entitlement.into(),
required_entitlement: None,
}
}

pub fn with_required_entitlement(mut self, entitlement: impl Into<String>) -> Self {
self.required_entitlement = Some(entitlement.into());
self
}
}

pub struct AuthError(SupabaseAuthError);
Expand Down Expand Up @@ -63,15 +74,18 @@ pub async fn require_auth(
.and_then(|h| h.to_str().ok())
.ok_or(SupabaseAuthError::MissingAuthHeader)?;

let token =
SupabaseAuth::extract_token(auth_header).ok_or(SupabaseAuthError::InvalidAuthHeader)?;
let token = SupabaseAuth::extract_token(auth_header)
.ok_or(SupabaseAuthError::InvalidAuthHeader)?
.to_owned();

let claims = state
.inner
.require_entitlement(token, &state.required_entitlement)
.await?;
let claims = match &state.required_entitlement {
Some(entitlement) => state.inner.require_entitlement(&token, entitlement).await?,
None => state.inner.verify_token(&token).await?,
};

request.extensions_mut().insert(claims);
request
.extensions_mut()
.insert(AuthContext { token, claims });

Ok(next.run(request).await)
}
Expand Down Expand Up @@ -117,7 +131,14 @@ mod tests {

#[test]
fn test_auth_state_new() {
let state = AuthState::new("https://example.supabase.co", "hyprnote_pro");
assert_eq!(state.required_entitlement, "hyprnote_pro");
let state = AuthState::new("https://example.supabase.co");
assert_eq!(state.required_entitlement, None);
}

#[test]
fn test_auth_state_with_required_entitlement() {
let state =
AuthState::new("https://example.supabase.co").with_required_entitlement("hyprnote_pro");
assert_eq!(state.required_entitlement, Some("hyprnote_pro".to_string()));
}
}
8 changes: 7 additions & 1 deletion crates/api-calendar/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@ version = "0.1.0"
edition = "2024"

[dependencies]
hypr-supabase-auth = { workspace = true }
hypr-api-auth = { workspace = true }
hypr-api-env = { workspace = true }
hypr-google-calendar = { workspace = true }
hypr-http = { workspace = true }
hypr-nango = { workspace = true }

chrono = { workspace = true, features = ["serde"] }

utoipa = { workspace = true }

Expand Down
17 changes: 8 additions & 9 deletions crates/api-calendar/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
use std::sync::Arc;
use hypr_api_env::{NangoEnv, SupabaseEnv};

#[derive(Clone)]
pub struct CalendarConfig {
pub auth: Option<Arc<hypr_supabase_auth::SupabaseAuth>>,
pub nango: NangoEnv,
pub supabase: SupabaseEnv,
}

impl CalendarConfig {
pub fn new() -> Self {
Self { auth: None }
}

pub fn with_auth(mut self, auth: Arc<hypr_supabase_auth::SupabaseAuth>) -> Self {
self.auth = Some(auth);
self
pub fn new(nango: &NangoEnv, supabase: &SupabaseEnv) -> Self {
Self {
nango: nango.clone(),
supabase: supabase.clone(),
}
}
}
6 changes: 0 additions & 6 deletions crates/api-calendar/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,6 @@ pub enum CalendarError {
Internal(String),
}

impl From<hypr_supabase_auth::Error> for CalendarError {
fn from(err: hypr_supabase_auth::Error) -> Self {
Self::Auth(err.to_string())
}
}

impl IntoResponse for CalendarError {
fn into_response(self) -> Response {
let (status, error_code) = match &self {
Expand Down
4 changes: 3 additions & 1 deletion crates/api-calendar/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
mod config;
mod error;
mod nango_http;
mod routes;
mod state;

pub use config::CalendarConfig;
pub use error::{CalendarError, Result};
pub use routes::{openapi, router};
pub use hypr_api_env::{NangoEnv, SupabaseEnv};
pub use routes::{ListEventsResponse, openapi, router};
pub use state::AppState;
Original file line number Diff line number Diff line change
@@ -1,27 +1,18 @@
use hypr_nango::{NangoClient, NangoIntegration};
use hypr_nango::NangoProxy;

pub struct NangoHttpClient<'a> {
nango: &'a NangoClient,
connection_id: String,
proxy: NangoProxy<'a>,
}

impl<'a> NangoHttpClient<'a> {
pub fn new(nango: &'a NangoClient, connection_id: impl Into<String>) -> Self {
Self {
nango,
connection_id: connection_id.into(),
}
pub fn new(proxy: NangoProxy<'a>) -> Self {
Self { proxy }
}
}

impl<'a> hypr_http::HttpClient for NangoHttpClient<'a> {
async fn get(&self, path: &str) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync>> {
let response = self
.nango
.for_connection(NangoIntegration::GoogleCalendar, &self.connection_id)
.get(path)?
.send()
.await?;
let response = self.proxy.get(path)?.send().await?;
let bytes = response.error_for_status()?.bytes().await?;
Ok(bytes.to_vec())
}
Expand All @@ -32,12 +23,7 @@ impl<'a> hypr_http::HttpClient for NangoHttpClient<'a> {
body: Vec<u8>,
) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync>> {
let json_value: serde_json::Value = serde_json::from_slice(&body)?;
let response = self
.nango
.for_connection(NangoIntegration::GoogleCalendar, &self.connection_id)
.post(path, &json_value)?
.send()
.await?;
let response = self.proxy.post(path, &json_value)?.send().await?;
let bytes = response.error_for_status()?.bytes().await?;
Ok(bytes.to_vec())
}
Expand All @@ -48,12 +34,7 @@ impl<'a> hypr_http::HttpClient for NangoHttpClient<'a> {
body: Vec<u8>,
) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync>> {
let json_value: serde_json::Value = serde_json::from_slice(&body)?;
let response = self
.nango
.for_connection(NangoIntegration::GoogleCalendar, &self.connection_id)
.put(path, &json_value)?
.send()
.await?;
let response = self.proxy.put(path, &json_value)?.send().await?;
let bytes = response.error_for_status()?.bytes().await?;
Ok(bytes.to_vec())
}
Expand All @@ -64,12 +45,7 @@ impl<'a> hypr_http::HttpClient for NangoHttpClient<'a> {
body: Vec<u8>,
) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync>> {
let json_value: serde_json::Value = serde_json::from_slice(&body)?;
let response = self
.nango
.for_connection(NangoIntegration::GoogleCalendar, &self.connection_id)
.patch(path, &json_value)?
.send()
.await?;
let response = self.proxy.patch(path, &json_value)?.send().await?;
let bytes = response.error_for_status()?.bytes().await?;
Ok(bytes.to_vec())
}
Expand All @@ -78,12 +54,7 @@ impl<'a> hypr_http::HttpClient for NangoHttpClient<'a> {
&self,
path: &str,
) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync>> {
let response = self
.nango
.for_connection(NangoIntegration::GoogleCalendar, &self.connection_id)
.delete(path)?
.send()
.await?;
let response = self.proxy.delete(path)?.send().await?;
let bytes = response.error_for_status()?.bytes().await?;
Ok(bytes.to_vec())
}
Expand Down
Loading
Loading