atuin-server 0.7.2

server library for atuin
Documentation
/*
use self::diesel::prelude::*;
use eyre::Result;
use rocket::http::Status;
use rocket::request::{self, FromRequest, Outcome, Request};
use rocket::State;
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;

use crate::api::{LoginRequest, RegisterRequest};
use crate::schema::{sessions, users};
use crate::settings::Settings;
use crate::utils::hash_secret;

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)),
        }
    }
}

#[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,
    }
}

#[post("/register", data = "<register>")]
#[allow(clippy::clippy::needless_pass_by_value)]
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());

    let new_user = NewUser {
        email: register.email.as_str(),
        username: register.username.as_str(),
        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!({
                "message": "failed to create user - username or email in use?",
            }),
        };
    }

    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,
            json: json!({"message": "user created!", "session": token}),
        },
        Err(_) => ApiResponse {
            status: Status::BadRequest,
            json: json!({ "message": "failed to create user"}),
        },
    }
}

#[post("/login", data = "<login>")]
#[allow(clippy::clippy::needless_pass_by_value)]
pub fn login(conn: AtuinDbConn, login: Json<LoginRequest>) -> ApiResponse {
    let user = users::table
        .filter(users::username.eq(login.username.as_str()))
        .first(&*conn);

    if user.is_err() {
        return ApiResponse {
            status: Status::NotFound,
            json: json!({"message": "user not found"}),
        };
    }

    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,
            json: json!({"message": "something went wrong"}),
        };
    }

    let verified = verify_str(user.password.as_str(), login.password.as_str());

    if !verified {
        return ApiResponse {
            status: Status::NotFound,
            json: json!({"message": "user not found"}),
        };
    }

    let session: Session = session.unwrap();

    ApiResponse {
        status: Status::Ok,
        json: json!({"session": session.token}),
    }
}
*/