aboutsummaryrefslogtreecommitdiff
path: root/crates/secd/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--crates/secd/src/lib.rs170
1 files changed, 81 insertions, 89 deletions
diff --git a/crates/secd/src/lib.rs b/crates/secd/src/lib.rs
index 54759a5..eb5d33d 100644
--- a/crates/secd/src/lib.rs
+++ b/crates/secd/src/lib.rs
@@ -12,37 +12,26 @@ use client::{
Store, StoreError,
},
};
+use config::Config;
use email_address::EmailAddress;
-use lettre::message::Mailbox;
use log::{error, info, warn};
use serde::{Deserialize, Serialize};
-use serde_with::serde_as;
-use std::{
- env::{set_var, var},
- str::FromStr,
- sync::Arc,
+use serde_with::{
+ base64::{Base64, UrlSafe},
+ formats::Unpadded,
+ serde_as,
};
+use std::{env::var, fs::read_to_string, str::FromStr, sync::Arc};
use strum_macros::{Display, EnumString, EnumVariantNames};
use time::OffsetDateTime;
use util::crypter::{Crypter, CrypterError};
use uuid::Uuid;
-pub const ENV_AUTH_STORE_CONN_STRING: &str = "SECD_AUTH_STORE_CONN_STRING";
-pub const ENV_CRYPTER_SECRET_KEY: &str = "SECD_CRYPTER_SECRET_KEY";
-pub const ENV_EMAIL_ADDRESS_FROM: &str = "SECD_EMAIL_ADDRESS_FROM";
-pub const ENV_EMAIL_ADDRESS_REPLYTO: &str = "SECD_EMAIL_ADDRESS_REPLYTO";
-pub const ENV_EMAIL_MESSENGER: &str = "SECD_EMAIL_MESSENGER";
-pub const ENV_EMAIL_MESSENGER_CLIENT_ID: &str = "SECD_EMAIL_MESSENGER_CLIENT_ID";
-pub const ENV_EMAIL_MESSENGER_CLIENT_SECRET: &str = "SECD_EMAIL_MESSENGER_CLIENT_SECRET";
-pub const ENV_EMAIL_SENDGRID_API_KEY: &str = "SECD_EMAIL_SENDGRID_API_KEY";
-pub const ENV_EMAIL_SIGNIN_MESSAGE: &str = "SECD_EMAIL_SIGNIN_MESSAGE";
-pub const ENV_EMAIL_SIGNUP_MESSAGE: &str = "SECD_EMAIL_SIGNUP_MESSAGE";
-pub const ENV_SPICE_SECRET: &str = "SECD_SPICE_SECRET";
-pub const ENV_SPICE_SERVER: &str = "SECD_SPICE_SERVER";
-
const ADDRESSS_VALIDATION_CODE_SIZE: u8 = 6;
const ADDRESS_VALIDATION_ALLOWS_ATTEMPTS: u8 = 5;
const ADDRESS_VALIDATION_IDENTITY_SURJECTION: bool = true;
+const API_TOKEN_SIZE_BYTES: usize = 64;
+const CREDENTIAL_PUBLIC_PART_BYTES: usize = 16;
const CRYPTER_SECRET_KEY_DEFAULT: &str = "sup3rs3cr3t";
const EMAIL_VALIDATION_DURATION: i64 = 60 /* seconds*/ * 15 /* minutes */;
const SESSION_DURATION: i64 = 60 /* seconds*/ * 60 /* minutes */ * 24 /* hours */ * 360 /* days */;
@@ -55,7 +44,6 @@ pub type IdentityId = Uuid;
pub type MotifId = Uuid;
pub type PhoneNumber = String;
pub type RefId = Uuid;
-pub type SessionToken = String;
#[derive(Debug, derive_more::Display, thiserror::Error)]
pub enum SecdError {
@@ -67,11 +55,14 @@ pub enum SecdError {
AddressValidationExpiredOrConsumed,
CredentialAlreadyExists,
+ InvalidCredential,
+ CredentialIsNotApiToken,
CrypterError(#[from] CrypterError),
TooManyIdentities,
IdentityNotFound,
+ IdentityAlreadyExists,
EmailMessengerError(#[from] EmailMessengerError),
InvalidEmaillAddress(#[from] email_address::Error),
@@ -79,10 +70,13 @@ pub enum SecdError {
FailedToProvideSessionIdentity(String),
InvalidSession,
+ ParseAssetFileError(String),
+
StoreError(#[from] StoreError),
StoreInitFailure(String),
FailedToDecodeInput(#[from] hex::FromHexError),
+ DecodeError(String),
AuthorizationNotSupported(String),
SpiceClient(#[from] SpiceError),
@@ -97,11 +91,20 @@ pub struct Secd {
cfg: Cfg,
}
+#[derive(Debug, Deserialize)]
struct Cfg {
- email_address_from: Option<Mailbox>,
- email_address_replyto: Option<Mailbox>,
- email_signup_message: Option<String>,
+ auth_store_conn: String,
+ crypter_secret_key: Option<String>,
+ email_address_from: Option<String>,
+ email_address_replyto: Option<String>,
+ email_messenger: Option<String>,
+ email_sendgrid_api_key: Option<String>,
+ email_signin_message_asset_loc: Option<String>,
+ email_signup_message_asset_loc: Option<String>,
email_signin_message: Option<String>,
+ email_signup_message: Option<String>,
+ spice_secret: String,
+ spice_server: String,
}
#[async_trait]
@@ -191,9 +194,9 @@ pub struct AddressValidation {
#[serde(with = "time::serde::timestamp::option")]
pub validated_at: Option<OffsetDateTime>,
pub attempts: i32,
- #[serde_as(as = "serde_with::hex::Hex")]
+ #[serde_as(as = "Base64<UrlSafe, Unpadded>")]
hashed_token: Vec<u8>,
- #[serde_as(as = "serde_with::hex::Hex")]
+ #[serde_as(as = "Base64<UrlSafe, Unpadded>")]
hashed_code: Vec<u8>,
}
@@ -225,38 +228,19 @@ pub struct Credential {
}
#[serde_as]
-#[derive(Debug, Display, Serialize, Deserialize, EnumString)]
+#[derive(Clone, Debug, Display, Serialize, Deserialize, EnumString)]
pub enum CredentialType {
+ ApiToken { public: String, private: String },
Passphrase { key: String, value: String },
- Oidc { value: String },
- OneTimeCodes { codes: Vec<String> },
- // Totp {
- // #[serde_as(as = "DisplayFromStr")]
- // url: Url,
- // code: String,
- // },
- WebAuthn { value: String },
-}
-
-struct SecuredCredential {
- pub id: CredentialId,
- pub identity_id: IdentityId,
- pub t: CredentialType,
- pub created_at: OffsetDateTime,
- pub revoked_at: Option<OffsetDateTime>,
- pub deleted_at: Option<OffsetDateTime>,
-}
-
-enum SecuredCredentialType {
- Passphrase { key: String, value: String },
- Oidc { value: String },
- OneTimeCodes { codes: Vec<String> },
+ Session { key: String, secret: String },
+ // Oidc { key: String, value: String },
+ // OneTimeCodes { key: String, codes: Vec<String> },
// Totp {
// #[serde_as(as = "DisplayFromStr")]
// url: Url,
// code: String,
// },
- WebAuthn { value: String },
+ // WebAuthn { value: String },
}
#[serde_with::skip_serializing_none]
@@ -265,6 +249,8 @@ pub struct Identity {
pub id: IdentityId,
pub address_validations: Vec<AddressValidation>,
pub credentials: Vec<Credential>,
+ #[serde(skip_serializing_if = "Vec::is_empty")]
+ pub new_credentials: Vec<Credential>,
pub rules: Vec<String>, // TODO: rules for (e.g. mfa reqs)
pub metadata: Option<String>,
#[serde(with = "time::serde::timestamp")]
@@ -273,20 +259,22 @@ pub struct Identity {
pub deleted_at: Option<OffsetDateTime>,
}
-#[serde_as]
-#[serde_with::skip_serializing_none]
-#[derive(Debug, Serialize)]
-pub struct Session {
- pub identity_id: IdentityId,
- #[serde_as(as = "serde_with::hex::Hex")]
- #[serde(skip_serializing_if = "Vec::is_empty")]
- pub token: Vec<u8>,
- #[serde(with = "time::serde::timestamp")]
- pub created_at: OffsetDateTime,
- #[serde(with = "time::serde::timestamp")]
- pub expired_at: OffsetDateTime,
- #[serde(with = "time::serde::timestamp::option")]
- pub revoked_at: Option<OffsetDateTime>,
+impl Cfg {
+ fn resolve(&mut self) -> Result<(), SecdError> {
+ if let Some(path) = &self.email_signin_message_asset_loc {
+ self.email_signin_message = Some(read_to_string(path).map_err(|err| {
+ SecdError::ParseAssetFileError(format!("Email Sign In Asset [{}]: {}", path, err))
+ })?);
+ }
+
+ if let Some(path) = &self.email_signup_message_asset_loc {
+ self.email_signup_message = Some(read_to_string(path).map_err(|err| {
+ SecdError::ParseAssetFileError(format!("Email Sign In Asset [{}]: {}", path, err))
+ })?);
+ }
+
+ Ok(())
+ }
}
impl Secd {
@@ -294,16 +282,28 @@ impl Secd {
///
/// Initialize SecD with the specified configuration, established the necessary
/// constraints, persistance stores, and options.
- pub async fn init(z_schema: Option<&str>) -> Result<Self, SecdError> {
- let auth_store = AuthStore::from(var(ENV_AUTH_STORE_CONN_STRING).ok());
+ pub async fn init(cfg_path: Option<&str>, z_schema: Option<&str>) -> Result<Self, SecdError> {
+ let mut cfg: Cfg = Config::builder()
+ .add_source(config::File::with_name(cfg_path.unwrap_or(
+ &var("SECD_CONFIG_PATH").expect("coud not read SECD_CONFIG_PATH from environment"),
+ )))
+ .build()
+ .unwrap()
+ .try_deserialize()
+ .expect("failed to retrieve secd config.toml");
+
+ cfg.resolve()?;
+
+ let auth_store = AuthStore::from(Some(cfg.auth_store_conn.clone()));
let email_messenger = AuthEmailMessenger::from_str(
- &var(ENV_EMAIL_MESSENGER).unwrap_or(AuthEmailMessenger::Local.to_string()),
+ &cfg.email_messenger
+ .clone()
+ .unwrap_or(AuthEmailMessenger::Local.to_string()),
)
.expect("unreachable f4ad0f48-0812-427f-b477-0f9c67bb69c5");
- let crypter_secret_key = var(ENV_CRYPTER_SECRET_KEY).unwrap_or_else(|_| {
+ let crypter_secret_key = cfg.crypter_secret_key.clone().unwrap_or_else(|| {
warn!(
- "NO CRYPTER KEY PROVIDED, USING DEFAULT KEY. DO NOT USE THIS KEY IN PRODUCTION. PROVIDE A UNIQUE SECRET KEY BY SETTING THE ENVIORNMENT VARIABLE {}. THE DEFAULT KEY IS: {}",
- ENV_CRYPTER_SECRET_KEY,
+ "NO CRYPTER KEY PROVIDED, USING DEFAULT KEY. DO NOT USE THIS KEY IN PRODUCTION. PROVIDE A UNIQUE SECRET KEY BY SETTING THE CONFIGURATION KEY. THE DEFAULT KEY IS: {}",
CRYPTER_SECRET_KEY_DEFAULT,
);
CRYPTER_SECRET_KEY_DEFAULT.to_string()
@@ -312,17 +312,6 @@ impl Secd {
info!("starting client with auth_store: {:?}", auth_store);
info!("starting client with email_messenger: {:?}", auth_store);
- let cfg = Cfg {
- email_address_from: var(ENV_EMAIL_ADDRESS_FROM)
- .ok()
- .and_then(|s| s.parse().ok()),
- email_address_replyto: var(ENV_EMAIL_ADDRESS_REPLYTO)
- .ok()
- .and_then(|s| s.parse().ok()),
- email_signup_message: var(ENV_EMAIL_SIGNUP_MESSAGE).ok(),
- email_signin_message: var(ENV_EMAIL_SIGNIN_MESSAGE).ok(),
- };
-
let store = match auth_store {
AuthStore::Sqlite { conn } => {
if z_schema.is_some() {
@@ -331,7 +320,7 @@ impl Secd {
));
}
- SqliteClient::new(
+ SqliteClient::new_ref(
sqlx::sqlite::SqlitePoolOptions::new()
.connect(&conn)
.await
@@ -342,7 +331,7 @@ impl Secd {
.await
}
AuthStore::Postgres { conn } => {
- PgClient::new(
+ PgClient::new_ref(
sqlx::postgres::PgPoolOptions::new()
.connect(&conn)
.await
@@ -352,7 +341,7 @@ impl Secd {
)
.await
}
- rest @ _ => {
+ rest => {
error!(
"requested an AuthStore which has not yet been implemented: {:?}",
rest
@@ -362,19 +351,22 @@ impl Secd {
};
let email_sender = match email_messenger {
- AuthEmailMessenger::Local => LocalMailer::new(),
- AuthEmailMessenger::Sendgrid => Sendgrid::new(
- var(ENV_EMAIL_SENDGRID_API_KEY).expect("No SENDGRID_API_KEY provided"),
+ AuthEmailMessenger::Local => LocalMailer::new_ref(),
+ AuthEmailMessenger::Sendgrid => Sendgrid::new_ref(
+ cfg.email_sendgrid_api_key
+ .clone()
+ .expect("No SENDGRID_API_KEY provided"),
),
_ => unimplemented!(),
};
let spice = match z_schema {
Some(schema) => {
- let c: Arc<Spice> = Arc::new(Spice::new().await);
+ let c: Arc<Spice> =
+ Arc::new(Spice::new(cfg.spice_secret.clone(), cfg.spice_server.clone()).await);
c.write_schema(schema)
.await
- .expect("failed to write authorization schema".into());
+ .unwrap_or_else(|_| panic!("{}", "failed to write authorization schema"));
Some(c)
}
None => None,