use std::env; use std::thread::{spawn, sleep}; use std::time::{Duration}; use actix_web::{middleware, web, App, HttpMessage, HttpRequest, HttpResponse, HttpServer}; use actix_web::error::ResponseError; use actix_web::http::{Cookie}; use actix_web::cookie::{SameSite}; use actix_cors::Cors; use r2d2::{Pool}; use r2d2::{PooledConnection}; use r2d2_postgres::{TlsMode, PostgresConnectionManager}; use rpc::{RpcErrorResponse, AccountLoginParams, AccountCreateParams}; use warden::{warden}; use pubsub::{pg_listen}; use ws::{connect}; use account::{Account, account_create}; use payments::{post_stripe_event}; pub type Db = PooledConnection; pub type PgPool = Pool; const DB_POOL_SIZE: u32 = 20; #[derive(Fail, Debug)] pub enum MnmlHttpError { // User Facing Errors #[fail(display="internal server error")] ServerError, #[fail(display="unauthorized")] Unauthorized, #[fail(display="bad request")] BadRequest, } impl ResponseError for MnmlHttpError { fn error_response(&self) -> HttpResponse { match *self { MnmlHttpError::ServerError => HttpResponse::InternalServerError() .json(RpcErrorResponse { err: self.to_string() }), MnmlHttpError::BadRequest => HttpResponse::BadRequest() .json(RpcErrorResponse { err: self.to_string() }), MnmlHttpError::Unauthorized => HttpResponse::Unauthorized() .cookie(Cookie::build("x-auth-token", "") // .secure(secure) .http_only(true) .same_site(SameSite::Strict) .max_age(-1) // 1 week aligns with db set .finish()) .json(RpcErrorResponse { err: self.to_string() }), } } } fn login_res(token: String) -> HttpResponse { HttpResponse::Ok() .cookie(Cookie::build("x-auth-token", token) // .secure(secure) .http_only(true) .same_site(SameSite::Strict) .max_age(60 * 60 * 24 * 7) // 1 week aligns with db set .finish()) .finish() } fn logout_res() -> HttpResponse { HttpResponse::Ok() .cookie(Cookie::build("x-auth-token", "") // .secure(secure) .http_only(true) .same_site(SameSite::Strict) .max_age(-1) .finish()) .finish() } fn login(state: web::Data, params: web::Json::) -> Result { let db = state.pool.get().or(Err(MnmlHttpError::ServerError))?; let mut tx = db.transaction().or(Err(MnmlHttpError::ServerError))?; match Account::login(&mut tx, ¶ms.name, ¶ms.password) { Ok(a) => { let token = Account::new_token(&mut tx, a.id).or(Err(MnmlHttpError::ServerError))?; tx.commit().or(Err(MnmlHttpError::ServerError))?; Ok(login_res(token)) }, Err(e) => { info!("{:?}", e); Err(MnmlHttpError::Unauthorized) } } } fn logout(r: HttpRequest, state: web::Data) -> Result { match r.cookie("x-auth-token") { Some(t) => { let db = state.pool.get().or(Err(MnmlHttpError::ServerError))?; let mut tx = db.transaction().or(Err(MnmlHttpError::ServerError))?; match Account::from_token(&mut tx, t.value().to_string()) { Ok(a) => { Account::new_token(&mut tx, a.id).or(Err(MnmlHttpError::Unauthorized))?; tx.commit().or(Err(MnmlHttpError::ServerError))?; return Ok(logout_res()); }, Err(_) => Err(MnmlHttpError::Unauthorized), } }, None => Err(MnmlHttpError::Unauthorized), } } fn register(state: web::Data, params: web::Json::) -> Result { let db = state.pool.get().or(Err(MnmlHttpError::ServerError))?; let mut tx = db.transaction().or(Err(MnmlHttpError::ServerError))?; match account_create(¶ms.name, ¶ms.password, ¶ms.code, &mut tx) { Ok(token) => { tx.commit().or(Err(MnmlHttpError::ServerError))?; Ok(login_res(token)) }, Err(e) => { info!("{:?}", e); Err(MnmlHttpError::BadRequest) } } } fn create_pool(url: String) -> Pool { let manager = PostgresConnectionManager::new(url, TlsMode::None) .expect("could not instantiate pg manager"); Pool::builder() .max_size(DB_POOL_SIZE) .build(manager) .expect("Failed to create pool.") } pub struct State { pub pool: PgPool, // pub pubsub: PubSub, } pub fn start() { let database_url = env::var("DATABASE_URL") .expect("DATABASE_URL must be set"); let pool = create_pool(database_url); let warden_pool = pool.clone(); spawn(move || { loop { let db_connection = warden_pool.get().expect("unable to get db connection"); if let Err(e) = warden(db_connection) { info!("{:?}", e); } sleep(Duration::new(1, 0)); } }); let pubsub_pool = pool.clone(); spawn(move || loop { let pubsub_conn = pubsub_pool.get().expect("could not get pubsub pg connection"); match pg_listen(pubsub_conn) { Ok(_) => warn!("pg listen closed"), Err(e) => warn!("pg_listen error {:?}", e), } }); HttpServer::new(move || App::new() .data(State { pool: pool.clone() }) .wrap(middleware::Logger::default()) .wrap(Cors::new().supports_credentials()) .service(web::resource("/api/login").route(web::post().to(login))) .service(web::resource("/api/logout").route(web::post().to(logout))) .service(web::resource("/api/register").route(web::post().to(register))) .service(web::resource("/api/payments/stripe") .route(web::post().to(post_stripe_event))) // .service(web::resource("/api/payments/crypto") // .route(web::post().to(post_stripe_payment))) .service(web::resource("/api/ws").route(web::get().to(connect)))) .bind("127.0.0.1:40000").expect("could not bind to port") .run().expect("could not start http server"); }