aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBenedikt Peetz <benedikt.peetz@b-peetz.de>2026-06-11 16:27:35 +0200
committerBenedikt Peetz <benedikt.peetz@b-peetz.de>2026-06-11 16:27:35 +0200
commit5a4c4b9fa0df891fcdd9beee18be0db4a45da701 (patch)
treeb467a1bd5706643b77962e7a82d651a803bfc436
parentchore(server): Simplify the database support (diff)
downloadatuin-5a4c4b9fa0df891fcdd9beee18be0db4a45da701.zip
chore(server): Remove the last remnants of the "hub" sync-server thingy
-rw-r--r--crates/turtle/src/atuin_client/auth.rs94
-rw-r--r--crates/turtle/src/atuin_common/api.rs60
-rw-r--r--crates/turtle/src/atuin_server/database/db/mod.rs265
-rw-r--r--crates/turtle/src/atuin_server/database/db/wrappers.rs22
-rw-r--r--crates/turtle/src/atuin_server/database/mod.rs28
-rw-r--r--crates/turtle/src/atuin_server/handlers/user.rs8
-rw-r--r--crates/turtle/src/command/client/account/change_password.rs22
-rw-r--r--crates/turtle/src/command/client/account/delete.rs22
-rw-r--r--crates/turtle/src/command/client/account/login.rs15
9 files changed, 46 insertions, 490 deletions
diff --git a/crates/turtle/src/atuin_client/auth.rs b/crates/turtle/src/atuin_client/auth.rs
index b260d433..620e127e 100644
--- a/crates/turtle/src/atuin_client/auth.rs
+++ b/crates/turtle/src/atuin_client/auth.rs
@@ -1,4 +1,3 @@
-use async_trait::async_trait;
use eyre::{Context, Result, bail};
use reqwest::{Url, header::USER_AGENT};
@@ -14,66 +13,19 @@ use crate::atuin_client::settings::Settings;
static APP_USER_AGENT: &str = concat!("atuin/", env!("CARGO_PKG_VERSION"));
-/// Result of an auth operation that may require 2FA.
-pub(crate) enum AuthResponse {
- /// Operation succeeded; for login/register, contains the session token.
- /// `auth_type` indicates the kind of token: `Some("hub")` for Hub API
- /// tokens (prefixed `atapi_`), `Some("cli")` for legacy CLI session
- /// tokens. `None` when the server didn't include the field (old servers).
- Success {
- session: String,
- auth_type: Option<String>,
- },
- /// Two-factor authentication is required; the caller should prompt for a
- /// TOTP code and retry with it.
- TwoFactorRequired,
-}
-
-/// Result of a mutating account operation that may require 2FA.
-pub(crate) enum MutateResponse {
- /// Operation completed successfully.
- Success,
- /// Two-factor authentication is required; the caller should prompt for a
- /// TOTP code and retry.
- TwoFactorRequired,
-}
-
-/// Abstraction over the legacy (Rust sync server) and Hub auth APIs.
-///
-/// CLI commands use this trait so they don't need to know which backend is
-/// active — they just prompt for input and call these methods.
-#[async_trait]
-pub(crate) trait AuthClient: Send + Sync {
- /// Log in with username + password, optionally providing a TOTP code.
- async fn login(&self, username: &str, password: &str) -> Result<AuthResponse>;
-
- /// Register a new account.
- async fn register(&self, username: &str, email: &str, password: &str) -> Result<AuthResponse>;
-
- /// Change the account password, optionally providing a TOTP code.
- async fn change_password(
- &self,
- current_password: &str,
- new_password: &str,
- totp_code: Option<&str>,
- ) -> Result<MutateResponse>;
-
- /// Delete the account, requiring the current password and optionally a TOTP code.
- async fn delete_account(
- &self,
- password: &str,
- totp_code: Option<&str>,
- ) -> Result<MutateResponse>;
+/// Result of an auth operation
+pub(crate) struct AuthResponse {
+ pub(crate) session: String,
}
/// Resolve the appropriate [`AuthClient`] for the current settings.
-pub(crate) async fn auth_client(settings: &Settings) -> Box<dyn AuthClient> {
- Box::new(LegacyAuthClient::new(
+pub(crate) async fn auth_client(settings: &Settings) -> LegacyAuthClient {
+ LegacyAuthClient::new(
&settings.sync_address,
settings.session_token().await.ok(),
settings.network_connect_timeout,
settings.network_timeout,
- )) as Box<dyn AuthClient>
+ )
}
// ---------------------------------------------------------------------------
@@ -125,9 +77,9 @@ impl LegacyAuthClient {
}
}
-#[async_trait]
-impl AuthClient for LegacyAuthClient {
- async fn login(&self, username: &str, password: &str) -> Result<AuthResponse> {
+impl LegacyAuthClient {
+ /// Log in with username + password, optionally providing a TOTP code.
+ pub(crate) async fn login(&self, username: &str, password: &str) -> Result<AuthResponse> {
// The legacy server has no 2FA support; totp_code is ignored.
let resp = api_client::login(
&self.address,
@@ -138,26 +90,31 @@ impl AuthClient for LegacyAuthClient {
)
.await?;
- Ok(AuthResponse::Success {
+ Ok(AuthResponse {
session: resp.session,
- auth_type: resp.auth.or(Some("cli".into())),
})
}
- async fn register(&self, username: &str, email: &str, password: &str) -> Result<AuthResponse> {
+ /// Register a new account.
+ pub(crate) async fn register(
+ &self,
+ username: &str,
+ email: &str,
+ password: &str,
+ ) -> Result<AuthResponse> {
let resp = api_client::register(&self.address, username, email, password).await?;
- Ok(AuthResponse::Success {
+ Ok(AuthResponse {
session: resp.session,
- auth_type: resp.auth.or(Some("cli".into())),
})
}
- async fn change_password(
+ /// Change the account password, optionally providing a TOTP code.
+ pub(crate) async fn change_password(
&self,
current_password: &str,
new_password: &str,
_totp_code: Option<&str>,
- ) -> Result<MutateResponse> {
+ ) -> Result<()> {
let client = self.authenticated_client()?;
let url = make_url(&self.address, "/account/password")?;
@@ -171,18 +128,19 @@ impl AuthClient for LegacyAuthClient {
.await?;
match resp.status().as_u16() {
- 200 => Ok(MutateResponse::Success),
+ 200 => Ok(()),
401 => bail!("current password is incorrect"),
403 => bail!("invalid login details"),
_ => bail!("unknown error"),
}
}
- async fn delete_account(
+ /// Delete the account, requiring the current password and optionally a TOTP code.
+ pub(crate) async fn delete_account(
&self,
password: &str,
_totp_code: Option<&str>,
- ) -> Result<MutateResponse> {
+ ) -> Result<()> {
let client = self.authenticated_client()?;
let url = make_url(&self.address, "/account")?;
@@ -193,7 +151,7 @@ impl AuthClient for LegacyAuthClient {
.await?;
match resp.status().as_u16() {
- 200 => Ok(MutateResponse::Success),
+ 200 => Ok(()),
401 => bail!("password is incorrect"),
403 => bail!("invalid login details"),
_ => bail!("unknown error"),
diff --git a/crates/turtle/src/atuin_common/api.rs b/crates/turtle/src/atuin_common/api.rs
index 56adbcc5..4566c6e9 100644
--- a/crates/turtle/src/atuin_common/api.rs
+++ b/crates/turtle/src/atuin_common/api.rs
@@ -2,7 +2,6 @@ use semver::Version;
use serde::{Deserialize, Serialize};
use std::borrow::Cow;
use std::sync::LazyLock;
-use time::OffsetDateTime;
// the usage of X- has been deprecated for quite along time, it turns out
pub(crate) static ATUIN_HEADER_VERSION: &str = "Atuin-Version";
@@ -26,10 +25,6 @@ pub(crate) struct RegisterRequest {
#[derive(Debug, Serialize, Deserialize)]
pub(crate) struct RegisterResponse {
pub(crate) session: String,
- /// Auth type: "hub" for Hub API tokens, "cli" for legacy CLI session tokens.
- /// Old servers that don't return this field will deserialize as None.
- #[serde(default)]
- pub(crate) auth: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
@@ -53,38 +48,6 @@ pub(crate) struct LoginRequest {
#[derive(Debug, Serialize, Deserialize)]
pub(crate) struct LoginResponse {
pub(crate) session: String,
- /// Auth type: "hub" for Hub API tokens, "cli" for legacy CLI session tokens.
- /// Old servers that don't return this field will deserialize as None.
- #[serde(default)]
- pub(crate) auth: Option<String>,
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-pub(crate) struct AddHistoryRequest {
- pub(crate) id: String,
- #[serde(with = "time::serde::rfc3339")]
- pub(crate) timestamp: OffsetDateTime,
- pub(crate) data: String,
- pub(crate) hostname: String,
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-pub(crate) struct CountResponse {
- pub(crate) count: i64,
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-pub(crate) struct SyncHistoryRequest {
- #[serde(with = "time::serde::rfc3339")]
- pub(crate) sync_ts: OffsetDateTime,
- #[serde(with = "time::serde::rfc3339")]
- pub(crate) history_ts: OffsetDateTime,
- pub(crate) host: String,
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-pub(crate) struct SyncHistoryResponse {
- pub(crate) history: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize)]
@@ -99,29 +62,6 @@ pub(crate) struct IndexResponse {
}
#[derive(Debug, Serialize, Deserialize)]
-pub(crate) struct StatusResponse {
- pub(crate) count: i64,
- pub(crate) username: String,
- pub(crate) deleted: Vec<String>,
-
- // These could/should also go on the index of the server
- // However, we do not request the server index as a part of normal sync
- // I'd rather slightly increase the size of this response, than add an extra HTTP request
- pub(crate) page_size: i64, // max page size supported by the server
- pub(crate) version: String,
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-pub(crate) struct DeleteHistoryRequest {
- pub(crate) client_id: String,
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-pub(crate) struct MessageResponse {
- pub(crate) message: String,
-}
-
-#[derive(Debug, Serialize, Deserialize)]
pub(crate) struct MeResponse {
pub(crate) username: String,
}
diff --git a/crates/turtle/src/atuin_server/database/db/mod.rs b/crates/turtle/src/atuin_server/database/db/mod.rs
index 22d69d3c..e0c6b736 100644
--- a/crates/turtle/src/atuin_server/database/db/mod.rs
+++ b/crates/turtle/src/atuin_server/database/db/mod.rs
@@ -1,5 +1,4 @@
use std::collections::HashMap;
-use std::ops::Range;
use rand::Rng;
@@ -7,19 +6,14 @@ use crate::{
atuin_common::record::{EncryptedData, HostId, Record, RecordIdx, RecordStatus},
atuin_server::database::{
DbError, DbResult, DbSettings,
- calendar::{TimePeriod, TimePeriodInfo},
- into_utc,
- models::{History, NewHistory, NewSession, NewUser, Session, User},
+ models::{NewSession, NewUser, Session, User},
},
};
-use futures_util::TryStreamExt;
-use sqlx::Row;
use sqlx::postgres::PgPoolOptions;
-use time::{Date, Duration, Month, OffsetDateTime, Time, UtcOffset};
use tracing::instrument;
use uuid::Uuid;
-use wrappers::{DbHistory, DbRecord, DbSession, DbUser};
+use wrappers::{DbRecord, DbSession, DbUser};
mod wrappers;
@@ -102,94 +96,6 @@ impl Database {
}
#[instrument(skip_all)]
- pub(crate) async fn calendar(
- &self,
- user: &User,
- period: TimePeriod,
- tz: UtcOffset,
- ) -> DbResult<HashMap<u64, TimePeriodInfo>> {
- let mut ret = HashMap::new();
- let iter: Box<dyn Iterator<Item = DbResult<(u64, Range<Date>)>> + Send> = match period {
- TimePeriod::Year => {
- // First we need to work out how far back to calculate. Get the
- // oldest history item
- let oldest = self
- .oldest_history(user)
- .await?
- .timestamp
- .to_offset(tz)
- .year();
- let current_year = OffsetDateTime::now_utc().to_offset(tz).year();
-
- // All the years we need to get data for
- // The upper bound is exclusive, so include current +1
- let years = oldest..current_year + 1;
-
- Box::new(years.map(|year| {
- let start = Date::from_calendar_date(year, time::Month::January, 1)?;
- let end = Date::from_calendar_date(year + 1, time::Month::January, 1)?;
-
- Ok((year as u64, start..end))
- }))
- }
-
- TimePeriod::Month { year } => {
- let months =
- std::iter::successors(Some(Month::January), |m| Some(m.next())).take(12);
-
- Box::new(months.map(move |month| {
- let start = Date::from_calendar_date(year, month, 1)?;
- let days = start.month().length(year);
- let end = start + Duration::days(days as i64);
-
- Ok((month as u64, start..end))
- }))
- }
-
- TimePeriod::Day { year, month } => {
- let days = 1..month.length(year);
- Box::new(days.map(move |day| {
- let start = Date::from_calendar_date(year, month, day)?;
- let end = start
- .next_day()
- .ok_or_else(|| DbError::Other(eyre::eyre!("no next day?")))?;
-
- Ok((day as u64, start..end))
- }))
- }
- };
-
- for x in iter {
- let (index, range) = x?;
-
- let start = range.start.with_time(Time::MIDNIGHT).assume_offset(tz);
- let end = range.end.with_time(Time::MIDNIGHT).assume_offset(tz);
-
- let count = self.count_history_range(user, start..end).await?;
-
- ret.insert(
- index,
- TimePeriodInfo {
- count: count as u64,
- hash: "".to_string(),
- },
- );
- }
-
- Ok(ret)
- }
-
- #[instrument(skip_all)]
- pub(crate) async fn get_session(&self, token: &str) -> DbResult<Session> {
- sqlx::query_as("select id, user_id, token from sessions where token = $1")
- .bind(token)
- .fetch_one(self.read_pool())
- .await
- .map_err(Into::into)
- .map(|DbSession(session)| session)
- }
-
- #[instrument(skip_all)]
pub(crate) async fn get_user(&self, username: &str) -> DbResult<User> {
sqlx::query_as("select id, username, email, password from users where username = $1")
.bind(username)
@@ -214,36 +120,6 @@ impl Database {
.map(|DbUser(user)| user)
}
- #[instrument(skip_all)]
- pub(crate) async fn count_history(&self, user: &User) -> DbResult<i64> {
- // The cache is new, and the user might not yet have a cache value.
- // They will have one as soon as they post up some new history, but handle that
- // edge case.
-
- let res: (i64,) = sqlx::query_as(
- "select count(1) from history
- where user_id = $1",
- )
- .bind(user.id)
- .fetch_one(self.read_pool())
- .await?;
-
- Ok(res.0)
- }
-
- #[instrument(skip_all)]
- pub(crate) async fn count_history_cached(&self, user: &User) -> DbResult<i64> {
- let res: (i32,) = sqlx::query_as(
- "select total from total_history_count_user
- where user_id = $1",
- )
- .bind(user.id)
- .fetch_one(self.read_pool())
- .await?;
-
- Ok(res.0 as i64)
- }
-
pub(crate) async fn delete_store(&self, user: &User) -> DbResult<()> {
let mut tx = self.pool.begin().await?;
@@ -268,128 +144,6 @@ impl Database {
Ok(())
}
- pub(crate) async fn delete_history(&self, user: &User, id: String) -> DbResult<()> {
- sqlx::query(
- "update history
- set deleted_at = $3
- where user_id = $1
- and client_id = $2
- and deleted_at is null", // don't just keep setting it
- )
- .bind(user.id)
- .bind(id)
- .bind(OffsetDateTime::now_utc())
- .fetch_all(&self.pool)
- .await?;
-
- Ok(())
- }
-
- #[instrument(skip_all)]
- pub(crate) async fn deleted_history(&self, user: &User) -> DbResult<Vec<String>> {
- // The cache is new, and the user might not yet have a cache value.
- // They will have one as soon as they post up some new history, but handle that
- // edge case.
-
- let res = sqlx::query(
- "select client_id from history
- where user_id = $1
- and deleted_at is not null",
- )
- .bind(user.id)
- .fetch_all(self.read_pool())
- .await?;
-
- let res = res
- .iter()
- .map(|row| row.get::<String, _>("client_id"))
- .collect();
-
- Ok(res)
- }
-
- #[instrument(skip_all)]
- pub(crate) async fn count_history_range(
- &self,
- user: &User,
- range: Range<OffsetDateTime>,
- ) -> DbResult<i64> {
- let res: (i64,) = sqlx::query_as(
- "select count(1) from history
- where user_id = $1
- and timestamp >= $2::date
- and timestamp < $3::date",
- )
- .bind(user.id)
- .bind(into_utc(range.start))
- .bind(into_utc(range.end))
- .fetch_one(self.read_pool())
- .await?;
-
- Ok(res.0)
- }
-
- #[instrument(skip_all)]
- pub(crate) async fn list_history(
- &self,
- user: &User,
- created_after: OffsetDateTime,
- since: OffsetDateTime,
- host: &str,
- page_size: i64,
- ) -> DbResult<Vec<History>> {
- let res = sqlx::query_as(
- "select id, client_id, user_id, hostname, timestamp, data, created_at 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(into_utc(created_after))
- .bind(into_utc(since))
- .bind(page_size)
- .fetch(self.read_pool())
- .map_ok(|DbHistory(h)| h)
- .try_collect()
- .await?;
-
- Ok(res)
- }
-
- #[instrument(skip_all)]
- pub(crate) async fn add_history(&self, history: &[NewHistory]) -> DbResult<()> {
- let mut tx = self.pool.begin().await?;
-
- for i in history {
- let client_id: &str = &i.client_id;
- let hostname: &str = &i.hostname;
- let data: &str = &i.data;
-
- sqlx::query(
- "insert into history
- (client_id, user_id, hostname, timestamp, data)
- values ($1, $2, $3, $4, $5)
- on conflict do nothing
- ",
- )
- .bind(client_id)
- .bind(i.user_id)
- .bind(hostname)
- .bind(i.timestamp)
- .bind(data)
- .execute(&mut *tx)
- .await?;
- }
-
- tx.commit().await?;
-
- Ok(())
- }
-
#[instrument(skip_all)]
pub(crate) async fn delete_user(&self, u: &User) -> DbResult<()> {
sqlx::query("delete from sessions where user_id = $1")
@@ -484,21 +238,6 @@ impl Database {
}
#[instrument(skip_all)]
- pub(crate) async fn oldest_history(&self, user: &User) -> DbResult<History> {
- sqlx::query_as(
- "select id, client_id, user_id, hostname, timestamp, data, created_at from history
- where user_id = $1
- order by timestamp asc
- limit 1",
- )
- .bind(user.id)
- .fetch_one(self.read_pool())
- .await
- .map_err(Into::into)
- .map(|DbHistory(h)| h)
- }
-
- #[instrument(skip_all)]
pub(crate) async fn add_records(
&self,
user: &User,
diff --git a/crates/turtle/src/atuin_server/database/db/wrappers.rs b/crates/turtle/src/atuin_server/database/db/wrappers.rs
index de4c5814..c0633202 100644
--- a/crates/turtle/src/atuin_server/database/db/wrappers.rs
+++ b/crates/turtle/src/atuin_server/database/db/wrappers.rs
@@ -1,14 +1,12 @@
use crate::{
atuin_common::record::{EncryptedData, Host, Record},
- atuin_server::database::models::{History, Session, User},
+ atuin_server::database::models::{Session, User},
};
use ::sqlx::{FromRow, Result};
use sqlx::{Row, postgres::PgRow};
-use time::PrimitiveDateTime;
pub struct DbUser(pub User);
pub struct DbSession(pub Session);
-pub struct DbHistory(pub History);
pub struct DbRecord(pub Record<EncryptedData>);
impl<'a> FromRow<'a, PgRow> for DbUser {
@@ -32,24 +30,6 @@ impl<'a> ::sqlx::FromRow<'a, PgRow> for DbSession {
}
}
-impl<'a> ::sqlx::FromRow<'a, PgRow> for DbHistory {
- fn from_row(row: &'a PgRow) -> ::sqlx::Result<Self> {
- Ok(Self(History {
- id: row.try_get("id")?,
- client_id: row.try_get("client_id")?,
- user_id: row.try_get("user_id")?,
- hostname: row.try_get("hostname")?,
- timestamp: row
- .try_get::<PrimitiveDateTime, _>("timestamp")?
- .assume_utc(),
- data: row.try_get("data")?,
- created_at: row
- .try_get::<PrimitiveDateTime, _>("created_at")?
- .assume_utc(),
- }))
- }
-}
-
impl<'a> ::sqlx::FromRow<'a, PgRow> for DbRecord {
fn from_row(row: &'a PgRow) -> ::sqlx::Result<Self> {
let timestamp: i64 = row.try_get("timestamp")?;
diff --git a/crates/turtle/src/atuin_server/database/mod.rs b/crates/turtle/src/atuin_server/database/mod.rs
index 845d67d7..a009ae1f 100644
--- a/crates/turtle/src/atuin_server/database/mod.rs
+++ b/crates/turtle/src/atuin_server/database/mod.rs
@@ -5,7 +5,6 @@ pub(crate) mod models;
use std::fmt::{Debug, Display};
use serde::{Deserialize, Serialize};
-use time::{OffsetDateTime, PrimitiveDateTime, UtcOffset};
#[derive(Debug)]
pub(crate) enum DbError {
@@ -94,30 +93,3 @@ impl Debug for DbSettings {
}
}
}
-
-pub(crate) fn into_utc(x: OffsetDateTime) -> PrimitiveDateTime {
- let x = x.to_offset(UtcOffset::UTC);
- PrimitiveDateTime::new(x.date(), x.time())
-}
-
-#[cfg(test)]
-mod tests {
- use time::macros::datetime;
-
- use super::into_utc;
-
- #[test]
- fn utc() {
- let dt = datetime!(2023-09-26 15:11:02 +05:30);
- assert_eq!(into_utc(dt), datetime!(2023-09-26 09:41:02));
- assert_eq!(into_utc(dt).assume_utc(), dt);
-
- let dt = datetime!(2023-09-26 15:11:02 -07:00);
- assert_eq!(into_utc(dt), datetime!(2023-09-26 22:11:02));
- assert_eq!(into_utc(dt).assume_utc(), dt);
-
- let dt = datetime!(2023-09-26 15:11:02 +00:00);
- assert_eq!(into_utc(dt), datetime!(2023-09-26 15:11:02));
- assert_eq!(into_utc(dt).assume_utc(), dt);
- }
-}
diff --git a/crates/turtle/src/atuin_server/handlers/user.rs b/crates/turtle/src/atuin_server/handlers/user.rs
index 28cebfab..e777acc3 100644
--- a/crates/turtle/src/atuin_server/handlers/user.rs
+++ b/crates/turtle/src/atuin_server/handlers/user.rs
@@ -152,10 +152,7 @@ pub(crate) async fn register(
counter!("atuin_users_registered").increment(1);
match db.add_session(&new_session).await {
- Ok(_) => Ok(Json(RegisterResponse {
- session: token,
- auth: Some("cli".into()),
- })),
+ Ok(_) => Ok(Json(RegisterResponse { session: token })),
Err(e) => {
error!("failed to add session: {}", e);
Err(ErrorResponse::reply("failed to register user")
@@ -210,7 +207,7 @@ pub(crate) async fn change_password(
return Err(ErrorResponse::reply("failed to change user password")
.with_status(StatusCode::INTERNAL_SERVER_ERROR));
- };
+ }
Ok(Json(ChangePasswordResponse {}))
}
@@ -259,7 +256,6 @@ pub(crate) async fn login(
Ok(Json(LoginResponse {
session: session.token,
- auth: Some("cli".into()),
}))
}
diff --git a/crates/turtle/src/command/client/account/change_password.rs b/crates/turtle/src/command/client/account/change_password.rs
index f7f7eb69..b23f518d 100644
--- a/crates/turtle/src/command/client/account/change_password.rs
+++ b/crates/turtle/src/command/client/account/change_password.rs
@@ -1,10 +1,7 @@
use clap::Parser;
use eyre::{Result, bail};
-use crate::atuin_client::{
- auth::{self, MutateResponse},
- settings::Settings,
-};
+use crate::atuin_client::{auth, settings::Settings};
use rpassword::prompt_password;
#[derive(Parser, Debug)]
@@ -45,20 +42,11 @@ impl Cmd {
bail!("please provide a new password");
}
- let mut totp_code = self.totp_code.clone();
+ let totp_code = self.totp_code.clone();
- loop {
- let response = client
- .change_password(&current_password, &new_password, totp_code.as_deref())
- .await?;
-
- match response {
- MutateResponse::Success => break,
- MutateResponse::TwoFactorRequired => {
- totp_code = Some(super::login::or_user_input(None, "two-factor code"));
- }
- }
- }
+ client
+ .change_password(&current_password, &new_password, totp_code.as_deref())
+ .await?;
println!("Account password successfully changed!");
diff --git a/crates/turtle/src/command/client/account/delete.rs b/crates/turtle/src/command/client/account/delete.rs
index 1c96cb4a..722c39ec 100644
--- a/crates/turtle/src/command/client/account/delete.rs
+++ b/crates/turtle/src/command/client/account/delete.rs
@@ -1,11 +1,8 @@
-use crate::atuin_client::{
- auth::{self, MutateResponse},
- settings::Settings,
-};
+use crate::atuin_client::{auth, settings::Settings};
use clap::Parser;
use eyre::{Result, bail};
-use super::login::{or_user_input, read_user_password};
+use super::login::read_user_password;
#[derive(Parser, Debug)]
pub(crate) struct Cmd {
@@ -33,18 +30,9 @@ impl Cmd {
let mut totp_code = self.totp_code.clone();
- loop {
- let response = client
- .delete_account(&password, totp_code.as_deref())
- .await?;
-
- match response {
- MutateResponse::Success => break,
- MutateResponse::TwoFactorRequired => {
- totp_code = Some(or_user_input(None, "two-factor code"));
- }
- }
- }
+ client
+ .delete_account(&password, totp_code.as_deref())
+ .await?;
// Clean up sessions from meta store
let meta = Settings::meta_store().await?;
diff --git a/crates/turtle/src/command/client/account/login.rs b/crates/turtle/src/command/client/account/login.rs
index 1ec0293a..e9513879 100644
--- a/crates/turtle/src/command/client/account/login.rs
+++ b/crates/turtle/src/command/client/account/login.rs
@@ -5,7 +5,7 @@ use eyre::{Context, Result, bail};
use tokio::{fs::File, io::AsyncWriteExt};
use crate::atuin_client::{
- auth::{self, AuthResponse},
+ auth,
encryption::{decode_key, load_key},
record::sqlite_store::SqliteStore,
record::store::Store,
@@ -67,15 +67,10 @@ impl Cmd {
let client = auth::auth_client(settings).await;
let response = client.login(&username, &password).await?;
- match response {
- AuthResponse::Success { session, .. } => {
- Settings::meta_store().await?.save_session(&session).await?;
- }
- AuthResponse::TwoFactorRequired => {
- // Legacy server doesn't support 2FA, so this shouldn't happen.
- bail!("unexpected two-factor requirement from legacy server");
- }
- }
+ Settings::meta_store()
+ .await?
+ .save_session(&response.session)
+ .await?;
println!("Logged in!");
Ok(())