use std::fmt::Debug;
use fnd::identifier::Uid;
use serde::{Deserialize, Serialize};
use super::{Authn, JwtTokenPair, SigninArgs, SignupArgs, UserHandle, UserIdentifier};
use crate::prelude::*;
#[derive(Clone)]
#[non_exhaustive]
pub struct DefaultAuthn<U: UserAuth> {
authn: Authn<U>,
}
impl<U: UserAuth> DefaultAuthn<U> {
pub const fn new(authn: Authn<U>) -> Self {
Self { authn }
}
}
#[async_trait]
pub trait UserAuth: UserHandle + Clone {
type User: User;
type SignupRequestBody: SignupRequest;
async fn create_user(
&self,
depot: &mut Depot,
user_info: Self::SignupRequestBody,
password_hash: String,
otp_secret: String,
) -> Result<Self::User, ApiError>;
async fn get_user(&self, depot: &mut Depot, user_ident: UserIdentifier) -> Result<Self::User, ApiError>;
}
pub trait User: Send + Debug + Clone + for<'d> Deserialize<'d> + Serialize + ToSchema {
fn user_id(&self) -> Uid;
}
pub trait SignupRequest: Send + Debug + Clone + for<'d> Deserialize<'d> + ToSchema {
fn signup_args(&self) -> SignupArgs;
}
#[derive(Debug, Clone, Serialize, ToSchema)]
pub struct SignupResponse<T: User> {
jwt: JwtTokenPair,
user: T,
otp_qr: String,
}
#[derive(Debug, Clone, Serialize, ToSchema)]
pub struct SigninResponse<T: User> {
jwt: JwtTokenPair,
user: T,
}
#[dipper]
impl<U: Debug> DefaultAuthn<U>
where
U: UserAuth,
{
#[dipper(handler)]
pub async fn sign_up_goal(
&self,
depot: &mut Depot,
user_info: JsonBody<U::SignupRequestBody>,
res: &mut Response,
) -> ApiResponse<SignupResponse<<U as UserAuth>::User>> {
let args = user_info.signup_args();
let reply = self.authn.check_sign_up(args).await?;
let user = self
.authn
.user_handle()
.create_user(depot, user_info.into_inner(), reply.password_hash, reply.otp_secret)
.await?;
let jwt = self.authn.new_jwt_token_pair(user.user_id().clone(), None).await?;
self.authn.set_jwt_auth_cookie(jwt.auth_token.clone(), res);
SignupResponse {
jwt,
user,
otp_qr: reply.otp_qr,
}
.api_response_without_meta()
}
#[dipper(handler)]
pub async fn sign_in_goal(
&self,
depot: &mut Depot,
args: JsonBody<SigninArgs>,
res: &mut Response,
) -> ApiResponse<SigninResponse<<U as UserAuth>::User>> {
let user_identifier = args.user_identifier();
let jwt = self.authn.check_sign_in(depot, args.into_inner()).await?;
let user = self.authn.user_handle().get_user(depot, user_identifier).await?;
self.authn.set_jwt_auth_cookie(jwt.auth_token.clone(), res);
SigninResponse { jwt, user }.api_response_without_meta()
}
pub const fn inner_ref(&self) -> &Authn<U> {
&self.authn
}
pub fn get_user_totp_qr_goal(&self) -> impl Handler {
self.authn.get_user_totp_qr_goal()
}
pub fn refresh_user_totp_qr_goal(&self) -> impl Handler {
self.authn.refresh_user_totp_qr_goal()
}
pub fn jwt_verifying_hoop(&self) -> impl Handler {
self.authn.jwt_verifying_hoop()
}
#[dipper(handler)]
pub async fn refresh_jwt_token_goal(&self, depot: &Depot, res: &mut Response) -> ApiResponse<JwtTokenPair> {
let Some(token_data) = self.authn.obtain_jwt_claims(depot) else {
return api_err!(
ET_ACCESS_AUTHZ,
"There is a problem with the authentication hoop.",
&ERR_PATH_AUTHN
)
.api_response_without_meta();
};
let jwt = self
.authn
.new_jwt_token_pair(token_data.claims.uid.clone(), None)
.await?;
self.authn.set_jwt_auth_cookie(jwt.auth_token.clone(), res);
jwt.api_response_without_meta()
}
#[dipper(handler)]
pub async fn reset_user_password_goal(
&self,
depot: &mut Depot,
args: JsonBody<ResetPasswordArgs>,
) -> ApiResponse<()> {
let args = args.into_inner();
let user_identifier = args.sign.user_identifier();
let _ = self.authn.check_sign_in(depot, args.sign).await?;
self.authn
.reset_user_password(depot, &user_identifier, args.new_password)
.await
.into()
}
#[dipper(handler)]
pub async fn get_user_goal(&self, depot: &mut Depot) -> ApiResponse<<U as UserAuth>::User> {
let Some(token_data) = self.authn.obtain_jwt_claims(depot) else {
return api_err!(
ET_ACCESS_AUTHZ,
"There is a problem with the authentication hoop.",
&ERR_PATH_AUTHN
)
.api_response_without_meta();
};
let user = self
.authn
.user_handle()
.get_user(depot, UserIdentifier::id(token_data.claims.uid.clone()))
.await?;
user.api_response_without_meta()
}
}
impl SigninArgs {
pub fn user_identifier(&self) -> UserIdentifier {
match self {
SigninArgs::username { username, .. } => UserIdentifier::username(username.clone()),
SigninArgs::email { email, .. } => UserIdentifier::email(email.clone()),
SigninArgs::phone { phone, .. } => UserIdentifier::phone(phone.clone()),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResetPasswordArgs {
#[serde(flatten)]
sign: SigninArgs,
new_password: String,
}