removing basically everything since almost nothing is going to translate over very easily from the auth module
for now we're only going to use two methods to auth::wall_entry is what allows to impose rate on a per user basic, not per route, although this feature can be configured later
This commit is contained in:
parent
c1f8ebee1d
commit
e48720d6ac
@ -1,322 +1,10 @@
|
||||
// Handlers for the base auth routes
|
||||
use crate::{
|
||||
DBConn, schema,
|
||||
utils:: {
|
||||
encode_param, new_key
|
||||
},
|
||||
models::{
|
||||
Invite,
|
||||
User
|
||||
}
|
||||
};
|
||||
use hyper::{Response, Body};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use rocket::http::Status;
|
||||
use rocket::response::{self, Responder, Response};
|
||||
use rocket::request::{Form, Request};
|
||||
use rocket_contrib::json::{Json, JsonValue};
|
||||
use diesel::{self, prelude::*};
|
||||
|
||||
use chrono::{Duration, Utc};
|
||||
use std::{error, fmt};
|
||||
|
||||
#[allow(dead_code)] // added because these fields are read through rocket, not directly; and rls keeps complainin
|
||||
#[derive(FromForm)]
|
||||
pub struct JoinParams {
|
||||
code: u64,
|
||||
name: String,
|
||||
pub async fn wall_entry(params: &HashMap<&str, Option<&str>>) -> bool {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[derive(FromForm, Deserialize)]
|
||||
pub struct AuthKey {
|
||||
id: u64,
|
||||
secret: String,
|
||||
}
|
||||
|
||||
pub type AuthResult<T, AuthErr> = std::result::Result<T, AuthErr>;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AuthErr {
|
||||
msg: &'static str,
|
||||
status: u16,
|
||||
}
|
||||
|
||||
impl fmt::Display for AuthErr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "Authentication error")
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for AuthErr {
|
||||
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r> Responder<'r> for AuthErr {
|
||||
fn respond_to(self, _:&Request) -> response::Result<'r> {
|
||||
Response::build()
|
||||
.status(Status::InternalServerError)
|
||||
.raw_header("db-error", self.msg)
|
||||
.ok()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn join(conn: DBConn, hashcode: u64, name: String) -> AuthResult<Json<User>, AuthErr>{
|
||||
/*
|
||||
* Requires <code:int> -> body
|
||||
* Requires <name:string> -> body
|
||||
* Struct JoinParams enforces this for us so if something is missing then rocket should 404
|
||||
*/
|
||||
use schema::invites::{self, dsl::*};
|
||||
|
||||
let diesel_result = invites
|
||||
.filter(invites::dsl::id.eq(hashcode))
|
||||
.first::<Invite>(&conn.0);
|
||||
|
||||
if let Ok(data) = diesel_result {
|
||||
match data.uses {
|
||||
1 ..= std::i32::MAX => {
|
||||
let new_user = crate::users::create_new_user(name);
|
||||
// At this point we don't really care about the return
|
||||
let _ignore = diesel::update(invites.filter(invites::dsl::id.eq(hashcode)))
|
||||
.set(uses.eq(data.uses - 1))
|
||||
.execute(&conn.0);
|
||||
|
||||
Ok(Json(new_user))
|
||||
}
|
||||
// The invite has been used up and thus should be removed
|
||||
std::i32::MIN ..= 0 => {
|
||||
let _ = diesel::delete(invites.filter(invites::dsl::id.eq(data.id)))
|
||||
.execute(&conn.0)
|
||||
.expect("Could not delete invite");
|
||||
|
||||
Err(AuthErr{msg: "Invite expired", status: 404})
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
Err(AuthErr{msg: "Malformed request", status: 500})
|
||||
}
|
||||
}
|
||||
|
||||
fn confirm_user_api_access(conn: &MysqlConnection, user_id: u64, user_secret: &str) -> bool {
|
||||
use schema::users::dsl::*;
|
||||
let result = users
|
||||
.filter(id.eq(user_id))
|
||||
.filter(secret.eq(user_secret))
|
||||
.first::<User>(conn);
|
||||
|
||||
match result {
|
||||
Ok(_data) => true,
|
||||
Err(_e) => false
|
||||
}
|
||||
}
|
||||
|
||||
fn blind_remove_session(conn: &MysqlConnection, sesh_secret: &str) {
|
||||
use crate::schema::sessions::dsl::*;
|
||||
|
||||
let _ignore_result = diesel::delete(sessions
|
||||
.filter(secret.eq(sesh_secret)))
|
||||
.execute(conn);
|
||||
}
|
||||
|
||||
fn create_new_session_key(conn: &MysqlConnection) -> Option<String> {
|
||||
use crate::models::InsertableSession;
|
||||
|
||||
let new_session = InsertableSession {
|
||||
secret: encode_param(&new_key()),
|
||||
expires: (Utc::now() + Duration::hours(1)).timestamp() as u64
|
||||
};
|
||||
|
||||
// insert the new key into our db
|
||||
let db_result = diesel::insert_into(schema::sessions::table)
|
||||
.values(&new_session)
|
||||
.execute(conn);
|
||||
|
||||
// finally return the key assuming everything went well
|
||||
match db_result {
|
||||
Ok(_val) => Some(new_session.secret),
|
||||
Err(_e) => None
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/login", data = "<api_key>")]
|
||||
pub fn login(conn: DBConn, api_key: Form<AuthKey>) -> AuthResult<JsonValue, AuthErr>{
|
||||
/*
|
||||
* Session Tokens are used to key into a subset of online users
|
||||
* This is what should make queries faster per instance as we'll have less data to sift through w/ diesel
|
||||
*/
|
||||
if confirm_user_api_access(&conn.0, api_key.id, &api_key.secret) {
|
||||
blind_remove_session(&conn.0, &api_key.secret);
|
||||
let key = create_new_session_key(&conn.0);
|
||||
match key {
|
||||
Some(data) => Ok(json!({"key": data})),
|
||||
None => Err(AuthErr {
|
||||
msg: "Could not create session",
|
||||
status: 500
|
||||
})
|
||||
}
|
||||
}
|
||||
else {
|
||||
Err(AuthErr {
|
||||
msg: "Nothing found",
|
||||
status: 400
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/leave", data = "<api_key>")]
|
||||
pub fn leave(conn: DBConn, api_key: Form<AuthKey>) -> Status {
|
||||
/*
|
||||
* Basic removal of the user from our users table
|
||||
*/
|
||||
use crate::schema::users::dsl::*;
|
||||
use crate::diesel::ExpressionMethods;
|
||||
let _db_result = diesel::delete(users
|
||||
.filter(id.eq(api_key.id))
|
||||
.filter(secret.eq(api_key.secret.clone())))
|
||||
.execute(&conn.0).unwrap();
|
||||
|
||||
|
||||
Status::Ok
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod auth_tests {
|
||||
use crate::{
|
||||
invites::static_rocket_route_info_for_use_invite,
|
||||
schema,
|
||||
models::Invite,
|
||||
utils::{encode_param, new_key}
|
||||
};
|
||||
use super::*;
|
||||
use rocket::{
|
||||
self, local::Client, http::ContentType
|
||||
};
|
||||
use diesel::mysql::MysqlConnection;
|
||||
use chrono::{Duration, Utc};
|
||||
use rand::random;
|
||||
use std::env;
|
||||
use dotenv::dotenv;
|
||||
use serde_json::Value;
|
||||
|
||||
fn setup_dotenv() -> Result<(), i32> {
|
||||
match dotenv() {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => panic!("`.env` could not be loaded: {:?}", e)
|
||||
}
|
||||
}
|
||||
fn mysql_conn() -> MysqlConnection {
|
||||
MysqlConnection::establish(&env::var("DATABASE_URL").unwrap())
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn join_and_leave() {
|
||||
// Create an invite in our db manually
|
||||
// Use that invite to join
|
||||
// Then leave using our neato /auth/leave route
|
||||
if let Err(_denv) = setup_dotenv() {
|
||||
panic!("env failed fukc")
|
||||
}
|
||||
let app = rocket::ignite()
|
||||
.mount("/invite", routes![use_invite])
|
||||
.mount("/auth", routes![leave])
|
||||
.attach(DBConn::fairing());
|
||||
|
||||
// First we create a new invite
|
||||
let conn = mysql_conn();
|
||||
let dt = Utc::now() + Duration::minutes(30);
|
||||
let invite = Invite {
|
||||
id: random::<u64>(),
|
||||
uses: 1,
|
||||
expires: dt.timestamp() as u64,
|
||||
};
|
||||
let _ = diesel::insert_into(schema::invites::table)
|
||||
.values(&invite)
|
||||
.execute(&conn);
|
||||
|
||||
// use our new invite to "join" the server
|
||||
let rocket_c = Client::new(app).expect("Invalid rocket instance");
|
||||
let mut response = rocket_c.get(format!("/invite/join/{}/{}", invite.id, "billybob")).dispatch();
|
||||
let body: String = response.body_string().unwrap();
|
||||
let api_key: Value = serde_json::from_str(&body).unwrap();
|
||||
|
||||
// Go about leaving the server
|
||||
let secret_str = format!("{}", api_key["secret"]);
|
||||
let body_params = format!("id={}&secret={}", api_key["id"], secret_str);
|
||||
println!("Parameters being sent {}", body_params);
|
||||
let leave_response = rocket_c.post("/auth/leave")
|
||||
.body(body_params)
|
||||
.header(ContentType::Form)
|
||||
.dispatch();
|
||||
|
||||
assert_eq!(leave_response.status(), Status::Ok);
|
||||
println!("{}", body);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dummy_leave() {
|
||||
/*
|
||||
* Naive test for the /auth/leave route
|
||||
*/
|
||||
setup_dotenv().unwrap();
|
||||
let app = rocket::ignite()
|
||||
.mount("/auth", routes![leave])
|
||||
.attach(DBConn::fairing());
|
||||
|
||||
let rocket_client = Client::new(app).expect("asdf");
|
||||
// Some dummy parameters as the /auth/leave route only has one type of response
|
||||
let id = 12345;
|
||||
let secret = encode_param(&new_key());
|
||||
let params = format!("id={}&secret={}", id, secret);
|
||||
println!("Parameters posted to /auth/leave: {}", params);
|
||||
let response = rocket_client.post("/auth/leave")
|
||||
.body(params)
|
||||
.header(ContentType::Form)
|
||||
.dispatch();
|
||||
|
||||
assert_eq!(response.status(), Status::Ok);
|
||||
}
|
||||
|
||||
fn bogus_user_insertion(test_name: String, conn: &MysqlConnection) -> User {
|
||||
use crate::models::{USER_OFFLINE, InsertableUser};
|
||||
use schema::users::{self, dsl::*};
|
||||
|
||||
let insertable_user = InsertableUser {
|
||||
name: test_name,
|
||||
secret: encode_param(&new_key()),
|
||||
date: Utc::now().timestamp() as u64,
|
||||
status: USER_OFFLINE
|
||||
};
|
||||
|
||||
let _insertion_result = diesel::insert_into(users::table)
|
||||
.values(&insertable_user)
|
||||
.execute(conn);
|
||||
|
||||
users.filter(date.eq(insertable_user.date)).first::<User>(conn).unwrap()
|
||||
}
|
||||
#[test]
|
||||
fn login() {
|
||||
setup_dotenv().unwrap();
|
||||
let app = rocket::ignite()
|
||||
.mount("/auth", routes![login])
|
||||
.attach(DBConn::fairing());
|
||||
let conn = mysql_conn();
|
||||
|
||||
// We need a valid user to login so we'll make one up
|
||||
let bogus_user = bogus_user_insertion("test::auth::login".to_string(), &conn);
|
||||
|
||||
// Finaly we can test our route with the boilerplate ootw
|
||||
let rocket_client = Client::new(app).expect("test::auth::login => client creation failed");
|
||||
let params = format!("id={}&secret={}", bogus_user.id, bogus_user.secret);
|
||||
let response = rocket_client.post("/auth/login")
|
||||
.body(params)
|
||||
.header(ContentType::Form)
|
||||
.dispatch();
|
||||
|
||||
assert_eq!(response.status(), Status::Ok);
|
||||
}
|
||||
|
||||
}
|
||||
pub fn wall_failure(resp: &Response<Body>) {
|
||||
unimplemented!()
|
||||
}
|
Loading…
Reference in New Issue
Block a user