1
0

Working Login & Register functionality

This commit is contained in:
2025-02-08 16:35:07 +02:00
parent f65312209c
commit a7a474743c
37 changed files with 1208 additions and 802 deletions

View File

@ -7,7 +7,7 @@ edition = "2024"
thiserror = "2.0.11"
argon2 = { version = "0.5.3", features = ["std"] }
garde = { version = "0.22.0", features = ["email", "url", "derive"] }
derive_more = { version = "1.0.0", features = ["deref", "deref_mut", "into"] }
derive_more = { version = "2.0.1", features = ["deref", "deref_mut", "into"] }
[dependencies.data]
path = "../data"

View File

@ -11,6 +11,7 @@ where
UR: UserRepository<C> + Sync,
{
driver: D,
// connection: Option<C>,
_user_repository: PhantomData<UR>,
}
@ -23,14 +24,24 @@ where
pub const fn new(driver: D) -> Self {
Self {
driver,
// connection: None,
_user_repository: PhantomData,
}
}
// async fn connection(&mut self) -> Result<&C> {
// if let Some(ref c) = self.connection {
// Ok(c)
// } else {
// self.connection = Some(self.driver.open_connection().await?);
// self.connection().await
// }
// }
}
impl<D, C, UR> AuthenticationRepository for AuthenticationAdapter<D, C, UR>
where
C: Send,
C: Send, //+ Sync,
D: Connect<Connection = C> + Sync,
UR: UserRepository<C> + Sync,
{

View File

@ -1,12 +1,13 @@
use super::Authenticated;
use data::user;
pub use data::Validation;
use data::{BoxDynError, user};
use derive_more::{Deref, Into};
use garde::Validate;
pub type Result<T = (), E = Error> = std::result::Result<T, E>;
pub trait AuthenticationContract {
pub trait AuthenticationContract: Send {
fn name_available(&self, name: Name) -> impl Future<Output = Result> + Send;
fn email_available(&self, email: Email) -> impl Future<Output = Result> + Send;
@ -45,78 +46,110 @@ pub enum Error {
InvalidPassword(data::BoxDynError),
#[error("data source error: {0}")]
Repository(data::BoxDynError),
#[error(transparent)]
Other(data::BoxDynError),
}
pub type ReturnError<T = String> = (T, BoxDynError);
#[derive(Clone)]
pub enum Login {
Name(Name),
Email(Email),
}
impl AsRef<str> for Login {
fn as_ref(&self) -> &str {
match self {
Self::Name(name) => name.as_ref(),
Self::Email(email) => email.as_ref(),
}
}
}
impl TryFrom<String> for Login {
type Error = (String, &'static str);
type Error = ReturnError;
fn try_from(value: String) -> Result<Self, Self::Error> {
let value = match Email::try_from(value) {
Ok(x) => return Ok(Self::Email(x)),
Err((s, _)) => s,
Err((v, _)) => v,
};
match Name::try_from(value) {
Ok(x) => Ok(Self::Name(x)),
Err((s, _)) => Err((s, "login is invalid")),
Err((v, _)) => Err((v, "login is invalid".into())),
}
}
}
impl From<Login> for String {
fn from(val: Login) -> Self {
match val {
Login::Name(name) => name.0.into(),
Login::Email(email) => email.0.into(),
}
}
}
#[derive(Clone, Deref, Into)]
pub struct Name(user::Name);
impl AsRef<str> for Name {
fn as_ref(&self) -> &str {
self.0.as_ref()
}
}
impl TryFrom<String> for Name {
type Error = (String, Box<dyn std::error::Error>);
type Error = ReturnError;
fn try_from(value: String) -> Result<Self, Self::Error> {
#[derive(Validate)]
#[garde(transparent)]
struct Username<'a>(#[garde(alphanumeric, length(chars, min = 2, max = 31))] &'a str);
struct Username<'a>(#[garde(ascii, length(chars, min = 2, max = 31))] &'a str);
if let Err(e) = Username(&value).validate() {
return Err((value, e.into()));
}
match user::Name::try_from(value) {
Ok(x) => Ok(Self(x)),
Err((s, e)) => Err((s, e.into())),
match Username(value.as_str()).validate() {
Ok(()) => (),
Err(e) => return Err((value, e.into())),
}
Ok(Self(user::Name::new(value)?))
}
}
#[derive(Clone, Deref, Into)]
pub struct Email(user::Email);
impl AsRef<str> for Email {
fn as_ref(&self) -> &str {
self.0.as_ref()
}
}
impl TryFrom<String> for Email {
type Error = (String, Box<dyn std::error::Error>);
type Error = ReturnError;
fn try_from(value: String) -> Result<Self, Self::Error> {
#[derive(Validate)]
#[garde(transparent)]
pub struct Email<'a>(#[garde(email, length(chars, max = 255))] &'a str);
if let Err(e) = Email(&value).validate() {
return Err((value, e.into()));
}
match user::Email::try_from(value) {
Ok(x) => Ok(Self(x)),
Err((s, e)) => Err((s, e.into())),
match Email(value.as_str()).validate() {
Ok(()) => (),
Err(e) => return Err((value, e.into())),
}
Ok(Self(user::Email::new(value)?))
}
}
#[derive(Clone, Deref, Into)]
pub struct Password(String);
impl AsRef<str> for Password {
fn as_ref(&self) -> &str {
self.0.as_ref()
}
}
impl TryFrom<String> for Password {
type Error = (String, &'static str);
type Error = ReturnError;
fn try_from(value: String) -> Result<Self, Self::Error> {
if value.chars().count() >= 8 {
if value.chars().count() > 7 {
Ok(Self(value))
} else {
Err((value, "password must be 8 characters or more"))
Err((value, "password must be longer than 7 characters".into()))
}
}
}

View File

@ -3,7 +3,7 @@ use data::user::{Email, Name, New, Unique, User};
use derive_more::{Deref, DerefMut};
#[derive(Deref, DerefMut, Debug)]
#[derive(Debug, Clone, Deref, DerefMut)]
pub struct Authenticated(pub(super) User);
pub trait AuthenticationRepository {

View File

@ -1,6 +1,6 @@
use super::{
Authenticated, AuthenticationContract, AuthenticationRepository, Email, Error, Get, Login,
LoginData, Name, RegisterData, Result,
LoginData, Name, RegisterData, Result, Validation,
};
use argon2::{
@ -48,7 +48,7 @@ where
.is_some()
{
return Err(Error::NameExists);
};
}
Ok(())
}
async fn email_available(&self, email: Email) -> Result {
@ -60,7 +60,7 @@ where
.is_some()
{
return Err(Error::EmailExists);
};
}
Ok(())
}
@ -89,11 +89,11 @@ where
self.email_available(data.email.clone()).await?;
// Get PHC string ($argon2id$v=19$...)
let password = Argon2::default()
let phc = Argon2::default()
.hash_password(data.password.as_bytes(), &SaltString::generate(&mut OsRng))?
.to_string()
.try_into()
.map_err(|(_, e)| Error::InvalidPassword(Box::from(e)))?;
.to_string();
let password = data::user::Password::new(phc)
.map_err(|(_, e)| Error::InvalidPassword(e))?;
let user = self
.repository

View File

@ -4,3 +4,5 @@ pub use authentication::{
Authenticated, AuthenticationAdapter, AuthenticationContract, AuthenticationRepository,
AuthenticationService,
};
// pub