#![cfg_attr(docsrs, feature(doc_cfg))]
#![doc = include_str!("../README.md")]
#![warn(clippy::all, clippy::pedantic)]
#![allow(clippy::missing_errors_doc, clippy::missing_panics_doc)]
use anyhow::bail;
use regex::Regex;
pub static ACCOUNT_REGEX: std::sync::LazyLock<Regex> = std::sync::LazyLock::new(|| {
Regex::new(r"^[a-z][a-z0-9_]{0,13}[a-z0-9]$").expect("failed to create regex")
});
pub static SUBDOMAIN_REGEX: std::sync::LazyLock<Regex> = std::sync::LazyLock::new(|| {
Regex::new(r"^[a-z][a-z0-9\-]{0,35}[a-z0-9][.]$").expect("failed to create regex")
});
use argon2::Argon2;
use opaque_ke::ciphersuite::CipherSuite;
pub use chacha20poly1305::aead::OsRng;
pub use opaque_ke::ServerSetup;
pub(crate) const ZEROED_KEY: [u8; 32] = [0u8; 32];
pub const EXP_LEN: usize = 8;
#[cfg(feature = "core")]
pub(crate) const SIG_LEN: usize = 64;
pub(crate) const MAC_LEN: usize = 32;
#[cfg(feature = "core")]
pub mod keys;
pub mod login;
pub mod registration;
pub mod token;
#[cfg(feature = "core")]
mod core;
#[cfg(feature = "core")]
pub use core::Auth;
#[cfg(feature = "core")]
pub mod recovery;
#[cfg(feature = "client")]
mod client;
#[cfg(feature = "client")]
pub use client::AuthClient;
pub struct DefaultCipherSuite;
impl CipherSuite for DefaultCipherSuite {
type OprfCs = opaque_ke::Ristretto255;
type KeyExchange = opaque_ke::TripleDh<opaque_ke::Ristretto255, opaque_sha2::Sha512>;
type Ksf = Argon2<'static>;
}
pub fn validate_account(account: &str) -> anyhow::Result<String> {
let lowercase_account = account.to_ascii_lowercase();
if !ACCOUNT_REGEX.is_match(&lowercase_account) {
bail!("invalid account name");
}
Ok(lowercase_account)
}
pub fn validate_domain(base_domains: &Vec<String>, domain: &str) -> bool {
for base_domain in base_domains {
if domain.ends_with(base_domain) {
let mut domain = domain.to_string();
domain.truncate(domain.len() - base_domain.len());
return SUBDOMAIN_REGEX.is_match(&domain);
}
}
false
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn validate_account_test() -> anyhow::Result<()> {
let account = validate_account("my_account")?;
assert_eq!(account, "my_account");
let account = validate_account("MyAccount")?;
assert_eq!(account, "myaccount");
let account = validate_account("myAccount123")?;
assert_eq!(account, "myaccount123");
let account = validate_account("_my_account");
assert!(account.is_err());
let account = validate_account("my_account_");
assert!(account.is_err());
let account = validate_account("1my_account");
assert!(account.is_err());
let account = validate_account("my-account");
assert!(account.is_err());
let account = validate_account("@#$%1234");
assert!(account.is_err());
Ok(())
}
#[test]
fn validate_domain_test() {
let app_domains = vec!["example.com".to_string(), "sub.example.com".to_string()];
assert!(validate_domain(&app_domains, "my.example.com"));
assert!(validate_domain(&app_domains, "asdf.example.com"));
assert!(validate_domain(&app_domains, "as-df.example.com"));
assert!(!validate_domain(&app_domains, "asdf..example.com"));
assert!(!validate_domain(&app_domains, "asdf-example.com"));
assert!(!validate_domain(&app_domains, "asdf-.example.com"));
assert!(!validate_domain(&app_domains, "asdf-.example.com"));
assert!(!validate_domain(&app_domains, "asdf.sub.example.com"));
assert!(!validate_domain(&app_domains, "a-sdf.sub.example.com"));
}
}