aboutsummaryrefslogtreecommitdiffstats
path: root/src/server
diff options
context:
space:
mode:
Diffstat (limited to 'src/server')
-rw-r--r--src/server/auth.rs222
-rw-r--r--src/server/database.rs202
-rw-r--r--src/server/handlers/history.rs89
-rw-r--r--src/server/handlers/mod.rs6
-rw-r--r--src/server/handlers/user.rs140
-rw-r--r--src/server/mod.rs23
-rw-r--r--src/server/models.rs49
-rw-r--r--src/server/router.rs121
8 files changed, 0 insertions, 852 deletions
diff --git a/src/server/auth.rs b/src/server/auth.rs
deleted file mode 100644
index 52a73108..00000000
--- a/src/server/auth.rs
+++ /dev/null
@@ -1,222 +0,0 @@
-/*
-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}),
- }
-}
-*/
diff --git a/src/server/database.rs b/src/server/database.rs
deleted file mode 100644
index 5945baaf..00000000
--- a/src/server/database.rs
+++ /dev/null
@@ -1,202 +0,0 @@
-use async_trait::async_trait;
-
-use eyre::{eyre, Result};
-use sqlx::postgres::PgPoolOptions;
-
-use crate::settings::HISTORY_PAGE_SIZE;
-
-use super::models::{History, NewHistory, NewSession, NewUser, Session, User};
-
-#[async_trait]
-pub trait Database {
- async fn get_session(&self, token: &str) -> Result<Session>;
- async fn get_session_user(&self, token: &str) -> Result<User>;
- async fn add_session(&self, session: &NewSession) -> Result<()>;
-
- async fn get_user(&self, username: String) -> Result<User>;
- async fn get_user_session(&self, u: &User) -> Result<Session>;
- async fn add_user(&self, user: NewUser) -> Result<i64>;
-
- async fn count_history(&self, user: &User) -> Result<i64>;
- async fn list_history(
- &self,
- user: &User,
- created_since: chrono::NaiveDateTime,
- since: chrono::NaiveDateTime,
- host: String,
- ) -> Result<Vec<History>>;
- async fn add_history(&self, history: &[NewHistory]) -> Result<()>;
-}
-
-#[derive(Clone)]
-pub struct Postgres {
- pool: sqlx::Pool<sqlx::postgres::Postgres>,
-}
-
-impl Postgres {
- pub async fn new(uri: &str) -> Result<Self, sqlx::Error> {
- let pool = PgPoolOptions::new()
- .max_connections(100)
- .connect(uri)
- .await?;
-
- Ok(Self { pool })
- }
-}
-
-#[async_trait]
-impl Database for Postgres {
- async fn get_session(&self, token: &str) -> Result<Session> {
- let res: Option<Session> =
- sqlx::query_as::<_, Session>("select * from sessions where token = $1")
- .bind(token)
- .fetch_optional(&self.pool)
- .await?;
-
- if let Some(s) = res {
- Ok(s)
- } else {
- Err(eyre!("could not find session"))
- }
- }
-
- async fn get_user(&self, username: String) -> Result<User> {
- let res: Option<User> =
- sqlx::query_as::<_, User>("select * from users where username = $1")
- .bind(username)
- .fetch_optional(&self.pool)
- .await?;
-
- if let Some(u) = res {
- Ok(u)
- } else {
- Err(eyre!("could not find user"))
- }
- }
-
- async fn get_session_user(&self, token: &str) -> Result<User> {
- let res: Option<User> = sqlx::query_as::<_, User>(
- "select * from users
- inner join sessions
- on users.id = sessions.user_id
- and sessions.token = $1",
- )
- .bind(token)
- .fetch_optional(&self.pool)
- .await?;
-
- if let Some(u) = res {
- Ok(u)
- } else {
- Err(eyre!("could not find user"))
- }
- }
-
- async fn count_history(&self, user: &User) -> Result<i64> {
- let res: (i64,) = sqlx::query_as(
- "select count(1) from history
- where user_id = $1",
- )
- .bind(user.id)
- .fetch_one(&self.pool)
- .await?;
-
- Ok(res.0)
- }
-
- async fn list_history(
- &self,
- user: &User,
- created_since: chrono::NaiveDateTime,
- since: chrono::NaiveDateTime,
- host: String,
- ) -> Result<Vec<History>> {
- let res = sqlx::query_as::<_, History>(
- "select * from history
- where user_id = $1
- and hostname != $2
- and created_at >= $3
- and timestamp >= $4
- order by timestamp asc
- limit $5",
- )
- .bind(user.id)
- .bind(host)
- .bind(created_since)
- .bind(since)
- .bind(HISTORY_PAGE_SIZE)
- .fetch_all(&self.pool)
- .await?;
-
- Ok(res)
- }
-
- async fn add_history(&self, history: &[NewHistory]) -> Result<()> {
- let mut tx = self.pool.begin().await?;
-
- for i in history {
- sqlx::query(
- "insert into history
- (client_id, user_id, hostname, timestamp, data)
- values ($1, $2, $3, $4, $5)
- on conflict do nothing
- ",
- )
- .bind(i.client_id)
- .bind(i.user_id)
- .bind(i.hostname)
- .bind(i.timestamp)
- .bind(i.data)
- .execute(&mut tx)
- .await?;
- }
-
- tx.commit().await?;
-
- Ok(())
- }
-
- async fn add_user(&self, user: NewUser) -> Result<i64> {
- let res: (i64,) = sqlx::query_as(
- "insert into users
- (username, email, password)
- values($1, $2, $3)
- returning id",
- )
- .bind(user.username.as_str())
- .bind(user.email.as_str())
- .bind(user.password)
- .fetch_one(&self.pool)
- .await?;
-
- Ok(res.0)
- }
-
- async fn add_session(&self, session: &NewSession) -> Result<()> {
- sqlx::query(
- "insert into sessions
- (user_id, token)
- values($1, $2)",
- )
- .bind(session.user_id)
- .bind(session.token)
- .execute(&self.pool)
- .await?;
-
- Ok(())
- }
-
- async fn get_user_session(&self, u: &User) -> Result<Session> {
- let res: Option<Session> =
- sqlx::query_as::<_, Session>("select * from sessions where user_id = $1")
- .bind(u.id)
- .fetch_optional(&self.pool)
- .await?;
-
- if let Some(s) = res {
- Ok(s)
- } else {
- Err(eyre!("could not find session"))
- }
- }
-}
diff --git a/src/server/handlers/history.rs b/src/server/handlers/history.rs
deleted file mode 100644
index 4fd6f03f..00000000
--- a/src/server/handlers/history.rs
+++ /dev/null
@@ -1,89 +0,0 @@
-use std::convert::Infallible;
-
-use warp::{http::StatusCode, reply::json};
-
-use crate::api::{
- AddHistoryRequest, CountResponse, ErrorResponse, SyncHistoryRequest, SyncHistoryResponse,
-};
-use crate::server::database::Database;
-use crate::server::models::{NewHistory, User};
-
-pub async fn count(
- user: User,
- db: impl Database + Clone + Send + Sync,
-) -> Result<Box<dyn warp::Reply>, Infallible> {
- db.count_history(&user).await.map_or(
- Ok(Box::new(ErrorResponse::reply(
- "failed to query history count",
- StatusCode::INTERNAL_SERVER_ERROR,
- ))),
- |count| Ok(Box::new(json(&CountResponse { count }))),
- )
-}
-
-pub async fn list(
- req: SyncHistoryRequest,
- user: User,
- db: impl Database + Clone + Send + Sync,
-) -> Result<Box<dyn warp::Reply>, Infallible> {
- let history = db
- .list_history(
- &user,
- req.sync_ts.naive_utc(),
- req.history_ts.naive_utc(),
- req.host,
- )
- .await;
-
- if let Err(e) = history {
- error!("failed to load history: {}", e);
- let resp =
- ErrorResponse::reply("failed to load history", StatusCode::INTERNAL_SERVER_ERROR);
- let resp = Box::new(resp);
- return Ok(resp);
- }
-
- let history: Vec<String> = history
- .unwrap()
- .iter()
- .map(|i| i.data.to_string())
- .collect();
-
- debug!(
- "loaded {} items of history for user {}",
- history.len(),
- user.id
- );
-
- Ok(Box::new(json(&SyncHistoryResponse { history })))
-}
-
-pub async fn add(
- req: Vec<AddHistoryRequest>,
- user: User,
- db: impl Database + Clone + Send + Sync,
-) -> Result<Box<dyn warp::Reply>, Infallible> {
- debug!("request to add {} history items", req.len());
-
- let history: Vec<NewHistory> = req
- .iter()
- .map(|h| NewHistory {
- client_id: h.id.as_str(),
- user_id: user.id,
- hostname: h.hostname.as_str(),
- timestamp: h.timestamp.naive_utc(),
- data: h.data.as_str(),
- })
- .collect();
-
- if let Err(e) = db.add_history(&history).await {
- error!("failed to add history: {}", e);
-
- return Ok(Box::new(ErrorResponse::reply(
- "failed to add history",
- StatusCode::INTERNAL_SERVER_ERROR,
- )));
- };
-
- Ok(Box::new(warp::reply()))
-}
diff --git a/src/server/handlers/mod.rs b/src/server/handlers/mod.rs
deleted file mode 100644
index 3c20538c..00000000
--- a/src/server/handlers/mod.rs
+++ /dev/null
@@ -1,6 +0,0 @@
-pub mod history;
-pub mod user;
-
-pub const fn index() -> &'static str {
- "\"Through the fathomless deeps of space swims the star turtle Great A\u{2019}Tuin, bearing on its back the four giant elephants who carry on their shoulders the mass of the Discworld.\"\n\t-- Sir Terry Pratchett"
-}
diff --git a/src/server/handlers/user.rs b/src/server/handlers/user.rs
deleted file mode 100644
index 782d7dbd..00000000
--- a/src/server/handlers/user.rs
+++ /dev/null
@@ -1,140 +0,0 @@
-use std::convert::Infallible;
-
-use sodiumoxide::crypto::pwhash::argon2id13;
-use uuid::Uuid;
-use warp::http::StatusCode;
-use warp::reply::json;
-
-use crate::api::{
- ErrorResponse, LoginRequest, LoginResponse, RegisterRequest, RegisterResponse, UserResponse,
-};
-use crate::server::database::Database;
-use crate::server::models::{NewSession, NewUser};
-use crate::settings::Settings;
-use crate::utils::hash_secret;
-
-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,
- }
-}
-
-pub async fn get(
- username: String,
- db: impl Database + Clone + Send + Sync,
-) -> Result<Box<dyn warp::Reply>, Infallible> {
- let user = match db.get_user(username).await {
- Ok(user) => user,
- Err(e) => {
- debug!("user not found: {}", e);
- return Ok(Box::new(ErrorResponse::reply(
- "user not found",
- StatusCode::NOT_FOUND,
- )));
- }
- };
-
- Ok(Box::new(warp::reply::json(&UserResponse {
- username: user.username,
- })))
-}
-
-pub async fn register(
- register: RegisterRequest,
- settings: Settings,
- db: impl Database + Clone + Send + Sync,
-) -> Result<Box<dyn warp::Reply>, Infallible> {
- if !settings.server.open_registration {
- return Ok(Box::new(ErrorResponse::reply(
- "this server is not open for registrations",
- StatusCode::BAD_REQUEST,
- )));
- }
-
- let hashed = hash_secret(register.password.as_str());
-
- let new_user = NewUser {
- email: register.email,
- username: register.username,
- password: hashed,
- };
-
- let user_id = match db.add_user(new_user).await {
- Ok(id) => id,
- Err(e) => {
- error!("failed to add user: {}", e);
- return Ok(Box::new(ErrorResponse::reply(
- "failed to add user",
- StatusCode::BAD_REQUEST,
- )));
- }
- };
-
- let token = Uuid::new_v4().to_simple().to_string();
-
- let new_session = NewSession {
- user_id,
- token: token.as_str(),
- };
-
- match db.add_session(&new_session).await {
- Ok(_) => Ok(Box::new(json(&RegisterResponse { session: token }))),
- Err(e) => {
- error!("failed to add session: {}", e);
- Ok(Box::new(ErrorResponse::reply(
- "failed to register user",
- StatusCode::BAD_REQUEST,
- )))
- }
- }
-}
-
-pub async fn login(
- login: LoginRequest,
- db: impl Database + Clone + Send + Sync,
-) -> Result<Box<dyn warp::Reply>, Infallible> {
- let user = match db.get_user(login.username.clone()).await {
- Ok(u) => u,
- Err(e) => {
- error!("failed to get user {}: {}", login.username.clone(), e);
-
- return Ok(Box::new(ErrorResponse::reply(
- "user not found",
- StatusCode::NOT_FOUND,
- )));
- }
- };
-
- let session = match db.get_user_session(&user).await {
- Ok(u) => u,
- Err(e) => {
- error!("failed to get session for {}: {}", login.username, e);
-
- return Ok(Box::new(ErrorResponse::reply(
- "user not found",
- StatusCode::NOT_FOUND,
- )));
- }
- };
-
- let verified = verify_str(user.password.as_str(), login.password.as_str());
-
- if !verified {
- return Ok(Box::new(ErrorResponse::reply(
- "user not found",
- StatusCode::NOT_FOUND,
- )));
- }
-
- Ok(Box::new(warp::reply::json(&LoginResponse {
- session: session.token,
- })))
-}
diff --git a/src/server/mod.rs b/src/server/mod.rs
deleted file mode 100644
index d5e083df..00000000
--- a/src/server/mod.rs
+++ /dev/null
@@ -1,23 +0,0 @@
-use std::net::IpAddr;
-
-use eyre::Result;
-
-use crate::settings::Settings;
-
-pub mod auth;
-pub mod database;
-pub mod handlers;
-pub mod models;
-pub mod router;
-
-pub async fn launch(settings: &Settings, host: String, port: u16) -> Result<()> {
- // routes to run:
- // index, register, add_history, login, get_user, sync_count, sync_list
- let host = host.parse::<IpAddr>()?;
-
- let r = router::router(settings).await?;
-
- warp::serve(r).run((host, port)).await;
-
- Ok(())
-}
diff --git a/src/server/models.rs b/src/server/models.rs
deleted file mode 100644
index fbf1897e..00000000
--- a/src/server/models.rs
+++ /dev/null
@@ -1,49 +0,0 @@
-use chrono::prelude::*;
-
-#[derive(sqlx::FromRow)]
-pub struct History {
- pub id: i64,
- pub client_id: String, // a client generated ID
- pub user_id: i64,
- pub hostname: String,
- pub timestamp: NaiveDateTime,
-
- pub data: String,
-
- pub created_at: NaiveDateTime,
-}
-
-pub struct NewHistory<'a> {
- pub client_id: &'a str,
- pub user_id: i64,
- pub hostname: &'a str,
- pub timestamp: chrono::NaiveDateTime,
-
- pub data: &'a str,
-}
-
-#[derive(sqlx::FromRow)]
-pub struct User {
- pub id: i64,
- pub username: String,
- pub email: String,
- pub password: String,
-}
-
-#[derive(sqlx::FromRow)]
-pub struct Session {
- pub id: i64,
- pub user_id: i64,
- pub token: String,
-}
-
-pub struct NewUser {
- pub username: String,
- pub email: String,
- pub password: String,
-}
-
-pub struct NewSession<'a> {
- pub user_id: i64,
- pub token: &'a str,
-}
diff --git a/src/server/router.rs b/src/server/router.rs
deleted file mode 100644
index ed317ab2..00000000
--- a/src/server/router.rs
+++ /dev/null
@@ -1,121 +0,0 @@
-use std::convert::Infallible;
-
-use eyre::Result;
-use warp::Filter;
-
-use super::handlers;
-use super::{database::Database, database::Postgres};
-use crate::server::models::User;
-use crate::{api::SyncHistoryRequest, settings::Settings};
-
-fn with_settings(
- settings: Settings,
-) -> impl Filter<Extract = (Settings,), Error = Infallible> + Clone {
- warp::any().map(move || settings.clone())
-}
-
-fn with_db(
- db: impl Database + Clone + Send + Sync,
-) -> impl Filter<Extract = (impl Database + Clone,), Error = Infallible> + Clone {
- warp::any().map(move || db.clone())
-}
-
-fn with_user(
- postgres: Postgres,
-) -> impl Filter<Extract = (User,), Error = warp::Rejection> + Clone {
- warp::header::<String>("authorization").and_then(move |header: String| {
- // async closures are still buggy :(
- let postgres = postgres.clone();
-
- async move {
- let header: Vec<&str> = header.split(' ').collect();
-
- let token;
-
- if header.len() == 2 {
- if header[0] != "Token" {
- return Err(warp::reject());
- }
-
- token = header[1];
- } else {
- return Err(warp::reject());
- }
-
- let user = postgres
- .get_session_user(token)
- .await
- .map_err(|_| warp::reject())?;
-
- Ok(user)
- }
- })
-}
-
-pub async fn router(
- settings: &Settings,
-) -> Result<impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone> {
- let postgres = Postgres::new(settings.server.db_uri.as_str()).await?;
- let index = warp::get().and(warp::path::end()).map(handlers::index);
-
- let count = warp::get()
- .and(warp::path("sync"))
- .and(warp::path("count"))
- .and(warp::path::end())
- .and(with_user(postgres.clone()))
- .and(with_db(postgres.clone()))
- .and_then(handlers::history::count);
-
- let sync = warp::get()
- .and(warp::path("sync"))
- .and(warp::path("history"))
- .and(warp::query::<SyncHistoryRequest>())
- .and(warp::path::end())
- .and(with_user(postgres.clone()))
- .and(with_db(postgres.clone()))
- .and_then(handlers::history::list);
-
- let add_history = warp::post()
- .and(warp::path("history"))
- .and(warp::path::end())
- .and(warp::body::json())
- .and(with_user(postgres.clone()))
- .and(with_db(postgres.clone()))
- .and_then(handlers::history::add);
-
- let user = warp::get()
- .and(warp::path("user"))
- .and(warp::path::param::<String>())
- .and(warp::path::end())
- .and(with_db(postgres.clone()))
- .and_then(handlers::user::get);
-
- let register = warp::post()
- .and(warp::path("register"))
- .and(warp::path::end())
- .and(warp::body::json())
- .and(with_settings(settings.clone()))
- .and(with_db(postgres.clone()))
- .and_then(handlers::user::register);
-
- let login = warp::post()
- .and(warp::path("login"))
- .and(warp::path::end())
- .and(warp::body::json())
- .and(with_db(postgres))
- .and_then(handlers::user::login);
-
- let r = warp::any()
- .and(
- index
- .or(count)
- .or(sync)
- .or(add_history)
- .or(user)
- .or(register)
- .or(login),
- )
- .with(warp::filters::log::log("atuin::api"));
-
- Ok(r)
-}