2021-03-21 14:04:39 -06:00
|
|
|
use self::diesel::prelude::*;
|
2021-04-13 12:14:07 -06:00
|
|
|
use eyre::Result;
|
2021-03-21 14:04:39 -06:00
|
|
|
use rocket::http::Status;
|
|
|
|
use rocket::request::{self, FromRequest, Outcome, Request};
|
2021-04-13 12:14:07 -06:00
|
|
|
use rocket::State;
|
2021-03-21 14:04:39 -06:00
|
|
|
use rocket_contrib::databases::diesel;
|
|
|
|
use sodiumoxide::crypto::pwhash::argon2id13;
|
|
|
|
|
|
|
|
use rocket_contrib::json::Json;
|
|
|
|
use uuid::Uuid;
|
|
|
|
|
|
|
|
use super::models::{NewSession, NewUser, Session, User};
|
|
|
|
use super::views::ApiResponse;
|
2021-04-13 12:14:07 -06:00
|
|
|
|
|
|
|
use crate::api::{LoginRequest, RegisterRequest};
|
2021-03-21 14:04:39 -06:00
|
|
|
use crate::schema::{sessions, users};
|
2021-04-13 12:14:07 -06:00
|
|
|
use crate::settings::Settings;
|
|
|
|
use crate::utils::hash_secret;
|
2021-03-21 14:04:39 -06:00
|
|
|
|
|
|
|
use super::database::AtuinDbConn;
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub enum KeyError {
|
|
|
|
Missing,
|
|
|
|
Invalid,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn verify_str(secret: &str, verify: &str) -> bool {
|
|
|
|
sodiumoxide::init().unwrap();
|
|
|
|
|
|
|
|
let mut padded = [0_u8; 128];
|
|
|
|
secret.as_bytes().iter().enumerate().for_each(|(i, val)| {
|
|
|
|
padded[i] = *val;
|
|
|
|
});
|
|
|
|
|
|
|
|
match argon2id13::HashedPassword::from_slice(&padded) {
|
|
|
|
Some(hp) => argon2id13::pwhash_verify(&hp, verify.as_bytes()),
|
|
|
|
None => false,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, 'r> FromRequest<'a, 'r> for User {
|
|
|
|
type Error = KeyError;
|
|
|
|
|
|
|
|
fn from_request(request: &'a Request<'r>) -> request::Outcome<User, Self::Error> {
|
|
|
|
let session: Vec<_> = request.headers().get("authorization").collect();
|
|
|
|
|
|
|
|
if session.is_empty() {
|
|
|
|
return Outcome::Failure((Status::BadRequest, KeyError::Missing));
|
|
|
|
} else if session.len() > 1 {
|
|
|
|
return Outcome::Failure((Status::BadRequest, KeyError::Invalid));
|
|
|
|
}
|
|
|
|
|
|
|
|
let session: Vec<_> = session[0].split(' ').collect();
|
|
|
|
|
|
|
|
if session.len() != 2 {
|
|
|
|
return Outcome::Failure((Status::BadRequest, KeyError::Invalid));
|
|
|
|
}
|
|
|
|
|
|
|
|
if session[0] != "Token" {
|
|
|
|
return Outcome::Failure((Status::BadRequest, KeyError::Invalid));
|
|
|
|
}
|
|
|
|
|
|
|
|
let session = session[1];
|
|
|
|
|
|
|
|
let db = request
|
|
|
|
.guard::<AtuinDbConn>()
|
|
|
|
.succeeded()
|
|
|
|
.expect("failed to load database");
|
|
|
|
|
|
|
|
let session = sessions::table
|
|
|
|
.filter(sessions::token.eq(session))
|
|
|
|
.first::<Session>(&*db);
|
|
|
|
|
|
|
|
if session.is_err() {
|
|
|
|
return Outcome::Failure((Status::Unauthorized, KeyError::Invalid));
|
|
|
|
}
|
|
|
|
|
|
|
|
let session = session.unwrap();
|
|
|
|
|
|
|
|
let user = users::table.find(session.user_id).first(&*db);
|
|
|
|
|
|
|
|
match user {
|
|
|
|
Ok(user) => Outcome::Success(user),
|
|
|
|
Err(_) => Outcome::Failure((Status::Unauthorized, KeyError::Invalid)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-13 12:14:07 -06:00
|
|
|
#[get("/user/<user>")]
|
|
|
|
#[allow(clippy::clippy::needless_pass_by_value)]
|
|
|
|
pub fn get_user(user: String, conn: AtuinDbConn) -> ApiResponse {
|
|
|
|
use crate::schema::users::dsl::{username, users};
|
|
|
|
|
|
|
|
let user: Result<String, diesel::result::Error> = users
|
|
|
|
.select(username)
|
|
|
|
.filter(username.eq(user))
|
|
|
|
.first(&*conn);
|
|
|
|
|
|
|
|
if user.is_err() {
|
|
|
|
return ApiResponse {
|
|
|
|
json: json!({
|
|
|
|
"message": "could not find user",
|
|
|
|
}),
|
|
|
|
status: Status::NotFound,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
let user = user.unwrap();
|
|
|
|
|
|
|
|
ApiResponse {
|
|
|
|
json: json!({ "username": user.as_str() }),
|
|
|
|
status: Status::Ok,
|
|
|
|
}
|
2021-03-21 14:04:39 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
#[post("/register", data = "<register>")]
|
|
|
|
#[allow(clippy::clippy::needless_pass_by_value)]
|
2021-04-13 12:14:07 -06:00
|
|
|
pub fn register(
|
|
|
|
conn: AtuinDbConn,
|
|
|
|
register: Json<RegisterRequest>,
|
|
|
|
settings: State<Settings>,
|
|
|
|
) -> ApiResponse {
|
|
|
|
if !settings.server.open_registration {
|
|
|
|
return ApiResponse {
|
|
|
|
status: Status::BadRequest,
|
|
|
|
json: json!({
|
|
|
|
"message": "registrations are not open"
|
|
|
|
}),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
let hashed = hash_secret(register.password.as_str());
|
2021-03-21 14:04:39 -06:00
|
|
|
|
|
|
|
let new_user = NewUser {
|
|
|
|
email: register.email.as_str(),
|
2021-04-13 12:14:07 -06:00
|
|
|
username: register.username.as_str(),
|
2021-03-21 14:04:39 -06:00
|
|
|
password: hashed.as_str(),
|
|
|
|
};
|
|
|
|
|
|
|
|
let user = diesel::insert_into(users::table)
|
|
|
|
.values(&new_user)
|
|
|
|
.get_result(&*conn);
|
|
|
|
|
|
|
|
if user.is_err() {
|
|
|
|
return ApiResponse {
|
|
|
|
status: Status::BadRequest,
|
|
|
|
json: json!({
|
2021-04-13 12:14:07 -06:00
|
|
|
"message": "failed to create user - username or email in use?",
|
2021-03-21 14:04:39 -06:00
|
|
|
}),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
let user: User = user.unwrap();
|
|
|
|
let token = Uuid::new_v4().to_simple().to_string();
|
|
|
|
|
|
|
|
let new_session = NewSession {
|
|
|
|
user_id: user.id,
|
|
|
|
token: token.as_str(),
|
|
|
|
};
|
|
|
|
|
|
|
|
match diesel::insert_into(sessions::table)
|
|
|
|
.values(&new_session)
|
|
|
|
.execute(&*conn)
|
|
|
|
{
|
|
|
|
Ok(_) => ApiResponse {
|
|
|
|
status: Status::Ok,
|
2021-04-13 12:14:07 -06:00
|
|
|
json: json!({"message": "user created!", "session": token}),
|
2021-03-21 14:04:39 -06:00
|
|
|
},
|
|
|
|
Err(_) => ApiResponse {
|
|
|
|
status: Status::BadRequest,
|
2021-04-13 12:14:07 -06:00
|
|
|
json: json!({ "message": "failed to create user"}),
|
2021-03-21 14:04:39 -06:00
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[post("/login", data = "<login>")]
|
|
|
|
#[allow(clippy::clippy::needless_pass_by_value)]
|
2021-04-13 12:14:07 -06:00
|
|
|
pub fn login(conn: AtuinDbConn, login: Json<LoginRequest>) -> ApiResponse {
|
2021-03-21 14:04:39 -06:00
|
|
|
let user = users::table
|
2021-04-13 12:14:07 -06:00
|
|
|
.filter(users::username.eq(login.username.as_str()))
|
2021-03-21 14:04:39 -06:00
|
|
|
.first(&*conn);
|
|
|
|
|
|
|
|
if user.is_err() {
|
|
|
|
return ApiResponse {
|
|
|
|
status: Status::NotFound,
|
2021-04-13 12:14:07 -06:00
|
|
|
json: json!({"message": "user not found"}),
|
2021-03-21 14:04:39 -06:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
let user: User = user.unwrap();
|
|
|
|
|
|
|
|
let session = sessions::table
|
|
|
|
.filter(sessions::user_id.eq(user.id))
|
|
|
|
.first(&*conn);
|
|
|
|
|
|
|
|
// a session should exist...
|
|
|
|
if session.is_err() {
|
|
|
|
return ApiResponse {
|
|
|
|
status: Status::InternalServerError,
|
2021-04-13 12:14:07 -06:00
|
|
|
json: json!({"message": "something went wrong"}),
|
2021-03-21 14:04:39 -06:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
let verified = verify_str(user.password.as_str(), login.password.as_str());
|
|
|
|
|
|
|
|
if !verified {
|
|
|
|
return ApiResponse {
|
|
|
|
status: Status::NotFound,
|
2021-04-13 12:14:07 -06:00
|
|
|
json: json!({"message": "user not found"}),
|
2021-03-21 14:04:39 -06:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
let session: Session = session.unwrap();
|
|
|
|
|
|
|
|
ApiResponse {
|
|
|
|
status: Status::Ok,
|
2021-04-13 12:14:07 -06:00
|
|
|
json: json!({"session": session.token}),
|
2021-03-21 14:04:39 -06:00
|
|
|
}
|
|
|
|
}
|