mod api; mod command; mod util; use anyhow::bail; use api::{ AdminAction, AdminObject, Args, CliError, Command, CreateObject, DevObject, GetObject, LinkObject, ListObject, Validation, }; use clap::Parser; use command::dev_oauth2_listen; use env_logger::Env; use secd::{Secd, SecdError}; use util::{error_detail, Result}; use uuid::Uuid; use crate::api::ValidationMethod; 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::Init { interactive } | 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( cfg.store, Some(&cfg.store_conn), cfg.emailer, cfg.email_template_login, cfg.email_template_signup, ) .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::Init { .. } => bail!(CliError::InternalError(error_detail( "4a696b66-6231-4a2f-811c-4448a41473d2", "Code path should be unreachable", ))), Command::Link { object, unlink } => link(&secd, object, unlink).await?, Command::Ls { object, name, before, after, } => list(&secd, object, name, before, after).await?, Command::Repl => { unimplemented!() } } } }) } async fn admin(secd: &Secd, cmd: AdminAction) -> Result> { Ok(match cmd { AdminAction::Backend { action } => { println!("do backend stuff!"); None } AdminAction::Create { object } => match object { AdminObject::Oauth2Provider { provider, client_id, secret, redirect_url, } => { secd.create_oauth_provider(&provider, client_id, secret, redirect_url) .await?; None } rest @ _ => unimplemented!(), }, 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::ApiKey { identity, expired_at, } => { println!("create object"); None } CreateObject::Group { name, identities } => { println!("create group"); None } CreateObject::Permission { services, actions } => { println!("create permission"); None } CreateObject::Role { name, permissions } => { println!("create role"); None } CreateObject::Service { name, uri } => { println!("create service"); None } CreateObject::ServiceAction { name, program } => { println!("create service action"); None } CreateObject::Session { validation_id, secret_code, } => { let session = secd .exchange_code_for_session(validation_id, secret_code) .await .map_err(|e| match e { SecdError::InvalidCode => CliError::InvalidCode, _ => CliError::InternalError(error_detail( "17e5c226-5d7d-44a2-b3b5-be3ee958c252", "An unknown error while exchanging a session", )), })?; serde_json::to_string(&session).ok() } CreateObject::Validation { method, identity } => match method { ValidationMethod::Email { address } => serde_json::to_string(&Validation { validation_id: secd.create_validation_request_email(Some(&address)).await?, note: Some(" sent to client".into()), oauth_auth_url: None, }) .ok(), ValidationMethod::Oauth2 { provider, scope, identity, } => { let redirect = secd .create_validation_request_oauth(&provider, scope) .await? .to_string(); let validation_id = redirect .split("state=") .collect::>() .last() .map(|i| Uuid::parse_str(i).ok()) .flatten() .unwrap(); serde_json::to_string(&Validation { validation_id, note: Some( " is retrieved by completing oauth flow in the browser".into(), ), oauth_auth_url: Some(redirect), }) .ok() } _ => unimplemented!(), }, }) } 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::ApiKey { public_key } => { println!("get object api key"); None } GetObject::Group { name, id } => { println!("get object group"); None } GetObject::Identity { id } => { println!("get object identity"); None } GetObject::Permission { name, id } => { println!("get object permission"); None } GetObject::Role { name, id } => { println!("get object role"); None } GetObject::Service { name, id } => { println!("get object service"); None } GetObject::ServiceAction { name, id } => { println!("get object service action"); None } GetObject::Session { secret } => { println!("get object session"); None } GetObject::Validation { id } => { println!("get object validation"); None } }) } async fn link(secd: &Secd, cmd: LinkObject, should_unlink: bool) -> Result> { Ok(match cmd { LinkObject::Group { group_name, group_id, identity_ids, } => { println!("link object group"); None } LinkObject::Identity { identity_id, group_names, group_ids, } => { println!("link object identity"); None } LinkObject::Permission { permission_name, permission_id, role_names, role_ids, } => { println!("link object permission"); None } LinkObject::Role { role_name, role_id, permission_names, permission_ids, } => { println!("link object role"); None } LinkObject::Service { service_name, service_id, permission_names, permission_ids, } => { println!("link object service"); None } LinkObject::ServiceAction { service_action_name, service_action_id, service_name, service_ids, } => { println!("link object service action"); None } }) } async fn list( secd: &Secd, cmd: ListObject, filter_name: Option, filter_before: Option, filter_after: Option, ) -> Result> { Ok(match cmd { ListObject::ApiKey => { println!("list object api key"); None } ListObject::Group => { println!("list object group"); None } ListObject::Identity => { println!("list object identity"); None } ListObject::Permission => { println!("list object permission"); None } ListObject::Role => { println!("list object role"); None } ListObject::Service => { println!("list object service"); None } ListObject::ServiceAction => { println!("list object service action"); None } ListObject::Session => { println!("list object session"); None } ListObject::Validation => { println!("list object valiation"); None } }) }