Skip to content

Commit

Permalink
Users controller
Browse files Browse the repository at this point in the history
  • Loading branch information
levkk committed Dec 6, 2024
1 parent b7359b3 commit 3fa4dec
Show file tree
Hide file tree
Showing 11 changed files with 194 additions and 159 deletions.
51 changes: 0 additions & 51 deletions examples/users/src/controllers.rs
Original file line number Diff line number Diff line change
@@ -1,57 +1,6 @@
use crate::models::*;
use rwf::prelude::*;

#[derive(macros::Form)]
struct SignupForm {
email: String,
password: String,
}

#[derive(Default, macros::PageController)]
pub struct Signup;

#[async_trait]
impl PageController for Signup {
async fn get(&self, request: &Request) -> Result<Response, Error> {
let user = request.user::<User>(Pool::pool()).await?;

if let Some(_) = user {
return Ok(Response::new().redirect("/profile"));
}

render!(request, "templates/signup.html")
}

async fn post(&self, request: &Request) -> Result<Response, Error> {
let form = request.form::<SignupForm>()?;
let user = User::signup(&form.email, &form.password).await?;

match user {
UserLogin::Ok(user) => Ok(request.login_user(&user)?.redirect("/profile")),
_ => render!(request, "templates/signup.html", "error" => true, 400),
}
}
}

#[controller]
pub async fn login(request: &Request) -> Result<Response, Error> {
let form = request.form::<SignupForm>()?;

let user = User::login(&form.email, &form.password).await?;

if let UserLogin::Ok(_) = user {
Ok(Response::new().redirect("/profile"))
} else {
render!(
request,
"templates/signup.html",
"login" => true,
"error" => true,
400
)
}
}

#[controller]
pub async fn profile(request: &Request) -> Result<Response, Error> {
let user = {
Expand Down
12 changes: 8 additions & 4 deletions examples/users/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use rwf::controller::LoginController;
use rwf::controller::{LoginController, LogoutController, SignupController};
use rwf::{http::Server, prelude::*};

mod controllers;
Expand All @@ -8,12 +8,16 @@ mod models;
async fn main() {
Logger::init();

let signup: LoginController<models::User> =
LoginController::new("templates/signup.html").redirect("/profile");
let signup: SignupController<models::User> =
SignupController::new("templates/signup.html").redirect("/profile");

let login: LoginController<models::User> =
LoginController::new("templates/login.html").redirect("/profile");

Server::new(vec![
route!("/signup" => { signup }),
route!("/login" => controllers::login),
route!("/login" => { login }),
route!("/logout" => { LogoutController::default().redirect("/signup") }),
route!("/profile" => controllers::profile),
])
.launch()
Expand Down
73 changes: 0 additions & 73 deletions examples/users/src/models.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,4 @@
// use rwf::model::Error;
use rwf::crypto::{hash, hash_validate};
use rwf::prelude::*;
use tokio::task::spawn_blocking;

#[derive(Clone, macros::Model, macros::UserModel)]
#[user_model(email, password_hash)]
pub struct User2 {
id: Option<i64>,
email: String,
password_hash: String,
}

pub enum UserLogin {
NoSuchUser,
WrongPassword,
Ok(User),
}

#[derive(Clone, macros::Model, macros::UserModel)]
#[user_model(email, password)]
Expand All @@ -25,59 +8,3 @@ pub struct User {
password: String,
created_at: OffsetDateTime,
}

impl User {
/// Create new user with email and password.
pub async fn signup(email: &str, password: &str) -> Result<UserLogin, Error> {
let hash_password = password.to_owned();
let encrypted_password = spawn_blocking(move || hash(hash_password.as_bytes()))
.await
.unwrap()?;

match Self::login(email, password).await? {
UserLogin::Ok(user) => return Ok(UserLogin::Ok(user)),
UserLogin::WrongPassword => return Ok(UserLogin::WrongPassword),
_ => (),
}

let user = User::create(&[
("email", email.to_value()),
("password", encrypted_password.to_value()),
])
.fetch(Pool::pool())
.await?;

Ok(UserLogin::Ok(user))
}

/// Login user with email and password.
///
/// Return a user if one exists and the passwords match.
/// Return `None` otherwise.
pub async fn login(email: &str, password: &str) -> Result<UserLogin, Error> {
if let Some(user) = User::filter("email", email)
.fetch_optional(Pool::pool())
.await?
{
if hash_validate(password.as_bytes(), &user.password)? {
return Ok(UserLogin::Ok(user));
} else {
return Ok(UserLogin::WrongPassword);
}
}

Ok(UserLogin::NoSuchUser)
}
}

#[cfg(test)]
mod test {
use super::*;

#[tokio::test]
async fn test_user() {
Migrations::migrate().await.unwrap();
let _user = User::signup("[email protected]", "password2").await.unwrap();
let _user = User::login("[email protected]", "password2").await.unwrap();
}
}
42 changes: 42 additions & 0 deletions examples/users/templates/login.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<!doctype html>
<html data-bs-theme="dark">
<head>
<%% "templates/head.html" %>
</head>
<body>
<div class="container pt-5">
<h1 class="mb-4">Login</h1>
<form method="post" action="/login">
<%= csrf_token() %>

<% if error_user_does_not_exist %>
<div class="alert alert-danger">
Account with this email doesn't exist or the password is incorrect.
</div>
<% end %>

<% if error_password %>
<div class="alert alert-danger">
Wrong password.
</div>
<% end %>

<div class="mb-3">
<label class="form-label">Email</label>
<input class="form-control" type="email" placeholder="Your email, e.g. [email protected]" required autocomplete="off" name="identifier">
</div>

<div class="mb-3">
<label class="form-label">Password</label>
<input class="form-control" type="password" placeholder="A secure password" required autocomplete="off" name="password">
</div>

<div class="d-flex justify-content-end">
<button type="submit" class="btn btn-primary">
Signup
</button>
</div>
</form>
</div>
</body>
</html>
14 changes: 2 additions & 12 deletions examples/users/templates/signup.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,22 @@
</head>
<body>
<div class="container pt-5">
<h1 class="mb-4">Create account</h1>
<form method="post" action="/signup">
<% if error_user_exists %>
<div class="alert alert-danger">
Account with this email already exists, and the password is incorrect.
Account with this email already exists.
</div>
<% end %>
<%= csrf_token() %>
<div class="mb-3">
<label class="form-label">Email</label>
<input class="form-control" type="email" placeholder="Your email, e.g. [email protected]" required autocomplete="off" name="identifier">
<% if error_identifier %>
<div class="invalid-feedback">
Provided email is not valid.
</div>
<% end %>
</div>

<div class="mb-3">
<label class="form-label">Password</label>
<input class="form-control" type="password" placeholder="A secure password" required autocomplete="off" name="password">

<% if error_password %>
<div class="invalid-feedback">
Provided password is not valid.
</div>
<% end %>
</div>

<div class="d-flex justify-content-end">
Expand Down
2 changes: 1 addition & 1 deletion rwf/src/controller/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ pub use error::Error;
pub use middleware::{Middleware, MiddlewareHandler, MiddlewareSet, Outcome, RateLimiter};
pub use static_files::{CacheControl, StaticFiles};
pub use turbo_stream::TurboStream;
pub use user::LoginController;
pub use user::{LoginController, LogoutController, SignupController};

use super::http::{
websocket::{self, DataFrame},
Expand Down
Loading

0 comments on commit 3fa4dec

Please sign in to comment.