Skip to content

Commit

Permalink
Improve AuthState
Browse files Browse the repository at this point in the history
  • Loading branch information
Almaju committed Feb 7, 2024
1 parent dc05193 commit a3e0541
Show file tree
Hide file tree
Showing 19 changed files with 187 additions and 146 deletions.
13 changes: 11 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ resolver = "2"

members = [
"auth",
"framework",
"framework",
"lab",
]
4 changes: 3 additions & 1 deletion auth/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ serde = "1.0.196"
serde_json = "1.0.113"
sha2 = "0.10.8"
uuid = { version = "1.7.0", features = ["serde", "v4"] }
wasm-bindgen = { version = "0.2.90", optional = true }

[dev-dependencies]
axum-test = "14.3.0"
Expand All @@ -26,6 +27,7 @@ serde_json = "1.0.113"
tokio = { version = "1.0", features = ["full"] }

[features]
default = ["axum", "mongodb"]
default = ["axum", "mongodb", "wasm"]
axum = ["dep:axum"]
mongodb = ["dep:futures", "dep:mongodb"]
wasm = ["dep:wasm-bindgen"]
4 changes: 2 additions & 2 deletions auth/src/application/auth_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ where
AuthStore::push(runtime, &new_events).await?;

// Recompute user projection
let state = AuthState(new_events);
if let Some(user_id) = state.user_id() {
let state = AuthState::new(new_events);
if let Some(user_id) = state.user_id {
recompute_user_projection(runtime, &user_id, new_events).await;
}

Expand Down
1 change: 0 additions & 1 deletion auth/src/application/auth_port.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ pub use crate::application::auth_projection::User;
pub use crate::domain::auth_event::Credentials;
pub use crate::domain::auth_scalar::Email;
pub use crate::domain::{auth_event::AuthEvent, auth_scalar::UserId};
use chrono::{DateTime, Utc};
use framework::*;
use serde::{Deserialize, Serialize};

Expand Down
82 changes: 3 additions & 79 deletions auth/src/clients/axum/middleware.rs
Original file line number Diff line number Diff line change
@@ -1,96 +1,20 @@
use super::error::ApiError;
use crate::application::auth_command::{AuthCommandError, LoginCommand, RegisterCommand};
use crate::application::auth_port::*;
use crate::application::auth_query::{GetJwtByEmail, GetJwtByEmailError};
use axum::middleware::{self, FromFnLayer};
use axum::{
body::Body,
extract::{Request, State},
http::{header, StatusCode},
http::header,
middleware::Next,
response::{IntoResponse, Response},
routing::post,
Json, Router,
response::Response,
};
use framework::*;
use std::sync::Arc;
use std::task::{Context, Poll};
use tower::{Layer, Service};

// struct AuthLayer<R> {
// runtime: Arc<R>,
// }

// impl<R, S> Layer<S> for AuthLayer<R> {
// type Service = AuthService<R, S>;

// fn layer(&self, inner: S) -> Self::Service {
// AuthService {
// inner,
// runtime: self.runtime.clone(),
// }
// }
// }

// #[derive(Clone)]
// struct AuthService<R, S> {
// inner: S,
// runtime: Arc<R>,
// }

// impl<R, S, B> Service<Request<B>> for AuthService<R, S>
// where
// R: JwtPort,
// S: Service<Request<B>>,
// {
// type Response = S::Response;
// type Error = S::Error;
// type Future = S::Future;

// fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
// self.inner.poll_ready(cx)
// }

// fn call(&mut self, req: Request<B>) -> Self::Future {
// let token = req
// .headers()
// .get(header::AUTHORIZATION)
// .and_then(|auth_header| auth_header.to_str().ok())
// .and_then(|auth_value| {
// if auth_value.starts_with("Bearer ") {
// Some(auth_value[7..].to_owned())
// } else {
// None
// }
// })
// .map(|s| Jwt(s))
// .unwrap(); // TODO: handle error

// let user =
// JwtPort::verify(self.runtime.as_ref(), &token).map_err(|_| ApiError::InvalidJwt)?;

// req.extensions_mut().insert(user);

// self.inner.call(req)
// }
// }

// pub struct AuthLayer;

// impl AuthLayer {
// pub fn new<F, R, T>(runtime: Arc<R>) -> FromFnLayer<F, R, T>
// where
// R: JwtPort,
// {
// middleware::from_fn_with_state(runtime, auth_middleware)
// }
// }

pub async fn auth_middleware<R>(
State(runtime): State<Arc<R>>,
mut req: Request<Body>,
next: Next,
) -> Result<impl IntoResponse, ApiError>
) -> Result<Response, ApiError>
where
R: Runtime + JwtPort,
{
Expand Down
5 changes: 5 additions & 0 deletions auth/src/clients/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,8 @@ pub use auth_runtime::*;
mod axum;
#[cfg(feature = "axum")]
pub use axum::*;

#[cfg(feature = "wasm")]
mod wasm;
#[cfg(feature = "wasm")]
pub use wasm::*;
9 changes: 9 additions & 0 deletions auth/src/clients/wasm/api.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
struct AuthApi {
linkedin: LinkedInApi,

Check warning on line 5 in auth/src/clients/wasm/api.rs

View workflow job for this annotation

GitHub Actions / Check

field `linkedin` is never read

Check warning on line 5 in auth/src/clients/wasm/api.rs

View workflow job for this annotation

GitHub Actions / Test Suite

field `linkedin` is never read
}

#[wasm_bindgen]
struct LinkedInApi;
1 change: 1 addition & 0 deletions auth/src/clients/wasm/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod api;
4 changes: 4 additions & 0 deletions auth/src/domain/auth_config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pub struct AuthConfig {

Check warning on line 1 in auth/src/domain/auth_config.rs

View workflow job for this annotation

GitHub Actions / Check

struct `AuthConfig` is never constructed

Check warning on line 1 in auth/src/domain/auth_config.rs

View workflow job for this annotation

GitHub Actions / Test Suite

struct `AuthConfig` is never constructed
pub jwt_secret: String,
pub jwt_expiration: i64,
}
8 changes: 4 additions & 4 deletions auth/src/domain/auth_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ pub enum AuthError {

impl Message<AuthEvent, AuthError> for AuthMessage {
fn send(&self, events: &[AuthEvent]) -> Result<Vec<AuthEvent>, AuthError> {
let state = AuthState(events);
let state = AuthState::new(events);
match self {
AuthMessage::Register { method } => {
if state.is_registered() {
if state.is_registered {
Err(AuthError::AlreadyRegistered)
} else {
let user_id = UserId::default();
Expand All @@ -59,8 +59,8 @@ impl Message<AuthEvent, AuthError> for AuthMessage {
AuthMessage::LogIn { method } => {
let RegisterMethod::EmailPassword { password, .. } = method;

if let Some(user_id) = state.user_id() {
if state.verify_password(password) {
if let Some(user_id) = state.user_id {
if state.password_hash == Some(&password.into()) {
Ok(vec![AuthEvent::LoggedIn {
at: chrono::Utc::now(),
user_id: user_id.clone(),
Expand Down
2 changes: 1 addition & 1 deletion auth/src/domain/auth_scalar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ pub enum PasswordError {
InvalidLength,
}

#[derive(Serialize, Deserialize, PartialEq)]
#[derive(PartialEq, Serialize, Deserialize)]
pub struct PasswordHash([u8; 32]);

impl From<Password> for PasswordHash {
Expand Down
62 changes: 25 additions & 37 deletions auth/src/domain/auth_state.rs
Original file line number Diff line number Diff line change
@@ -1,46 +1,34 @@
use super::{
auth_event::{AuthEvent, Credentials},
auth_scalar::{Password, PasswordHash, UserId},
auth_scalar::{PasswordHash, UserId},
};

pub struct AuthState<'a>(pub &'a [AuthEvent]);
#[derive(Default)]
pub struct AuthState<'a> {
pub is_registered: bool,
pub password_hash: Option<&'a PasswordHash>,
pub user_id: Option<&'a UserId>,
}

impl<'a> AuthState<'a> {
pub fn iter(&self) -> std::slice::Iter<AuthEvent> {
self.0.iter()
}

pub fn is_registered(&self) -> bool {
return self
.iter()
.any(|event| matches!(event, AuthEvent::Registered { .. }));
}

pub fn verify_password(&self, password: &Password) -> bool {
let password_hash: &PasswordHash = &password.into();
let current_password_hash = self
.iter()
.filter_map(|event| match event {
AuthEvent::Registered { credentials, .. } => Some(credentials),
_ => None,
})
.map(|credentials| match credentials {
Credentials::EmailPassword { password, .. } => password_hash,
})
.last();

match current_password_hash {
Some(current_password_hash) => current_password_hash == password_hash,
None => false,
pub fn new(events: &'a [AuthEvent]) -> Self {
let mut state = AuthState::default();
for event in events {
match event {
AuthEvent::Registered {
credentials,
user_id,
..
} => {
state.is_registered = true;
state.password_hash = match credentials {
Credentials::EmailPassword { password, .. } => Some(password),
};
state.user_id = Some(user_id);
}
_ => {}
}
}
}

pub fn user_id(&self) -> Option<&UserId> {
self.iter()
.filter_map(|event| match event {
AuthEvent::Registered { user_id, .. } => Some(user_id),
_ => None,
})
.last()
state
}
}
1 change: 1 addition & 0 deletions auth/src/domain/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod auth_config;
pub mod auth_event;
pub mod auth_message;
pub mod auth_scalar;
Expand Down
18 changes: 0 additions & 18 deletions framework/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,24 +27,6 @@ pub trait Projection: Default + Serialize + for<'de> serde::Deserialize<'de> {

pub trait Runtime: Send + Sync {}

// pub trait Store<T, E>
// where
// T: Event,
// {
// fn pull(&self) -> Result<Vec<T>, E>;
// fn push(&self, events: &[T]) -> Result<(), E>;
// }

// pub trait Repository<P, E>
// where
// P: Projection,
// {
// type Filter;

// fn find(&self, filter: Self::Filter) -> Result<P, E>;
// fn save(&self, projection: &P) -> Result<(), E>;
// }

#[async_trait]
pub trait Command<R, E>
where
Expand Down
10 changes: 10 additions & 0 deletions lab/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "lab"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
futures = "0.3.30"
tokio = { version = "1", features = ["full"] }
25 changes: 25 additions & 0 deletions lab/src/effect.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use std::{future::Future, pin::Pin};

pub struct Effect<R, T, E> {
f: Box<dyn Fn(R) -> Pin<Box<dyn Future<Output = Result<T, E>> + Send + Sync>>>,

Check failure on line 4 in lab/src/effect.rs

View workflow job for this annotation

GitHub Actions / Clippy

very complex type used. Consider factoring parts into `type` definitions
}

impl<R, T, E> Effect<R, T, E> {
pub fn new<F, Fut>(f: F) -> Effect<R, T, E>
where
F: Fn(R) -> Fut + 'static,
Fut: Future<Output = Result<T, E>> + Send + Sync + 'static,
{
Effect {
f: Box::new(move |runtime: R| Box::pin(f(runtime))),
}
}

pub async fn run(&self, runtime: R) -> Result<T, E> {
(self.f)(runtime).await
}
}

pub trait ToEffect<R, T, E> {
fn to_effect(self) -> Effect<R, T, E>;
}
18 changes: 18 additions & 0 deletions lab/src/function.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
pub struct Function<R, T, E> {

Check failure on line 1 in lab/src/function.rs

View workflow job for this annotation

GitHub Actions / Clippy

struct `Function` is never constructed

Check warning on line 1 in lab/src/function.rs

View workflow job for this annotation

GitHub Actions / Check

struct `Function` is never constructed

Check warning on line 1 in lab/src/function.rs

View workflow job for this annotation

GitHub Actions / Test Suite

struct `Function` is never constructed
f: Box<dyn Fn(R) -> Result<T, E>>,
}

impl<R, T, E> Function<R, T, E> {
pub fn new<F>(f: F) -> Function<R, T, E>

Check failure on line 6 in lab/src/function.rs

View workflow job for this annotation

GitHub Actions / Clippy

associated items `new` and `run` are never used

Check warning on line 6 in lab/src/function.rs

View workflow job for this annotation

GitHub Actions / Check

associated items `new` and `run` are never used

Check warning on line 6 in lab/src/function.rs

View workflow job for this annotation

GitHub Actions / Test Suite

associated items `new` and `run` are never used
where
F: Fn(R) -> Result<T, E> + 'static,
{
Function {
f: Box::new(move |runtime: R| f(runtime)),
}
}

pub fn run(&self, runtime: R) -> Result<T, E> {
(self.f)(runtime)
}
}
Loading

0 comments on commit a3e0541

Please sign in to comment.