use crate::{ api::{self, CliError, Validation, ValidationSecretCode}, util::{self, error_detail, get_config_profile, Result}, CONFIG_LOGIN_TEMPLATE, CONFIG_SIGNUP_TEMPLATE, }; use colored::*; use rand::distributions::{Alphanumeric, DistString}; use secd::{AuthEmailMessenger, AuthStore}; use std::{ fs::{self, File}, io::{self, stdin, stdout, Read, Write}, net::TcpListener, str::{self, FromStr}, }; use strum::VariantNames; use tiny_http::Server; use uuid::Uuid; const DEFAULT_LOGIN_EMAIL: &str = "

You requested a login link for %secd_email_address%. Please click the following link

http://localhost:5500/myapp/iam/exchange/%secd_link%

or use code: %secd_code%

"; const DEFAULT_SIGNUP_EMAIL: &str = "

Welcome to SecD IAM

If you did not request this sign up, you can safely ignore this email. Otherwise, please click the following link to validate your account

http://localhost:5500/myapp/iam/exchange/%secd_link%

or use code: %secd_code%

"; pub async fn admin_init(is_interactive: bool) -> Result<()> { let config_dir = util::get_config_dir(); let config_profile = get_config_profile(); fs::create_dir_all(config_dir.clone()); if config_profile.try_exists()? { writeln!( io::stdout(), "{} {}", config_profile.clone().display(), "already exists and there is nothing to initialize. To create a new IAM store use `iam admin create store` or modify the configuration profile directly" .yellow() )?; } else { writeln!(stdout(), "{}", "creating default profile".green())?; let mut login_template = config_dir.clone(); login_template.push(CONFIG_LOGIN_TEMPLATE); let mut f = File::create(login_template.clone())?; f.write_all(DEFAULT_LOGIN_EMAIL.as_bytes())?; let mut signup_template = config_dir.clone(); signup_template.push(CONFIG_SIGNUP_TEMPLATE); f = File::create(signup_template.clone())?; f.write_all(DEFAULT_SIGNUP_EMAIL.as_bytes())?; let mut cfg = api::Config { profile: vec![api::ConfigProfile { name: "default".to_string(), store: AuthStore::Sqlite { conn: "".into() }, store_conn: format!( "sqlite://{}/{}.sql?mode=rwc", config_dir.clone().display().to_string(), Alphanumeric.sample_string(&mut rand::thread_rng(), 5), ), emailer: secd::AuthEmailMessenger::Local, email_template_login: Some(login_template.display().to_string()), email_template_signup: Some(signup_template.display().to_string()), }], }; let mut input: String = String::new(); if is_interactive { writeln!(stdout(), "{}", "For a complete overview of configuration options, cancel the initialization and explore `iam help`")?; write!(stdout(), "Would you like to create a default local store with local stubs for external services?[(y)es/(n)o]: ")?; stdout().flush()?; 'outer: loop { input.clear(); stdin().read_line(&mut input)?; match input.as_str().trim() { "Y" | "y" | "Yes" | "yes" => break, "N" | "n" | "No" | "no" => { loop { write!( stdout(), "Persistence store {:?}: ", AuthStore::VARIANTS .iter() .map(|s| s.to_lowercase()) .collect::>() )?; stdout().flush()?; input.clear(); stdin().read_line(&mut input)?; match AuthStore::from_str(&input.trim()) { Ok(s) => { cfg.profile[0].store = s; break; } Err(_) => { writeln!(stdout(), "{}", "Invalid store type".red())?; } } } write!(stdout(), "Store connection string: ")?; stdout().flush()?; input.clear(); stdin().read_line(&mut input)?; cfg.profile[0].store_conn = input.trim().to_string().clone(); loop { write!( stdout(), "Email provider {:?}: ", AuthEmailMessenger::VARIANTS .iter() .map(|s| s.to_lowercase()) .collect::>() )?; stdout().flush()?; input.clear(); stdin().read_line(&mut input)?; match AuthEmailMessenger::from_str(&input.trim()) { Ok(s) => { cfg.profile[0].emailer = s; break; } Err(_) => { writeln!(stdout(), "{}", "Invalid email provider".red())?; } } } write!( stdout(), "Email template for login validation:[FilePath or Enter for default]: " )?; stdout().flush()?; input.clear(); stdin().read_line(&mut input)?; cfg.profile[0].email_template_login = Some(input.trim().to_string().clone()); write!( stdout(), "Email template for signup validation:[FilePath or Enter for default]: " )?; stdout().flush()?; input.clear(); stdin().read_line(&mut input)?; cfg.profile[0].email_template_login = Some(input.trim().to_string().clone()); break 'outer; } _ => {} } } } let mut f = File::create(config_profile.clone())?; f.write_all(toml::to_string(&cfg)?.as_bytes())?; writeln!( stdout(), "{} {} {} {} {}", "created iam config".green(), "default", "at".green(), config_dir.display().to_string(), "to hold secD iam configurations".green() )?; } Ok(()) } pub fn dev_oauth2_listen(port: Option) -> Result { let server = Server::http(&format!("localhost:{}", port.unwrap_or(1337))).map_err(|_| { CliError::InternalError(error_detail( "53abd03d-c426-4bba-969d-f1dbed9af75b", "Failure while creating a server to listen to oauth responese", )) })?; let parser = |s: &str| -> Option { let maybe_code = s.split("code=").collect::>(); if maybe_code.len() != 2 { None } else { let maybe_code = maybe_code .last() .map(|s| s.to_string()) .map(|c| { c.split("&") .collect::>() .first() .map(|s| s.to_string()) }) .flatten(); maybe_code.map(|s| s.to_string()) } }; let mut s_code = String::new(); for req in server.incoming_requests() { match parser(req.url()) { Some(secret_code) => { s_code = secret_code; break; } None => continue, } } Ok(urlencoding::decode(&s_code)?.to_string()) }