This is my full implementation:
use actix_cors::Cors;
use actix_web::{web, get, middleware, Result, Responder, HttpResponse, HttpServer, App, HttpRequest, Error, dev::ServiceResponse, dev::ServiceRequest, dev::Transform, dev::Service};
use actix_web::http::StatusCode;
use actix_session::{Session, CookieSession};
use serde::Deserialize;
use mysql::prelude::Queryable;
use chrono::{DateTime, NaiveDate, Utc};
use std::{collections::HashMap, sync::Mutex, sync::RwLock, pin::Pin, future::Future, task::Context, task::Poll, future::Ready};
use dashmap::DashMap;
#[macro_use]
extern crate lazy_static;
extern crate regex;
extern crate pwhash;
use regex::Regex;
lazy_static! {
static ref db : mysqldata::MySQLData = mysqldata::MySQLData::init_connection(&String::from("rustsite"), &String::from("root"), &String::from("toor"));
static ref SESSIONS : RwLock<DashMap<String, Option<u8>>> = RwLock::new(DashMap::new());
}
// Module for handling sessions
pub mod session_handler {
use actix_web::{HttpRequest, HttpResponse};
use rand::{thread_rng, Rng};
use rand::distributions::Alphanumeric;
use sha2::{Sha256, Digest};
use uuid::Uuid;
use crate::*;
// Generate session_token. Concatenation of sha256(ip_address) and a random string,
// ip_address is included to avoid collission
fn generate_session_token() -> String {
Uuid::new_v4().to_string()
}
// Check if a session exists by reading the cookie 'session' in the request-header
pub fn session_exists(req: HttpRequest) -> bool {
get_session_token(req).map_or(false, |s| s != "")
}
// Retrieves the session_token from the request.
// if none cookie is found this Option<String> will be None, otherwise
// it will contain the session_token after unwrapping
pub fn get_session_token(req: HttpRequest) -> Option<String> {
Some(req.headers().get("session").and_then(|r| r.to_str().ok()).unwrap().to_string())
}
// Make a HttpResponse with the NEW session_token in the header.
pub fn set_session_token(req: HttpRequest) -> HttpResponse {
let ip_address = req
.connection_info()
.remote_addr()
.unwrap()
.to_string();
let session_token = generate_session_token();
let header_string = format!("session={}; Secure; HttpOnly", session_token);
add_session(session_token);
HttpResponse::Ok()
.content_type("plain/text")
.header("Set-Cookie", header_string)
.body("")
}
// Save this session in the session_database
pub fn add_session(token: String) {
SESSIONS.read().unwrap().insert(token, None);
}
// Delete a session from the session_database
pub fn delete_session_by_token(token: String) {
SESSIONS.read().unwrap().remove(&token);
}
// Unimplemented...
pub fn delete_session_by_userid(id: u8) {
unimplemented!()
}
// A user is linked to a session after authenticating himself. This Function
// can retrieve which user is connected to which session.
pub fn get_user_by_session(token: String) -> Option<u8> {
*SESSIONS.read().unwrap().get(&token).unwrap()
}
pub fn set_user_by_session(token: String, user_id: u8) {
if let Some(mut value) = SESSIONS.read().unwrap().get_mut(&token) {
*value = Some(user_id);
}
}
}
// Module for MySQLs
pub mod mysqldata {
use mysql::{prelude::Queryable, params, chrono::NaiveDate, Row};
use crate::{RegistrationForm, User};
pub struct MySQLData {
pub conn: mysql::Pool
}
impl MySQLData {
//Return MySQLData object with conn field
pub fn init_connection(database_name : &String, database_user : &String, database_pass : &String) -> MySQLData {
let conn_str : String = format!("mysql://{user}:{pass}@localhost/{name}", user=database_user, pass=database_pass, name=database_name);
let conn = mysql::Pool::new(conn_str);
match conn {
Ok(_) => {
println!("Connection to {} successful!", database_name);
MySQLData {
conn: conn.unwrap()
}
},
Err(e) => {
eprintln!("Connection to {} failed: {}", database_name, e.to_string());
std::process::exit(-1);
}
}
}
// Initialize all needed tables
pub fn init_tables(&self) {
let mut conn = self.conn.get_conn().unwrap();
conn.query_drop(
"CREATE TABLE IF NOT EXISTS users (
ID BIGINT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
username VARCHAR(255) NOT NULL,
email VARCHAR(256) NOT NULL,
birthdate DATETIME,
password VARCHAR(255) NOT NULL
)"
);
println!("Tables initialized...");
}
pub fn add_user(&self, data: RegistrationForm) -> bool {
let mut conn = self.conn.get_conn().unwrap();
let date = NaiveDate::parse_from_str(&*data.birthdate, "%d-%m-%Y").unwrap();
//let mut insert_id : i64 = -1;
let execute_result = conn.exec_drop(r"
INSERT INTO users (
username, email, birthdate, password
) VALUES (
:username, :email, :birthdate, :password
)", params! {
"username" => &data.username,
"email" => data.email,
"birthdate" => date.format("%Y-%m-%d").to_string(),
"password" => pwhash::bcrypt::hash(data.password).unwrap()
});
//insert_id = self.conn.get_conn().unwrap().last_insert_id() as i64;
match execute_result {
Ok(_) => {
println!("User {} added!", data.username);
true
}
Err(e) => {
eprintln!("Failed to add user {}: {}", data.username, e.to_string());
false
}
}
//insert_id
}
pub fn read_user_by_id(&self, id: u8) -> RegistrationForm {
unimplemented!()
}
pub fn read_user_by_email(&self, email: String) -> User {
let mut conn = self.conn.get_conn().unwrap();
let stmt = conn.prep("SELECT id, username, email, birthdate, password FROM users WHERE email=:email").unwrap();
let mut row : Row = conn.exec_first(&stmt, params! {
"email" => email
}).unwrap().unwrap();
User {
id: row.take("id").unwrap(),
username: row.take("username").unwrap(),
email: row.take("email").unwrap(),
birthdate: row.take("birthdate").unwrap(),
password_hash: row.take("password").unwrap(),
}
}
}
}
#[derive(Deserialize)]
pub struct RegistrationForm {
username: String,
email: String,
birthdate: String,
password: String,
rp_password: String
}
impl RegistrationForm {
pub fn is_valid(&self) -> bool {
if self.username.is_empty() ||
self.email.is_empty() ||
self.birthdate.is_empty() ||
self.password.is_empty() ||
self.rp_password.is_empty() {
println!("User {} did not fill in all inputs!", self.username);
return false
}
if self.password != self.rp_password {
println!("User {} did not repeat the password correctly!", self.username);
return false
}
let re_date = Regex::new(r"^\d{2}-\d{2}-\d{4}$").unwrap();
if !re_date.is_match(&*&self.birthdate) {
println!("{} for user {} is not a valid date", self.birthdate, self.username);
return false
}
let re_email = Regex::new(r"[\w._%+-]+@[\w.-]+\.[a-zA-Z]{2,3}").unwrap();
if !re_email.is_match(&*&self.email) {
println!("{} for user {} is not a valid mail-address", self.email, self.username);
return false
}
true
}
}
#[derive(Deserialize)]
pub struct LoginForm {
email: String,
password: String
}
#[derive(Deserialize)]
pub struct User {
id: u64,
username: String,
email: String,
birthdate: chrono::NaiveDateTime,
password_hash: String
}
//Function which gets executed with correct route
async fn register(form: web::Form<RegistrationForm>, session: Session) -> String {
let registration_form = form.into_inner();
if !registration_form.is_valid() {
return String::from("error");
}
let add_result = db.add_user(registration_form);
match add_result {
true => String::from("ok"),
false => String::from("error")
}
}
async fn login(form: web::Form<LoginForm>, req: HttpRequest) -> String {
let login_form = form.into_inner();
let user : Option<User> = Some(db.read_user_by_email(login_form.email));
match user {
None => String::from("error"),
Some(user) => {
if pwhash::bcrypt::verify(login_form.password, &*user.password_hash) {
String::from("ok")
} else {
String::from("error")
}
}
}
}
// middleware
pub struct SendSession;
impl<S, B> Transform<S> for SendSession
where
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Request = ServiceRequest;
type Response = ServiceResponse<B>;
type Error = Error;
type Future = Ready<Result<Self::transform, Self::InitError>>;
type Transform = SendSessionMiddleware<S>;
type InitError = ();
fn new_transform(&self, service: S) -> Self::Future {
Ok(SendSessionMiddleware { service })
}
}
pub struct SendSessionMiddleware<S> {
service: S,
}
impl<S, B> Service for SendSessionMiddleware<S>
where
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Request = ServiceRequest;
type Response = ServiceResponse<B>;
type Error = Error;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.service.poll_ready(cx)
}
fn call(&mut self, req: ServiceRequest) -> Self::Future {
println!("Middleware test");
let fut = self.service.call(req);
Box::pin(async move {
let res = fut.await?;
Ok(res)
})
}
}
#[actix_rt::main]
async fn main() -> std::io::Result<()> {
db.init_tables();
HttpServer::new(|| App::new()
.wrap(
Cors::new()
.allowed_origin("http://rustsite.local")
.finish()
)
.wrap(
CookieSession::signed(&[0; 32])
.secure(false)
)
.service(
web::resource("/register").route(web::post().to(register))
)
.service(
web::resource("/login").route(web::post().to(login))
)
)
.bind("127.0.0.1:8088")?
.run()
.await
}
I get the following errors:
error[E0658]: use of unstable library feature 'future_readiness_fns'
--> src/main.rs:8:113
|
8 | use std::{collections::HashMap, sync::Mutex, sync::RwLock, pin::Pin, future::Future, task::Context, task::Poll, future::Ready};
| ^^^^^^^^^^^^^
|
= note: see issue #70921 <https://github.com/rust-lang/rust/issues/70921> for more information
error[E0658]: use of unstable library feature 'future_readiness_fns'
--> src/main.rs:319:19
|
319 | type Future = Ready<Result<Self::transform, Self::InitError>>;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: see issue #70921 <https://github.com/rust-lang/rust/issues/70921> for more information
error[E0220]: associated type `transform` not found for `Self`
--> src/main.rs:319:38
|
319 | type Future = Ready<Result<Self::transform, Self::InitError>>;
| ^^^^^^^^^ help: there is an associated type with a similar name: `Transform`
error: aborting due to 3 previous errors; 8 warnings emitted
Some errors have detailed explanations: E0220, E0658.
For more information about an error, try `rustc --explain E0220`.
error: could not compile `rustSite1`.
It says I'm using an unstable library, but I just followed the example.