mod api; mod command; mod util; use crate::api::ValidationMethod; use anyhow::bail; use api::{ AdminAction, Args, CliError, Command, CreateObject, DevObject, GetObject, UpdateObject, ValidateObject, }; use clap::Parser; use command::dev_oauth2_listen; use env_logger::Env; use secd::{CredentialType, Secd}; use time::OffsetDateTime; use util::Result; const CONFIG_DIR_NAME: &str = "secdiam"; const CONFIG_PROFILE_FILE: &str = "profiles.toml"; const CONFIG_LOGIN_TEMPLATE: &str = "default_login.html"; const CONFIG_SIGNUP_TEMPLATE: &str = "default_signup.html"; const ISSUE_TRACKER_LOC: &str = "https://www.github.com/secdiam/iam"; #[tokio::main] async fn main() { env_logger::init_from_env(Env::default().default_filter_or("debug")); match exec().await { Ok(Some(s)) => println!("{}", s), Err(e) => { println!("{}", e); std::process::exit(1); } _ => {} } } async fn exec() -> Result> { let args = Args::parse(); Ok(match args.command { Command::Admin { action: AdminAction::Init { interactive }, } => { command::admin_init(interactive) .await .map_err(|_| CliError::AdminInitializationError)?; None } rest @ _ => { // let cfg = util::read_config(args.profile).map_err(|_| CliError::InvalidProfile)?; let secd = Secd::init(None, None) .await .map_err(|e| CliError::SecdInitializationFailure(e.to_string()))?; match rest { Command::Admin { action } => admin(&secd, action).await?, Command::Create { object } => create(&secd, object).await?, Command::Dev { object } => dev(object).await?, Command::Get { object } => get(&secd, object).await?, Command::Repl => { unimplemented!() } Command::Update { object } => update(&secd, object).await?, Command::Validate { object } => validate(&secd, object).await?, } } }) } async fn admin(secd: &Secd, cmd: AdminAction) -> Result> { Ok(match cmd { AdminAction::Backend { action } => { println!("do backend stuff!"); None } AdminAction::Create { object } => todo!(), AdminAction::Seal => { println!("do seal"); None } AdminAction::Unseal { secret_key } => { println!("do unseal: {}", secret_key); None } AdminAction::Init { .. } => { panic!("Invariant violation: this path should be impossible") } }) } async fn create(secd: &Secd, cmd: CreateObject) -> Result> { Ok(match cmd { CreateObject::Credential { method, identity_id, } => { let t = match &method { api::CredentialMethod::Passphrase { username, passphrase, } => CredentialType::Passphrase { key: username.clone(), value: passphrase.clone(), }, api::CredentialMethod::ApiToken { .. } => CredentialType::new_api_token()?, }; let expires_at = match method { api::CredentialMethod::ApiToken { expires_at, .. } => expires_at.map(|t| { OffsetDateTime::from_unix_timestamp(t) .expect("The provided value is an invalid unix timestamp") }), api::CredentialMethod::Passphrase { .. } => None, }; let credential = secd.create_credential(t, identity_id, expires_at).await?; Some(serde_json::ser::to_string_pretty(&credential)?.to_string()) } CreateObject::Validation { method, identity_id, } => match method { ValidationMethod::Email { address } => { let validation = secd.validate_email(&address, identity_id).await?; Some(serde_json::ser::to_string_pretty(&validation)?.to_string()) } _ => unimplemented!(), }, CreateObject::ValidationCompletion { validation_id, token, code, } => { if token.is_none() && code.is_none() { bail!("A token or code must be specified") } let session = secd .complete_address_validation(&validation_id, token, code) .await?; Some(serde_json::ser::to_string_pretty(&session)?.to_string()) } }) } async fn dev(cmd: DevObject) -> Result> { Ok(match cmd { DevObject::Oauth2Server { port } => serde_json::to_string(&dev_oauth2_listen(port)?).ok(), }) } async fn get(secd: &Secd, cmd: GetObject) -> Result> { Ok(match cmd { GetObject::Identity { identity_id, credential, } => { let t = credential.map(|cred| match cred { ValidateObject::ApiToken { token } => { CredentialType::api_token_from_str(&token).expect("failed to build api token") } ValidateObject::Passphrase { username, passphrase, } => CredentialType::Passphrase { key: username, value: passphrase, }, ValidateObject::Session { token } => { CredentialType::session_from_str(&token).expect("failed to build session") } }); Some( serde_json::ser::to_string_pretty(&secd.get_identity(identity_id, t).await?)? .to_string(), ) } }) } async fn update(secd: &Secd, cmd: UpdateObject) -> Result> { Ok(match cmd { UpdateObject::Identity { id, metadata } => { let identity = if metadata.is_some() { secd.update_identity_metadata(id, metadata.unwrap()).await? } else { secd.get_identity(Some(id), None).await? }; Some(serde_json::to_string(&identity)?.to_string()) } UpdateObject::Credential { id, revoke } => { if revoke { secd.revoke_credential(id).await?; } Some("Ok".to_string()) } }) } async fn validate(secd: &Secd, cmd: ValidateObject) -> Result> { let credential = match cmd { ValidateObject::ApiToken { token } => { secd.validate_credential(CredentialType::api_token_from_str(&token)?) .await? } ValidateObject::Passphrase { username, passphrase, } => { secd.validate_credential(CredentialType::Passphrase { key: username, value: passphrase, }) .await? } ValidateObject::Session { token } => { secd.validate_credential(CredentialType::session_from_str(&token)?) .await? } }; Ok(Some(serde_json::to_string_pretty(&credential)?.to_string())) }