diff options
| author | benj <benj@rse8.com> | 2022-12-24 00:43:38 -0800 |
|---|---|---|
| committer | benj <benj@rse8.com> | 2022-12-24 00:43:38 -0800 |
| commit | c2268c285648ef02ece04de0d9df0813c6d70ff8 (patch) | |
| tree | f84ec7ee42f97d78245f26d0c5a0c559cd35e89d /crates/iam | |
| parent | de6339da72af1d61ca5908b780977e2b037ce014 (diff) | |
| download | secdiam-c2268c285648ef02ece04de0d9df0813c6d70ff8.tar secdiam-c2268c285648ef02ece04de0d9df0813c6d70ff8.tar.gz secdiam-c2268c285648ef02ece04de0d9df0813c6d70ff8.tar.bz2 secdiam-c2268c285648ef02ece04de0d9df0813c6d70ff8.tar.lz secdiam-c2268c285648ef02ece04de0d9df0813c6d70ff8.tar.xz secdiam-c2268c285648ef02ece04de0d9df0813c6d70ff8.tar.zst secdiam-c2268c285648ef02ece04de0d9df0813c6d70ff8.zip | |
refactor everything with more abstraction and a nicer interface
Diffstat (limited to '')
| -rw-r--r-- | crates/iam/Cargo.toml | 1 | ||||
| -rw-r--r-- | crates/iam/src/api.rs | 43 | ||||
| -rw-r--r-- | crates/iam/src/command.rs | 10 | ||||
| -rw-r--r-- | crates/iam/src/main.rs | 114 |
4 files changed, 66 insertions, 102 deletions
diff --git a/crates/iam/Cargo.toml b/crates/iam/Cargo.toml index ba642c3..966f341 100644 --- a/crates/iam/Cargo.toml +++ b/crates/iam/Cargo.toml @@ -8,6 +8,7 @@ anyhow = "1.0" async-std = { version = "1.12.0", features = [ "attributes" ] } clap = { version = "4.0.29", features = ["derive"] } colored = "2.0.0" +email_address = "0.2" env_logger = "0.9" home = "0.5.4" log = "0.4" 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<Uuid>, + identity_id: Option<Uuid>, + }, + #[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<String>, + /// The secret code for the validation. A code or token must be provided. + #[arg(long, short)] + code: Option<String>, }, } @@ -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<String>, - /// An optional existing identity to link to this validation request - identity: Option<IdentityId>, - }, - /// 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<Uuid>, }, 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<String>, pub email_template_signup: Option<String>, } 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::<Vec<String>>() @@ -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<Option<String>> { } 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<Option<String>> { "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<Option<String>> { 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<Option<String>> { 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("<secret code> 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::<Vec<&str>>() - .last() - .map(|i| Uuid::parse_str(i).ok()) - .flatten() - .unwrap(); - serde_json::to_string(&Validation { - validation_id, - note: Some( - "<secret code> 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<Option<String>> { 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<Option<String>> { 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"); |
