From c2268c285648ef02ece04de0d9df0813c6d70ff8 Mon Sep 17 00:00:00 2001 From: benj Date: Sat, 24 Dec 2022 00:43:38 -0800 Subject: refactor everything with more abstraction and a nicer interface --- crates/iam/src/api.rs | 43 +++++++++-------- crates/iam/src/command.rs | 10 ++-- crates/iam/src/main.rs | 114 ++++++++++++++++------------------------------ 3 files changed, 65 insertions(+), 102 deletions(-) (limited to 'crates/iam/src') diff --git a/crates/iam/src/api.rs b/crates/iam/src/api.rs index 841aa9e..ace3199 100644 --- a/crates/iam/src/api.rs +++ b/crates/iam/src/api.rs @@ -1,7 +1,7 @@ use crate::ISSUE_TRACKER_LOC; use clap::{Parser, Subcommand, ValueEnum}; use colored::*; -use secd::{IdentityId, OauthProviderName}; +use secd::IdentityId; use serde::{Deserialize, Serialize}; use thiserror; use url::Url; @@ -183,7 +183,6 @@ pub enum AdminObject { }, /// A selected Oauth2.0 provider capable of authenticating identities Oauth2Provider { - provider: OauthProviderName, client_id: String, secret: String, redirect_url: Url, @@ -310,7 +309,7 @@ pub enum CreateObject { secret_code: String, }, #[command( - about = "An action which initiates an identity validation", + about = "An action which initiates an address validation", long_about = "Validation\n\nA validation requires that the identity authenticate in some way, either by providing IAM managed credentials, an external gated mechanism (e.g. email, phone, or hardware key), or through a secondary authentication provider (oauth, saml, ldap, kerberos)." )] Validation { @@ -319,7 +318,21 @@ pub enum CreateObject { method: ValidationMethod, /// The identity against which to associate this validation. A new identity will be created if no identity is provided. #[arg(long, short)] - identity: Option, + identity_id: Option, + }, + #[command( + about = "An action which completes an address validation", + long_about = "Validation Completion\n\nA validation completion depends on an existing address validation, which is validated based on the provided validation id and secret token or secret code" + )] + ValidationCompletion { + /// The validation id against which to complete the validation. + validation_id: Uuid, + /// The secret token for the validation. A token or code must be provided. + #[arg(long, short)] + token: Option, + /// The secret code for the validation. A code or token must be provided. + #[arg(long, short)] + code: Option, }, } @@ -343,26 +356,12 @@ pub enum ValidationMethod { /// Email address which will receive the validation address: String, }, - /// A hardware security key to associate with an identity - HardwareKey, - /// A kerberos ticket to associated with an identity - Kerberos, - /// An oauth2 provider to authenticate (and authorize) an identity - Oauth2 { - provider: OauthProviderName, - /// An optional scope to use for authorization - scope: Option, - /// An optional existing identity to link to this validation request - identity: Option, - }, - /// A phone which an identity may authenticate via SMS or voice + /// A phone which an identity may authenticate via SMS or Voice Phone { /// Whether to use a voice code. Otherwise, uses SMS #[arg(long, short, action)] use_voice: bool, }, - /// A saml provider to authenticate an identity - Saml, } #[derive(Subcommand)] @@ -379,8 +378,8 @@ pub enum GetObject { id: Option, }, Identity { - /// Unique identity id - id: Uuid, + /// Any session corresponding to this identity. + session_token: String, }, Permission { /// Unique permission name @@ -497,7 +496,7 @@ pub struct ConfigProfile { pub name: String, pub store: secd::AuthStore, pub store_conn: String, - pub emailer: secd::AuthEmail, + pub emailer: secd::AuthEmailMessenger, pub email_template_login: Option, pub email_template_signup: Option, } diff --git a/crates/iam/src/command.rs b/crates/iam/src/command.rs index 379e7fb..56734b1 100644 --- a/crates/iam/src/command.rs +++ b/crates/iam/src/command.rs @@ -5,7 +5,7 @@ use crate::{ }; use colored::*; use rand::distributions::{Alphanumeric, DistString}; -use secd::{AuthEmail, AuthStore}; +use secd::{AuthEmailMessenger, AuthStore}; use std::{ fs::{self, File}, io::{self, stdin, stdout, Read, Write}, @@ -48,13 +48,13 @@ pub async fn admin_init(is_interactive: bool) -> Result<()> { let mut cfg = api::Config { profile: vec![api::ConfigProfile { name: "default".to_string(), - store: AuthStore::Sqlite, + 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::AuthEmail::LocalStub, + emailer: secd::AuthEmailMessenger::Local, email_template_login: Some(login_template.display().to_string()), email_template_signup: Some(signup_template.display().to_string()), }], @@ -104,7 +104,7 @@ pub async fn admin_init(is_interactive: bool) -> Result<()> { write!( stdout(), "Email provider {:?}: ", - AuthEmail::VARIANTS + AuthEmailMessenger::VARIANTS .iter() .map(|s| s.to_lowercase()) .collect::>() @@ -112,7 +112,7 @@ pub async fn admin_init(is_interactive: bool) -> Result<()> { stdout().flush()?; input.clear(); stdin().read_line(&mut input)?; - match AuthEmail::from_str(&input.trim()) { + match AuthEmailMessenger::from_str(&input.trim()) { Ok(s) => { cfg.profile[0].emailer = s; break; diff --git a/crates/iam/src/main.rs b/crates/iam/src/main.rs index 4f6316a..ce72072 100644 --- a/crates/iam/src/main.rs +++ b/crates/iam/src/main.rs @@ -10,7 +10,8 @@ use api::{ use clap::Parser; use command::dev_oauth2_listen; use env_logger::Env; -use secd::{Secd, SecdError}; +use secd::{Secd, SecdError, ENV_AUTH_STORE_CONN_STRING}; +use std::str::FromStr; use util::{error_detail, Result}; use uuid::Uuid; @@ -49,16 +50,15 @@ async fn exec() -> Result> { } 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()))?; + // let cfg = util::read_config(args.profile).map_err(|_| CliError::InvalidProfile)?; + std::env::set_var( + ENV_AUTH_STORE_CONN_STRING, + "sqlite:///tmp/store.db?mode=rwc", + // "postgresql://secduser:p4ssw0rd@localhost:5412/secd", + ); + let secd = Secd::init() + .await + .map_err(|e| CliError::SecdInitializationFailure(e.to_string()))?; match rest { Command::Admin { action } => admin(&secd, action).await?, @@ -69,13 +69,13 @@ async fn exec() -> Result> { "4a696b66-6231-4a2f-811c-4448a41473d2", "Code path should be unreachable", ))), - Command::Link { object, unlink } => link(&secd, object, unlink).await?, + Command::Link { object, unlink } => todo!(), Command::Ls { object, name, before, after, - } => list(&secd, object, name, before, after).await?, + } => todo!(), Command::Repl => { unimplemented!() } @@ -90,19 +90,7 @@ async fn admin(secd: &Secd, cmd: AdminAction) -> Result> { 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::Create { object } => todo!(), AdminAction::Seal => { println!("do seal"); None @@ -148,54 +136,31 @@ async fn create(secd: &Secd, cmd: CreateObject) -> Result> { 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(&address).await?, - note: Some(" sent to client".into()), - oauth_auth_url: None, - }) - .ok(), + } => todo!(), + CreateObject::Validation { + method, + identity_id, + } => match method { + ValidationMethod::Email { address } => { + let validation = secd.validate_email(&address, identity_id).await?; - 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() + Some(serde_json::ser::to_string(&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(&session)?.to_string()) + } }) } @@ -215,10 +180,10 @@ async fn get(secd: &Secd, cmd: GetObject) -> Result> { println!("get object group"); None } - GetObject::Identity { id } => { - println!("get object identity"); - None + GetObject::Identity { session_token } => { + Some(serde_json::ser::to_string(&secd.get_identity(&session_token).await?)?.to_string()) } + GetObject::Permission { name, id } => { println!("get object permission"); None @@ -236,8 +201,7 @@ async fn get(secd: &Secd, cmd: GetObject) -> Result> { None } GetObject::Session { secret } => { - println!("get object session"); - None + Some(serde_json::ser::to_string(&secd.get_session(&secret).await?)?.to_string()) } GetObject::Validation { id } => { println!("get object validation"); -- cgit v1.2.3