222 lines
7.2 KiB
Rust
222 lines
7.2 KiB
Rust
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 warden::{warden};
|
|
use pubsub::{pg_listen};
|
|
use ws::{connect};
|
|
use account;
|
|
use payments::{post_stripe_event};
|
|
|
|
pub type Db = PooledConnection<PostgresConnectionManager>;
|
|
pub type PgPool = Pool<PostgresConnectionManager>;
|
|
|
|
const DB_POOL_SIZE: u32 = 20;
|
|
|
|
#[derive(Debug,Clone,Serialize,Deserialize)]
|
|
pub struct JsonError {
|
|
pub err: String
|
|
}
|
|
|
|
#[derive(Fail, Debug, Serialize, Deserialize)]
|
|
pub enum MnmlHttpError {
|
|
// User Facing Errors
|
|
#[fail(display="internal server error")]
|
|
ServerError,
|
|
#[fail(display="unauthorized")]
|
|
Unauthorized,
|
|
#[fail(display="bad request")]
|
|
BadRequest,
|
|
#[fail(display="account name taken or invalid")]
|
|
AccountNameTaken,
|
|
#[fail(display="password unacceptable. must be > 11 characters")]
|
|
PasswordUnacceptable,
|
|
#[fail(display="invalid code. https://discord.gg/YJJgurM")]
|
|
InvalidCode,
|
|
}
|
|
|
|
impl ResponseError for MnmlHttpError {
|
|
fn error_response(&self) -> HttpResponse {
|
|
match *self {
|
|
MnmlHttpError::ServerError => HttpResponse::InternalServerError()
|
|
.json(JsonError { err: self.to_string() }),
|
|
|
|
MnmlHttpError::BadRequest => HttpResponse::BadRequest()
|
|
.json(JsonError { 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(JsonError { err: self.to_string() }),
|
|
|
|
MnmlHttpError::AccountNameTaken => HttpResponse::BadRequest()
|
|
.json(JsonError { err: self.to_string() }),
|
|
|
|
MnmlHttpError::PasswordUnacceptable => HttpResponse::BadRequest()
|
|
.json(JsonError { err: self.to_string() }),
|
|
|
|
MnmlHttpError::InvalidCode => HttpResponse::BadRequest()
|
|
.json(JsonError { 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()
|
|
}
|
|
|
|
#[derive(Debug,Clone,Serialize,Deserialize)]
|
|
struct AccountLoginParams {
|
|
name: String,
|
|
password: String,
|
|
}
|
|
|
|
fn login(state: web::Data<State>, params: web::Json::<AccountLoginParams>) -> Result<HttpResponse, MnmlHttpError> {
|
|
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<State>) -> Result<HttpResponse, MnmlHttpError> {
|
|
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),
|
|
}
|
|
}
|
|
|
|
#[derive(Debug,Clone,Serialize,Deserialize)]
|
|
struct AccountRegisterParams {
|
|
name: String,
|
|
password: String,
|
|
code: String,
|
|
}
|
|
|
|
fn register(state: web::Data<State>, params: web::Json::<AccountRegisterParams>) -> Result<HttpResponse, MnmlHttpError> {
|
|
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<PostgresConnectionManager> {
|
|
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");
|
|
}
|