use std::{io, sync::Arc}; use actix_identity::{Identity, IdentityMiddleware}; use actix_session::{ config::PersistentSession, storage::CookieSessionStore, Session, SessionMiddleware, }; use actix_web::{ cookie::{time::Duration, Key}, delete, get, http::{header::ContentType, Method}, middleware, post, put, web::{self}, App, HttpMessage, HttpRequest, HttpResponse, HttpServer, Responder, }; // Define our model module mod model; use model::todo::{Priority, Status, Todo}; use model::user::User; // Define our repo module mod repo; // use repo::todo_repository::TodoRepository; use repo::user_repository::UserRepository; async fn index(id: Option, method: Method) -> impl Responder { match method { Method::GET => match id { Some(id) => HttpResponse::Ok() .content_type(ContentType::plaintext()) .body(format!( "You are logged in. Welcome! ({:?})", id.id().unwrap() )), None => HttpResponse::Unauthorized().finish(), }, _ => HttpResponse::Forbidden().finish(), } } #[get("/user")] async fn get_user(id: Identity, repo: web::Data) -> impl Responder { match repo.read_all() { Ok(users) => { log::info!("{users:?}"); HttpResponse::Ok().body(format!("{users:?}")) } Err(err) => { log::error!("Could not read from user repo: {err}"); HttpResponse::InternalServerError() .body(format!("Could not read from user repo: {err}")) } } } // TODO: Guard for login #[put("/user")] async fn put_user( id: Identity, payload: web::Json, session: Session, repo: web::Data, ) -> impl Responder { log::debug!("Received {payload:?}"); match repo.update(&payload.0) { Ok(_) => HttpResponse::Ok().finish(), Err(_) => { let msg = format!("No user with that id: {payload:?}"); log::debug!("{}", msg); HttpResponse::InternalServerError().body(msg) } } } // TODO: For a real app, impl smth like a registration-secret or email verification #[post("/user")] async fn post_user(payload: web::Json, repo: web::Data) -> impl Responder { log::debug!("Received {payload:?}"); let user = User::new(payload.login(), payload.hash(), payload.salt()); match repo.create(&user) { Ok(_) => { log::debug!("Successfully created {user:?}"); HttpResponse::Created().finish() } Err(err) => { log::debug!("{err}"); HttpResponse::BadRequest().body(format!("{user:?}")) } } } #[post("/login")] async fn post_login( req: HttpRequest, payload: web::Json, repo: web::Data, session: Session, ) -> impl Responder { log::debug!("Received {payload:?}"); match repo.read(&payload.id()) { Ok(Some(user)) => { if payload.salt() == "" { log::debug!("Initial login request with empty salt: {payload:?}"); HttpResponse::Ok().json(format!("{{ 'salt': '{}' }}", user.salt())) } else if payload.hash() == user.hash() { log::debug!("User successfully logged in: {payload:?} == {user:?}"); // TODO: Mayb handle more gracefully Identity::login(&req.extensions(), format!("{}", payload.id())) .expect("Log the user in"); // TODO: Mayb handle more gracefully session .insert("user", payload.0) .expect("Insert user into session"); HttpResponse::Ok().finish() } else { log::debug!("Wrong password hash for user: {payload:?} != {user:?}"); HttpResponse::Unauthorized().finish() } } Ok(None) => { log::debug!("User not found: {payload:?}"); HttpResponse::Unauthorized().finish() } Err(_) => { let msg = format!("Could not create user: {payload:?}"); log::debug!("{}", msg); HttpResponse::InternalServerError().body(msg) } } } #[delete("/logout")] async fn delete_logout(id: Identity) -> impl Responder { id.logout(); HttpResponse::Ok() } #[actix_web::main] async fn main() -> io::Result<()> { env_logger::init_from_env(env_logger::Env::new().default_filter_or("debug")); let user = User::new("admin", "init_pw_hash", "init_salt"); log::info!("{user:#?}"); let todo = Todo::new( user.id().clone(), "Mein todo", "Es hat viele Aufgaben", Priority::Normal, Status::Todo, ); log::info!("{todo:#?}"); let cassandra_session = Arc::new(repo::init()); let user_repo = web::Data::new(UserRepository::new(Arc::clone(&cassandra_session))); if let Err(err) = user_repo.create(&user) { log::debug!("Default user already exists: {err}"); } let key = Key::generate(); log::info!("Starting HTTP server: http://127.0.0.1:6969"); HttpServer::new(move || { App::new() .wrap(middleware::Compress::default()) .wrap(IdentityMiddleware::default()) .wrap( SessionMiddleware::builder(CookieSessionStore::default(), key.clone()) .cookie_secure(false) // Session lifetime .session_lifecycle(PersistentSession::default().session_ttl(Duration::days(7))) .build(), ) .wrap(middleware::Logger::default()) .app_data(user_repo.clone()) // .service(get_counter) .service(get_user) .service(put_user) .service(post_user) .service(post_login) .service(delete_logout) .default_service(web::to(index)) }) .bind(("127.0.0.1", 6969))? .workers(2) // number of workers per bind default ist #cpus .run() .await }