pub mod default;
mod jwt_token;
mod mfa;
mod password;
mod signin;
mod signup;
mod user;
use std::{ops::Deref, sync::Arc, time::Duration as StdDuration};
pub use jwt_token::*;
pub use mfa::*;
pub use password::*;
pub use signin::*;
pub use signup::*;
pub use user::*;
pub use crate::{
fnd::{identifier::UserIdentifier, secret_key::EnvSecretArgsProvide},
jwt_auth::JWT_AUTH_DATA_KEY,
};
pub struct Authn<U: UserHandle> {
app_name: Arc<String>,
user_handle: Arc<U>,
jwt: Arc<JwtAuthn<U>>,
}
impl<U> Clone for Authn<U>
where
U: UserHandle,
{
fn clone(&self) -> Self {
Self {
app_name: self.app_name.clone(),
user_handle: self.user_handle.clone(),
jwt: self.jwt.clone(),
}
}
}
impl<U> Authn<U>
where
U: UserHandle,
{
pub fn builder(app_name: impl Into<String>, user_handle: U) -> AuthBuilder<U> {
AuthBuilder::new(app_name, user_handle)
}
pub fn user_handle(&self) -> &U {
self.user_handle.as_ref()
}
pub fn jwt(&self) -> Arc<JwtAuthn<U>> {
self.jwt.clone()
}
pub fn app_name(&self) -> &String {
&self.app_name
}
}
impl<U: UserHandle> Deref for Authn<U> {
type Target = Arc<JwtAuthn<U>>;
fn deref(&self) -> &Self::Target {
&self.jwt
}
}
pub struct AuthBuilder<U> {
app_name: String,
user_handle: Arc<U>,
secret_refresh_interval: Option<StdDuration>,
jwt_token_key: Option<String>,
exp_minutes: i64,
}
impl<U> AuthBuilder<U>
where
U: UserHandle,
{
pub fn new(app_name: impl Into<String>, user_handle: U) -> Self {
Self {
app_name: app_name.into(),
user_handle: Arc::new(user_handle),
secret_refresh_interval: None,
jwt_token_key: None,
exp_minutes: 14 * 24 * 60, }
}
pub const fn with_secret_refresh_interval(mut self, secret_refresh_interval: StdDuration) -> Self {
self.secret_refresh_interval = Some(secret_refresh_interval);
self
}
pub fn with_jwt_token_key(mut self, jwt_token_key: String) -> Self {
self.jwt_token_key = Some(jwt_token_key);
self
}
pub const fn with_exp_minutes(mut self, mut exp_minutes: i64) -> Self {
if exp_minutes <= 0 {
exp_minutes = i64::MAX;
}
self.exp_minutes = exp_minutes;
self
}
pub async fn build(self) -> anyhow::Result<Authn<U>> {
let mut jwt = JwtAuthn::new(
self.user_handle.clone(),
self.secret_refresh_interval,
self.jwt_token_key,
)
.await?;
jwt.set_exp_minutes(self.exp_minutes);
Ok(Authn {
app_name: Arc::new(self.app_name),
user_handle: self.user_handle,
jwt: Arc::new(jwt),
})
}
}
#[cfg(test)]
mod tests {
use std::env;
use crate::{
fnd::{authn::*, identifier::Uid, secret_key, secret_key::SecretArgsProvide},
prelude::*,
};
struct TestUserStorage(EnvSecretArgsProvide);
impl TestUserStorage {
fn new() -> Self {
TestUserStorage(EnvSecretArgsProvide::new().unwrap())
}
}
impl SecretArgsProvide for TestUserStorage {
fn secret_key_args(&self) -> anyhow::Result<secret_key::SecretArgs> {
self.0.secret_key_args()
}
}
#[async_trait]
impl UserHandle for TestUserStorage {
async fn get_password_hash(
&self,
_depot: &mut Depot,
_identifier: &UserIdentifier,
) -> Result<UidPasswordHash, ApiError> {
Ok(UidPasswordHash {
user_id: Uid::I64(123),
password_hash:
"$argon2id$v=19$m=19456,t=2,p=1$yh6/gTQf6zOmI/TRpuPPFg$VYHht1v1X/4I4j6N4AESMAiiViO0+imS4Lg8dcWyqk4"
.to_string(),
})
}
async fn get_totp_info(
&self,
_depot: &mut Depot,
_identifier: &UserIdentifier,
) -> Result<UserTotpInfo, ApiError> {
Ok(UserTotpInfo {
user_id: Uid::I64(123),
totp_secret: "BRUO5IP73ZFEWUQSMAGNKZU7QVNYCFBT".to_owned(),
username: "andeya".to_owned(),
enable_2fa: true,
})
}
async fn set_password_hash(
&self,
_depot: &mut Depot,
_identifier: &UserIdentifier,
_password_hash: String,
) -> Result<(), ApiError> {
Ok(())
}
async fn set_totp_secret(
&self,
_depot: &mut Depot,
user_id: Uid,
totp_secret: String,
) -> Result<UserTotpInfo, ApiError> {
Ok(UserTotpInfo {
user_id,
username: "andeya".to_owned(),
totp_secret,
enable_2fa: true,
})
}
async fn enable_2fa(&self, _depot: &mut Depot, _user_id: Uid, _enable_2fa: bool) -> Result<(), ApiError> {
Ok(())
}
}
async fn new_authn() -> Authn<TestUserStorage> {
env::set_var("DIPPER_SECRET_KEY", "123456");
env::set_var("DIPPER_SECRET_SATL", "x-mod");
Authn::builder("Dipper".to_owned(), TestUserStorage::new())
.build()
.await
.unwrap()
}
#[tokio::test]
async fn test_totp() {
let authn = new_authn().await;
let totp_secret = authn.obtain_pub_totp_secret().await;
let totp_token = authn.obtain_totp_token(TotpData::email(totp_secret.clone())).unwrap();
println!("totp_secret={totp_secret}, totp_token={totp_token}");
}
#[tokio::test]
async fn test_captcha() {
let authn = new_authn().await;
let captcha = authn.obtain_captcha().await;
println!("obtain_captcha={:?}", captcha);
let r = authn
.verify_captcha(&CaptchaPair {
captcha: "DMH7".to_owned(),
captcha_sign: "HuLkAk8CdOktP5tr09ZKPQ".to_owned(),
})
.await;
println!("verify_captcha={r:?}");
}
#[test]
fn signup_args_json_view() {
let signup_args = SignupArgs::email {
username: "andeya".to_owned(),
password: "super_secret_password".to_owned(),
email: "andeyalee@outlook.com".to_owned(),
totp_token: "123456".to_owned(),
};
println!("{}", serde_json::to_string_pretty(&signup_args).unwrap())
}
#[test]
fn signin_args_json_view() {
let signin_args = SigninArgs::email {
email: "andeyalee@outlook.com".to_owned(),
factor: Factor::totp_token {
totp_token: "123456".to_owned(),
captcha: None,
},
};
println!("{}", serde_json::to_string_pretty(&signin_args).unwrap())
}
}