use oauth::{OAuth, ZitadelConfig}; use serde::{Deserialize, Serialize}; mod auth; mod introspection; mod oauth; mod session; pub use auth::{Auth, AuthService}; use session::SessionClap; pub use session::SessionService; #[derive(clap::ValueEnum, Clone, PartialEq, Eq, Debug)] pub enum AuthEngine { Noop, Zitadel, } #[derive(clap::ValueEnum, Clone, PartialEq, Eq, Debug)] pub enum SessionBackend { InMemory, Postgresql, } #[derive(clap::Args, Clone, PartialEq, Eq, Debug)] pub struct AuthClap { #[arg( env = "AUTH_ENGINE", long = "auth-engine", requires_ifs = [ ( "zitadel", "ZitadelClap" ) ], default_value = "noop" ) ] pub engine: AuthEngine, #[arg( env = "SESSION_BACKEND", long = "session-backend", requires_ifs = [ ( "postgresql", "PostgresqlSessionClap" ) ], default_value = "in-memory" ) ] pub session_backend: SessionBackend, #[clap(flatten)] pub zitadel: ZitadelClap, #[clap(flatten)] pub session: SessionClap, } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct AuthConfigFile { zitadel: Option, } #[derive(clap::Args, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[group(requires_all = ["auth_url", "client_id", "client_secret", "redirect_url", "token_url", "authority_url"])] pub struct ZitadelClap { #[arg(env = "ZITADEL_AUTH_URL", long = "zitadel-auth-url")] pub auth_url: Option, #[arg(env = "ZITADEL_CLIENT_ID", long = "zitadel-client-id")] pub client_id: Option, #[arg(env = "ZITADEL_CLIENT_SECRET", long = "zitadel-client-secret")] pub client_secret: Option, #[arg(env = "ZITADEL_REDIRECT_URL", long = "zitadel-redirect-url")] pub redirect_url: Option, #[arg(env = "ZITADEL_AUTHORITY_URL", long = "zitadel-authority-url")] pub authority_url: Option, #[arg(env = "ZITADEL_TOKEN_URL", long = "zitadel-token-url")] pub token_url: Option, } impl TryFrom for OAuth { type Error = anyhow::Error; fn try_from(value: AuthClap) -> Result { match value.engine { AuthEngine::Noop => Ok(OAuth::new_noop()), AuthEngine::Zitadel => Ok(OAuth::from(ZitadelConfig::try_from(value.zitadel)?)), } } } impl AuthClap { pub fn merge(&mut self, config: AuthConfigFile) -> &mut Self { if let Some(zitadel) = config.zitadel { if let Some(auth_url) = zitadel.auth_url { if let Some(_) = self.zitadel.auth_url { _ = self.zitadel.auth_url.replace(auth_url); } } if let Some(client_id) = zitadel.client_id { if let Some(_) = self.zitadel.client_id { _ = self.zitadel.client_id.replace(client_id); } } if let Some(client_secret) = zitadel.client_secret { if let Some(_) = self.zitadel.client_secret { _ = self.zitadel.client_secret.replace(client_secret); } } if let Some(redirect_url) = zitadel.redirect_url { if let Some(_) = self.zitadel.redirect_url { _ = self.zitadel.redirect_url.replace(redirect_url); } } if let Some(authority_url) = zitadel.authority_url { if let Some(_) = self.zitadel.authority_url { _ = self.zitadel.authority_url.replace(authority_url); } } if let Some(token_url) = zitadel.token_url { if let Some(_) = self.zitadel.token_url { _ = self.zitadel.token_url.replace(token_url); } } } self } } #[cfg(test)] mod test { use crate::{ session::{PostgresqlSessionClap, SessionClap}, AuthClap, AuthEngine, SessionBackend, ZitadelClap, }; use clap::Parser; use pretty_assertions::assert_eq; #[derive(Parser)] #[command(author, version, about, long_about = None)] pub struct Cli { #[command(subcommand)] command: Commands, } #[derive(clap::Subcommand, Clone, Debug, Eq, PartialEq)] pub enum Commands { One { #[clap(flatten)] options: AuthClap, }, } #[test] fn test_command_parse_as_default_noop() { let cli: Cli = Cli::parse_from(&["base", "one"]); assert_eq!( cli.command, Commands::One { options: AuthClap { engine: AuthEngine::Noop, zitadel: ZitadelClap { auth_url: None, client_id: None, client_secret: None, redirect_url: None, token_url: None, authority_url: None, }, session_backend: SessionBackend::InMemory, session: SessionClap { postgresql: PostgresqlSessionClap { conn: None } } } } ); } #[test] fn test_command_parse_as_noop() { let cli: Cli = Cli::parse_from(&["base", "one", "--auth-engine", "noop"]); assert_eq!( cli.command, Commands::One { options: AuthClap { engine: AuthEngine::Noop, zitadel: ZitadelClap { auth_url: None, client_id: None, client_secret: None, redirect_url: None, token_url: None, authority_url: None, }, session_backend: crate::SessionBackend::InMemory, session: crate::SessionClap { postgresql: PostgresqlSessionClap { conn: None } } } } ); } #[test] fn test_command_parse_as_zitadel() { let cli: Cli = Cli::parse_from(&[ "base", "one", "--auth-engine=zitadel", "--zitadel-client-id=something", "--zitadel-client-secret=something", "--zitadel-auth-url=https://something", "--zitadel-redirect-url=https://something", "--zitadel-token-url=https://something", "--zitadel-authority-url=https://something", ]); assert_eq!( cli.command, Commands::One { options: AuthClap { engine: AuthEngine::Zitadel, zitadel: ZitadelClap { auth_url: Some("https://something".into()), client_id: Some("something".into()), client_secret: Some("something".into()), redirect_url: Some("https://something".into()), token_url: Some("https://something".into()), authority_url: Some("https://something".into()), }, session_backend: crate::SessionBackend::InMemory, session: crate::SessionClap { postgresql: PostgresqlSessionClap { conn: None } } }, } ); } }