Skip to content

Commit

Permalink
Update login example (CSR only) (#2155)
Browse files Browse the repository at this point in the history
  • Loading branch information
flosse authored Jan 15, 2024
1 parent 4616fee commit 862390a
Show file tree
Hide file tree
Showing 5 changed files with 47 additions and 42 deletions.
4 changes: 2 additions & 2 deletions examples/login_with_token_csr_only/client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ leptos_router = { path = "../../../router", features = ["csr"] }
log = "0.4"
console_error_panic_hook = "0.1"
console_log = "1"
gloo-net = "0.2"
gloo-storage = "0.2"
gloo-net = "0.5"
gloo-storage = "0.3"
serde = "1.0"
thiserror = "1.0"
4 changes: 2 additions & 2 deletions examples/login_with_token_csr_only/client/src/api.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use api_boundary::*;
use gloo_net::http::{Request, Response};
use gloo_net::http::{Request, RequestBuilder, Response};
use serde::de::DeserializeOwned;
use thiserror::Error;

Expand Down Expand Up @@ -41,7 +41,7 @@ impl AuthorizedApi {
fn auth_header_value(&self) -> String {
format!("Bearer {}", self.token.token)
}
async fn send<T>(&self, req: Request) -> Result<T>
async fn send<T>(&self, req: RequestBuilder) -> Result<T>
where
T: DeserializeOwned,
{
Expand Down
14 changes: 9 additions & 5 deletions examples/login_with_token_csr_only/server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,18 @@ edition = "2021"
publish = false

[dependencies]
api-boundary = "=0.0.0"

anyhow = "1.0"
api-boundary = "*"
axum = { version = "0.6", features = ["headers"] }
axum = "0.7"
axum-extra = { version = "0.9.2", features = ["typed-header"] }
env_logger = "0.10"
log = "0.4"
mailparse = "0.14"
pwhash = "1.0"
thiserror = "1.0"
tokio = { version = "1.25", features = ["macros", "rt-multi-thread"] }
tower-http = { version = "0.4", features = ["cors"] }
uuid = { version = "1.3", features = ["v4"] }
tokio = { version = "1.35", features = ["macros", "rt-multi-thread"] }
tower-http = { version = "0.5", features = ["cors"] }
uuid = { version = "1.6", features = ["v4"] }
parking_lot = "0.12.1"
headers = "0.4.0"
24 changes: 10 additions & 14 deletions examples/login_with_token_csr_only/server/src/application.rs
Original file line number Diff line number Diff line change
@@ -1,55 +1,53 @@
use mailparse::addrparse;
use pwhash::bcrypt;
use std::{collections::HashMap, str::FromStr, sync::RwLock};
use std::{collections::HashMap, str::FromStr};
use thiserror::Error;
use uuid::Uuid;

#[derive(Default)]
pub struct AppState {
users: RwLock<HashMap<EmailAddress, Password>>,
tokens: RwLock<HashMap<Uuid, EmailAddress>>,
users: HashMap<EmailAddress, Password>,
tokens: HashMap<Uuid, EmailAddress>,
}

impl AppState {
pub fn create_user(
&self,
&mut self,
credentials: Credentials,
) -> Result<(), CreateUserError> {
let Credentials { email, password } = credentials;
let user_exists = self.users.read().unwrap().get(&email).is_some();
let user_exists = self.users.get(&email).is_some();
if user_exists {
return Err(CreateUserError::UserExists);
}
self.users.write().unwrap().insert(email, password);
self.users.insert(email, password);
Ok(())
}

pub fn login(
&self,
&mut self,
email: EmailAddress,
password: &str,
) -> Result<Uuid, LoginError> {
let valid_credentials = self
.users
.read()
.unwrap()
.get(&email)
.map(|hashed_password| hashed_password.verify(password))
.unwrap_or(false);
if !valid_credentials {
Err(LoginError::InvalidEmailOrPassword)
} else {
let token = Uuid::new_v4();
self.tokens.write().unwrap().insert(token, email);
self.tokens.insert(token, email);
Ok(token)
}
}

pub fn logout(&self, token: &str) -> Result<(), LogoutError> {
pub fn logout(&mut self, token: &str) -> Result<(), LogoutError> {
let token = token
.parse::<Uuid>()
.map_err(|_| LogoutError::NotLoggedIn)?;
self.tokens.write().unwrap().remove(&token);
self.tokens.remove(&token);
Ok(())
}

Expand All @@ -62,8 +60,6 @@ impl AppState {
.map_err(|_| AuthError::NotAuthorized)
.and_then(|token| {
self.tokens
.read()
.unwrap()
.get(&token)
.cloned()
.map(|email| CurrentUser { email, token })
Expand Down
43 changes: 24 additions & 19 deletions examples/login_with_token_csr_only/server/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
use api_boundary as json;
use axum::{
extract::{State, TypedHeader},
headers::{authorization::Bearer, Authorization},
extract::State,
http::Method,
response::Json,
routing::{get, post},
Router,
};
use std::{env, sync::Arc};
use axum_extra::TypedHeader;
use headers::{authorization::Bearer, Authorization};
use parking_lot::RwLock;
use std::{env, net::SocketAddr, sync::Arc};
use tokio::net::TcpListener;
use tower_http::cors::{Any, CorsLayer};

mod adapters;
Expand All @@ -32,7 +35,7 @@ async fn main() -> anyhow::Result<()> {
}
env_logger::init();

let shared_state = Arc::new(AppState::default());
let shared_state = Arc::new(RwLock::new(AppState::default()));

let cors_layer = CorsLayer::new()
.allow_methods([Method::GET, Method::POST])
Expand All @@ -46,11 +49,10 @@ async fn main() -> anyhow::Result<()> {
.route_layer(cors_layer)
.with_state(shared_state);

let addr = "0.0.0.0:3000".parse().unwrap();
log::info!("Listen on {addr}");
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await?;
let addr = "0.0.0.0:3000".parse::<SocketAddr>()?;
log::info!("Start listening on http://{addr}");
let listener = TcpListener::bind(addr).await?;
axum::serve(listener, app.into_make_service()).await?;
Ok(())
}

Expand All @@ -73,40 +75,43 @@ enum Error {
}

async fn create_user(
State(state): State<Arc<AppState>>,
State(state): State<Arc<RwLock<AppState>>>,
Json(credentials): Json<json::Credentials>,
) -> Result<()> {
let credentials = Credentials::try_from(credentials)?;
state.create_user(credentials)?;
state.write().create_user(credentials)?;
Ok(Json(()))
}

async fn login(
State(state): State<Arc<AppState>>,
State(state): State<Arc<RwLock<AppState>>>,
Json(credentials): Json<json::Credentials>,
) -> Result<json::ApiToken> {
let json::Credentials { email, password } = credentials;
log::debug!("{email} tries to login");
let email = email.parse().map_err(|_|
// Here we don't want to leak detailed info.
LoginError::InvalidEmailOrPassword)?;
let token = state.login(email, &password).map(|s| s.to_string())?;
// Here we don't want to leak detailed info.
LoginError::InvalidEmailOrPassword)?;
let token = state
.write()
.login(email, &password)
.map(|s| s.to_string())?;
Ok(Json(json::ApiToken { token }))
}

async fn logout(
State(state): State<Arc<AppState>>,
State(state): State<Arc<RwLock<AppState>>>,
TypedHeader(auth): TypedHeader<Authorization<Bearer>>,
) -> Result<()> {
state.logout(auth.token())?;
state.write().logout(auth.token())?;
Ok(Json(()))
}

async fn get_user_info(
State(state): State<Arc<AppState>>,
State(state): State<Arc<RwLock<AppState>>>,
TypedHeader(auth): TypedHeader<Authorization<Bearer>>,
) -> Result<json::UserInfo> {
let user = state.authorize_user(auth.token())?;
let user = state.read().authorize_user(auth.token())?;
let CurrentUser { email, .. } = user;
Ok(Json(json::UserInfo {
email: email.into_string(),
Expand Down

0 comments on commit 862390a

Please sign in to comment.