diff --git a/como_auth/Cargo.toml b/como_auth/Cargo.toml new file mode 100644 index 0000000..d853b77 --- /dev/null +++ b/como_auth/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "como_auth" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +como_gql.workspace = true +como_core.workspace = true +como_domain.workspace = true +como_infrastructure.workspace = true + +async-trait.workspace = true +async-graphql.workspace = true +async-graphql-axum.workspace = true +axum.workspace = true +axum-extra.workspace = true +axum-sessions.workspace = true +serde.workspace = true +serde_json.workspace = true +tokio.workspace = true +uuid.workspace = true +sqlx.workspace = true +anyhow.workspace = true +tracing.workspace = true +async-sqlx-session.workspace = true + +zitadel = { version = "3.3.1", features = ["axum"] } +tower = "0.4.13" +tower-http = { version = "0.4.0", features = ["cors", "trace"] } +oauth2 = "4.4.0" +openidconnect = "3.0.0" diff --git a/como_auth/src/lib.rs b/como_auth/src/lib.rs new file mode 100644 index 0000000..3930ab9 --- /dev/null +++ b/como_auth/src/lib.rs @@ -0,0 +1 @@ +mod oauth; diff --git a/como_auth/src/oauth.rs b/como_auth/src/oauth.rs new file mode 100644 index 0000000..5969a31 --- /dev/null +++ b/como_auth/src/oauth.rs @@ -0,0 +1,143 @@ +use async_trait::async_trait; +use oauth2::{basic::BasicClient, AuthUrl, ClientId, ClientSecret, RedirectUrl, TokenUrl}; +use std::{env, ops::Deref, sync::Arc}; + +#[async_trait] +pub trait OAuthClient { + async fn get_token(&self) -> anyhow::Result<()>; +} + +pub struct OAuth(Arc); + +impl OAuth { + pub fn new_zitadel(config: ZitadelConfig) -> Self { + Self(Arc::new(ZitadelOAuthClient::from(config))) + } + pub fn new_noop() -> Self { + Self(Arc::new(NoopOAuthClient {})) + } +} + +pub enum OAuthConfig { + Zitadel(ZitadelConfig), + Noop, +} + +impl From for OAuth { + fn from(value: OAuthConfig) -> Self { + match value { + OAuthConfig::Zitadel(c) => c.into(), + OAuthConfig::Noop => Self::new_noop(), + } + } +} + +impl Deref for OAuth { + type Target = Arc; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl From for OAuth { + fn from(value: ZitadelConfig) -> Self { + Self::new_zitadel(value) + } +} + +// -- Noop +pub struct NoopOAuthClient; +#[async_trait] +impl OAuthClient for NoopOAuthClient { + async fn get_token(&self) -> anyhow::Result<()> { + Ok(()) + } +} + +// -- Zitadel + +pub struct ZitadelConfig { + client_id: String, + client_secret: String, + redirect_url: String, + auth_url: String, + token_url: String, +} + +pub struct ZitadelOAuthClient { + client: BasicClient, +} + +impl ZitadelOAuthClient { + pub fn new( + client_id: impl Into, + client_secret: impl Into, + redirect_url: impl Into, + auth_url: impl Into, + token_url: impl Into, + ) -> Self { + Self { + client: Self::oauth_client(ZitadelConfig { + client_id: client_id.into(), + client_secret: client_secret.into(), + redirect_url: redirect_url.into(), + auth_url: auth_url.into(), + token_url: token_url.into(), + }), + } + } + + fn oauth_client(config: ZitadelConfig) -> BasicClient { + BasicClient::new( + ClientId::new(config.client_id), + Some(ClientSecret::new(config.client_secret)), + AuthUrl::new(config.auth_url).unwrap(), + Some(TokenUrl::new(config.token_url).unwrap()), + ) + .set_redirect_uri(RedirectUrl::new(config.redirect_url).unwrap()) + } +} + +impl From for ZitadelOAuthClient { + fn from(value: ZitadelConfig) -> Self { + Self::new( + value.client_id, + value.client_secret, + value.redirect_url, + value.auth_url, + value.token_url, + ) + } +} + +#[async_trait] +impl OAuthClient for ZitadelOAuthClient { + async fn get_token(&self) -> anyhow::Result<()> { + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use crate::oauth::{OAuth, OAuthConfig, ZitadelConfig}; + + #[tokio::test] + async fn test_noop() { + OAuth::from(OAuthConfig::Noop).get_token().await.unwrap(); + } + + #[tokio::test] + async fn test_zitadel() { + OAuth::from(OAuthConfig::Zitadel(ZitadelConfig { + client_id: "something".into(), + client_secret: "something".into(), + redirect_url: "https://something".into(), + auth_url: "https://something".into(), + token_url: "https://something".into(), + })) + .get_token() + .await + .unwrap(); + } +}