use std::time::{Duration, SystemTime};
use super::issuance::issue_session_auth;
use crate::{
AccessToken, AuthError, Claims, Email, NewUser, NythosResult, Password, PasswordHasher,
RefreshToken, Session, SessionStore, TenantId, TokenSigner, User, UserRepository,
};
#[derive(Debug, Clone)]
pub struct RegisterInput {
tenant_id: TenantId,
email: String,
password: String,
issued_at: SystemTime,
access_token_ttl: Duration,
session_ttl: Duration,
auto_sign_in: bool,
}
impl RegisterInput {
pub fn new(
tenant_id: TenantId,
email: String,
password: String,
issued_at: SystemTime,
access_token_ttl: Duration,
session_ttl: Duration,
) -> Self {
Self {
tenant_id,
email,
password,
issued_at,
access_token_ttl,
session_ttl,
auto_sign_in: true,
}
}
pub const fn tenant_id(&self) -> TenantId {
self.tenant_id
}
pub fn email(&self) -> &str {
&self.email
}
pub fn password(&self) -> &str {
&self.password
}
pub const fn issued_at(&self) -> SystemTime {
self.issued_at
}
pub const fn access_token_ttl(&self) -> Duration {
self.access_token_ttl
}
pub const fn session_ttl(&self) -> Duration {
self.session_ttl
}
pub const fn auto_sign_in(&self) -> bool {
self.auto_sign_in
}
pub fn with_auto_sign_in(mut self, auto_sign_in: bool) -> Self {
self.auto_sign_in = auto_sign_in;
self
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RegisterAuthMaterial {
user: User,
session: Session,
refresh_token: RefreshToken,
access_token: AccessToken,
claims: Claims,
}
impl RegisterAuthMaterial {
pub fn new(
user: User,
session: Session,
refresh_token: RefreshToken,
access_token: AccessToken,
claims: Claims,
) -> Self {
Self {
user,
session,
refresh_token,
access_token,
claims,
}
}
pub fn user(&self) -> &User {
&self.user
}
pub fn session(&self) -> &Session {
&self.session
}
pub fn refresh_token(&self) -> &RefreshToken {
&self.refresh_token
}
pub fn access_token(&self) -> &AccessToken {
&self.access_token
}
pub fn claims(&self) -> &Claims {
&self.claims
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RegisterResult {
user: User,
auth: Option<RegisterAuthMaterial>,
}
impl RegisterResult {
pub fn new(user: User, auth: Option<RegisterAuthMaterial>) -> Self {
Self { user, auth }
}
pub fn user(&self) -> &User {
&self.user
}
pub fn auth(&self) -> Option<&RegisterAuthMaterial> {
self.auth.as_ref()
}
}
pub struct RegisterService<'a, U, S, H, T> {
user_repository: &'a U,
session_store: &'a S,
password_hasher: &'a H,
token_signer: &'a T,
}
impl<'a, U, S, H, T> RegisterService<'a, U, S, H, T>
where
U: UserRepository,
S: SessionStore,
H: PasswordHasher,
T: TokenSigner,
{
pub fn new(
user_repository: &'a U,
session_store: &'a S,
password_hasher: &'a H,
token_signer: &'a T,
) -> Self {
Self {
user_repository,
session_store,
password_hasher,
token_signer,
}
}
pub async fn register(&self, input: RegisterInput) -> NythosResult<RegisterResult> {
let email = Email::parse(input.email())?;
let password = Password::new(input.password())?;
self.ensure_email_available(input.tenant_id(), &email)
.await?;
let password_hash = self.password_hasher.hash(&password).await?;
let user = self
.user_repository
.create(input.tenant_id(), NewUser::new(email), password_hash)
.await?;
let auth = if input.auto_sign_in() {
Some(self.create_auth_material(&input, &user).await?)
} else {
None
};
Ok(RegisterResult::new(user, auth))
}
async fn ensure_email_available(&self, tenant_id: TenantId, email: &Email) -> NythosResult<()> {
if self
.user_repository
.find_by_email(tenant_id, email)
.await?
.is_some()
{
return Err(AuthError::ValidationError(
"user with email already exists in tenant".to_owned(),
));
}
Ok(())
}
async fn create_auth_material(
&self,
input: &RegisterInput,
user: &User,
) -> NythosResult<RegisterAuthMaterial> {
let issued = issue_session_auth(
self.session_store,
self.token_signer,
user.id(),
input.tenant_id(),
input.issued_at(),
input.access_token_ttl(),
input.session_ttl(),
)
.await?;
Ok(RegisterAuthMaterial::new(
user.clone(),
issued.session,
issued.refresh_token,
issued.access_token,
issued.claims,
))
}
}