aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock92
-rw-r--r--crates/atuin-client/src/api_client.rs36
-rw-r--r--crates/atuin-common/src/api.rs16
-rw-r--r--crates/atuin-server-database/src/lib.rs4
-rw-r--r--crates/atuin-server-database/src/models.rs1
-rw-r--r--crates/atuin-server-postgres/migrations/20260127000000_remove-email-verification.sql2
-rw-r--r--crates/atuin-server-postgres/src/lib.rs105
-rw-r--r--crates/atuin-server-postgres/src/wrappers.rs1
-rw-r--r--crates/atuin-server-sqlite/migrations/20260127000000_remove-email-verification.sql2
-rw-r--r--crates/atuin-server-sqlite/src/lib.rs99
-rw-r--r--crates/atuin-server-sqlite/src/wrappers.rs1
-rw-r--r--crates/atuin-server/Cargo.toml1
-rw-r--r--crates/atuin-server/src/handlers/user.rs105
-rw-r--r--crates/atuin-server/src/router.rs5
-rw-r--r--crates/atuin-server/src/settings.rs29
-rw-r--r--crates/atuin/src/command/client/account.rs5
-rw-r--r--crates/atuin/src/command/client/account/verify.rs51
-rw-r--r--crates/atuin/tests/common/mod.rs1
-rw-r--r--docs/docs/faq.md7
19 files changed, 33 insertions, 530 deletions
diff --git a/Cargo.lock b/Cargo.lock
index fdd2f061..b4c86b37 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -289,7 +289,7 @@ dependencies = [
"pretty_assertions",
"rand 0.8.5",
"regex",
- "reqwest 0.13.1",
+ "reqwest",
"rmp",
"rusty_paserk",
"rusty_paseto",
@@ -309,7 +309,7 @@ dependencies = [
"time",
"tiny-bip39",
"tokio",
- "typed-builder 0.18.2",
+ "typed-builder",
"urlencoding",
"uuid",
"whoami",
@@ -330,7 +330,7 @@ dependencies = [
"sysinfo",
"thiserror 1.0.69",
"time",
- "typed-builder 0.18.2",
+ "typed-builder",
"uuid",
]
@@ -401,7 +401,7 @@ dependencies = [
"tokio",
"tracing",
"tracing-subscriber",
- "typed-builder 0.18.2",
+ "typed-builder",
]
[[package]]
@@ -422,7 +422,7 @@ dependencies = [
"tokio",
"tracing",
"tracing-subscriber",
- "typed-builder 0.18.2",
+ "typed-builder",
"uuid",
]
@@ -440,9 +440,8 @@ dependencies = [
"fs-err",
"metrics",
"metrics-exporter-prometheus",
- "postmark",
"rand 0.8.5",
- "reqwest 0.13.1",
+ "reqwest",
"semver",
"serde",
"serde_json",
@@ -1987,7 +1986,6 @@ dependencies = [
"tokio",
"tokio-rustls",
"tower-service",
- "webpki-roots 1.0.5",
]
[[package]]
@@ -3257,24 +3255,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950"
[[package]]
-name = "postmark"
-version = "0.11.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "846751b682939565add1f69358a595fa6f3f7d4f1eb15d920b16478e0f981fe2"
-dependencies = [
- "async-trait",
- "bytes",
- "http",
- "reqwest 0.12.28",
- "serde",
- "serde_json",
- "thiserror 2.0.18",
- "time",
- "typed-builder 0.21.2",
- "url",
-]
-
-[[package]]
name = "potential_utf"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3796,44 +3776,6 @@ checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
[[package]]
name = "reqwest"
-version = "0.12.28"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
-dependencies = [
- "base64",
- "bytes",
- "futures-core",
- "http",
- "http-body",
- "http-body-util",
- "hyper",
- "hyper-rustls",
- "hyper-util",
- "js-sys",
- "log",
- "percent-encoding",
- "pin-project-lite",
- "quinn",
- "rustls",
- "rustls-pki-types",
- "serde",
- "serde_json",
- "serde_urlencoded",
- "sync_wrapper",
- "tokio",
- "tokio-rustls",
- "tower 0.5.3",
- "tower-http",
- "tower-service",
- "url",
- "wasm-bindgen",
- "wasm-bindgen-futures",
- "web-sys",
- "webpki-roots 1.0.5",
-]
-
-[[package]]
-name = "reqwest"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04e9018c9d814e5f30cc16a0f03271aeab3571e609612d9fe78c1aa8d11c2f62"
@@ -5351,16 +5293,7 @@ version = "0.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77739c880e00693faef3d65ea3aad725f196da38b22fdc7ea6ded6e1ce4d3add"
dependencies = [
- "typed-builder-macro 0.18.2",
-]
-
-[[package]]
-name = "typed-builder"
-version = "0.21.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fef81aec2ca29576f9f6ae8755108640d0a86dd3161b2e8bca6cfa554e98f77d"
-dependencies = [
- "typed-builder-macro 0.21.2",
+ "typed-builder-macro",
]
[[package]]
@@ -5375,17 +5308,6 @@ dependencies = [
]
[[package]]
-name = "typed-builder-macro"
-version = "0.21.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ecb9ecf7799210407c14a8cfdfe0173365780968dc57973ed082211958e0b18"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.114",
-]
-
-[[package]]
name = "typenum"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/crates/atuin-client/src/api_client.rs b/crates/atuin-client/src/api_client.rs
index 41f824a9..86452d50 100644
--- a/crates/atuin-client/src/api_client.rs
+++ b/crates/atuin-client/src/api_client.rs
@@ -15,9 +15,8 @@ use atuin_common::{
use atuin_common::{
api::{
AddHistoryRequest, ChangePasswordRequest, CountResponse, DeleteHistoryRequest,
- ErrorResponse, LoginRequest, LoginResponse, MeResponse, RegisterResponse,
- SendVerificationResponse, StatusResponse, SyncHistoryResponse, VerificationTokenRequest,
- VerificationTokenResponse,
+ ErrorResponse, LoginRequest, LoginResponse, MeResponse, RegisterResponse, StatusResponse,
+ SyncHistoryResponse,
},
record::RecordStatus,
};
@@ -427,35 +426,4 @@ impl<'a> Client<'a> {
bail!("Unknown error");
}
}
-
- // Either request a verification email if token is null, or validate a token
- pub async fn verify(&self, token: Option<String>) -> Result<(bool, bool)> {
- // could dedupe this a bit, but it's simple at the moment
- let (email_sent, verified) = if let Some(token) = token {
- let url = make_url(self.sync_addr, "/api/v0/account/verify")?;
- let url = Url::parse(url.as_str())?;
-
- let resp = self
- .client
- .post(url)
- .json(&VerificationTokenRequest { token })
- .send()
- .await?;
- let resp = handle_resp_error(resp).await?;
- let resp = resp.json::<VerificationTokenResponse>().await?;
-
- (false, resp.verified)
- } else {
- let url = make_url(self.sync_addr, "/api/v0/account/send-verification")?;
- let url = Url::parse(url.as_str())?;
-
- let resp = self.client.post(url).send().await?;
- let resp = handle_resp_error(resp).await?;
- let resp = resp.json::<SendVerificationResponse>().await?;
-
- (resp.email_sent, resp.verified)
- };
-
- Ok((email_sent, verified))
- }
}
diff --git a/crates/atuin-common/src/api.rs b/crates/atuin-common/src/api.rs
index 973bdc8e..5887424f 100644
--- a/crates/atuin-common/src/api.rs
+++ b/crates/atuin-common/src/api.rs
@@ -32,22 +32,6 @@ pub struct RegisterResponse {
pub struct DeleteUserResponse {}
#[derive(Debug, Serialize, Deserialize)]
-pub struct SendVerificationResponse {
- pub email_sent: bool,
- pub verified: bool,
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-pub struct VerificationTokenRequest {
- pub token: String,
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-pub struct VerificationTokenResponse {
- pub verified: bool,
-}
-
-#[derive(Debug, Serialize, Deserialize)]
pub struct ChangePasswordRequest {
pub current_password: String,
pub new_password: String,
diff --git a/crates/atuin-server-database/src/lib.rs b/crates/atuin-server-database/src/lib.rs
index a4ddf23c..6000a530 100644
--- a/crates/atuin-server-database/src/lib.rs
+++ b/crates/atuin-server-database/src/lib.rs
@@ -107,10 +107,6 @@ pub trait Database: Sized + Clone + Send + Sync + 'static {
async fn get_user_session(&self, u: &User) -> DbResult<Session>;
async fn add_user(&self, user: &NewUser) -> DbResult<i64>;
- async fn user_verified(&self, id: i64) -> DbResult<bool>;
- async fn verify_user(&self, id: i64) -> DbResult<()>;
- async fn user_verification_token(&self, id: i64) -> DbResult<String>;
-
async fn update_user_password(&self, u: &User) -> DbResult<()>;
async fn count_history(&self, user: &User) -> DbResult<i64>;
diff --git a/crates/atuin-server-database/src/models.rs b/crates/atuin-server-database/src/models.rs
index 894ac7f6..b71a9bc9 100644
--- a/crates/atuin-server-database/src/models.rs
+++ b/crates/atuin-server-database/src/models.rs
@@ -32,7 +32,6 @@ pub struct User {
pub username: String,
pub email: String,
pub password: String,
- pub verified: Option<OffsetDateTime>,
}
pub struct Session {
diff --git a/crates/atuin-server-postgres/migrations/20260127000000_remove-email-verification.sql b/crates/atuin-server-postgres/migrations/20260127000000_remove-email-verification.sql
new file mode 100644
index 00000000..15309920
--- /dev/null
+++ b/crates/atuin-server-postgres/migrations/20260127000000_remove-email-verification.sql
@@ -0,0 +1,2 @@
+drop table if exists user_verification_token;
+alter table users drop column if exists verified_at;
diff --git a/crates/atuin-server-postgres/src/lib.rs b/crates/atuin-server-postgres/src/lib.rs
index 54ba2ee8..ce101d8d 100644
--- a/crates/atuin-server-postgres/src/lib.rs
+++ b/crates/atuin-server-postgres/src/lib.rs
@@ -5,7 +5,6 @@ use rand::Rng;
use async_trait::async_trait;
use atuin_common::record::{EncryptedData, HostId, Record, RecordIdx, RecordStatus};
-use atuin_common::utils::crypto_random_string;
use atuin_server_database::models::{History, NewHistory, NewSession, NewUser, Session, User};
use atuin_server_database::{Database, DbError, DbResult, DbSettings};
use futures_util::TryStreamExt;
@@ -13,7 +12,7 @@ use sqlx::Row;
use sqlx::postgres::PgPoolOptions;
use time::{OffsetDateTime, PrimitiveDateTime, UtcOffset};
-use tracing::{instrument, trace};
+use tracing::instrument;
use uuid::Uuid;
use wrappers::{DbHistory, DbRecord, DbSession, DbUser};
@@ -121,100 +120,18 @@ impl Database for Postgres {
#[instrument(skip_all)]
async fn get_user(&self, username: &str) -> DbResult<User> {
- sqlx::query_as(
- "select id, username, email, password, verified_at from users where username = $1",
- )
- .bind(username)
- .fetch_one(self.read_pool())
- .await
- .map_err(fix_error)
- .map(|DbUser(user)| user)
- }
-
- #[instrument(skip_all)]
- async fn user_verified(&self, id: i64) -> DbResult<bool> {
- let res: (bool,) =
- sqlx::query_as("select verified_at is not null from users where id = $1")
- .bind(id)
- .fetch_one(self.read_pool())
- .await
- .map_err(fix_error)?;
-
- Ok(res.0)
- }
-
- #[instrument(skip_all)]
- async fn verify_user(&self, id: i64) -> DbResult<()> {
- sqlx::query(
- "update users set verified_at = (current_timestamp at time zone 'utc') where id=$1",
- )
- .bind(id)
- .execute(&self.pool)
- .await
- .map_err(fix_error)?;
-
- Ok(())
- }
-
- /// Return a valid verification token for the user
- /// If the user does not have any token, create one, insert it, and return
- /// If the user has a token, but it's invalid, delete it, create a new one, return
- /// If the user already has a valid token, return it
- #[instrument(skip_all)]
- async fn user_verification_token(&self, id: i64) -> DbResult<String> {
- const TOKEN_VALID_MINUTES: i64 = 15;
-
- // First we check if there is a verification token
- let token: Option<(String, sqlx::types::time::OffsetDateTime)> = sqlx::query_as(
- "select token, valid_until from user_verification_token where user_id = $1",
- )
- .bind(id)
- .fetch_optional(&self.pool)
- .await
- .map_err(fix_error)?;
-
- let token = if let Some((token, valid_until)) = token {
- trace!("Token for user {id} valid until {valid_until}");
-
- // We have a token, AND it's still valid
- if valid_until > time::OffsetDateTime::now_utc() {
- token
- } else {
- // token has expired. generate a new one, return it
- let token = crypto_random_string::<24>();
-
- sqlx::query("update user_verification_token set token = $2, valid_until = $3 where user_id=$1")
- .bind(id)
- .bind(&token)
- .bind(time::OffsetDateTime::now_utc() + time::Duration::minutes(TOKEN_VALID_MINUTES))
- .execute(&self.pool)
- .await
- .map_err(fix_error)?;
-
- token
- }
- } else {
- // No token in the database! Generate one, insert it
- let token = crypto_random_string::<24>();
-
- sqlx::query("insert into user_verification_token (user_id, token, valid_until) values ($1, $2, $3)")
- .bind(id)
- .bind(&token)
- .bind(time::OffsetDateTime::now_utc() + time::Duration::minutes(TOKEN_VALID_MINUTES))
- .execute(&self.pool)
- .await
- .map_err(fix_error)?;
-
- token
- };
-
- Ok(token)
+ sqlx::query_as("select id, username, email, password from users where username = $1")
+ .bind(username)
+ .fetch_one(self.read_pool())
+ .await
+ .map_err(fix_error)
+ .map(|DbUser(user)| user)
}
#[instrument(skip_all)]
async fn get_session_user(&self, token: &str) -> DbResult<User> {
sqlx::query_as(
- "select users.id, users.username, users.email, users.password, users.verified_at from users
+ "select users.id, users.username, users.email, users.password from users
inner join sessions
on users.id = sessions.user_id
and sessions.token = $1",
@@ -431,12 +348,6 @@ impl Database for Postgres {
.await
.map_err(fix_error)?;
- sqlx::query("delete from user_verification_token where user_id = $1")
- .bind(u.id)
- .execute(&self.pool)
- .await
- .map_err(fix_error)?;
-
sqlx::query("delete from total_history_count_user where user_id = $1")
.bind(u.id)
.execute(&self.pool)
diff --git a/crates/atuin-server-postgres/src/wrappers.rs b/crates/atuin-server-postgres/src/wrappers.rs
index 0d6a0ee6..cde4134c 100644
--- a/crates/atuin-server-postgres/src/wrappers.rs
+++ b/crates/atuin-server-postgres/src/wrappers.rs
@@ -16,7 +16,6 @@ impl<'a> FromRow<'a, PgRow> for DbUser {
username: row.try_get("username")?,
email: row.try_get("email")?,
password: row.try_get("password")?,
- verified: row.try_get("verified_at")?,
}))
}
}
diff --git a/crates/atuin-server-sqlite/migrations/20260127000000_remove-email-verification.sql b/crates/atuin-server-sqlite/migrations/20260127000000_remove-email-verification.sql
new file mode 100644
index 00000000..15309920
--- /dev/null
+++ b/crates/atuin-server-sqlite/migrations/20260127000000_remove-email-verification.sql
@@ -0,0 +1,2 @@
+drop table if exists user_verification_token;
+alter table users drop column if exists verified_at;
diff --git a/crates/atuin-server-sqlite/src/lib.rs b/crates/atuin-server-sqlite/src/lib.rs
index 83d05ea5..d69258c4 100644
--- a/crates/atuin-server-sqlite/src/lib.rs
+++ b/crates/atuin-server-sqlite/src/lib.rs
@@ -1,10 +1,7 @@
use std::str::FromStr;
use async_trait::async_trait;
-use atuin_common::{
- record::{EncryptedData, HostId, Record, RecordIdx, RecordStatus},
- utils::crypto_random_string,
-};
+use atuin_common::record::{EncryptedData, HostId, Record, RecordIdx, RecordStatus};
use atuin_server_database::{
Database, DbError, DbResult, DbSettings,
models::{History, NewHistory, NewSession, NewUser, Session, User},
@@ -67,9 +64,9 @@ impl Database for Sqlite {
#[instrument(skip_all)]
async fn get_session_user(&self, token: &str) -> DbResult<User> {
sqlx::query_as(
- "select users.id, users.username, users.email, users.password, users.verified_at from users
- inner join sessions
- on users.id = sessions.user_id
+ "select users.id, users.username, users.email, users.password from users
+ inner join sessions
+ on users.id = sessions.user_id
and sessions.token = $1",
)
.bind(token)
@@ -99,14 +96,12 @@ impl Database for Sqlite {
#[instrument(skip_all)]
async fn get_user(&self, username: &str) -> DbResult<User> {
- sqlx::query_as(
- "select id, username, email, password, verified_at from users where username = $1",
- )
- .bind(username)
- .fetch_one(&self.pool)
- .await
- .map_err(fix_error)
- .map(|DbUser(user)| user)
+ sqlx::query_as("select id, username, email, password from users where username = $1")
+ .bind(username)
+ .fetch_one(&self.pool)
+ .await
+ .map_err(fix_error)
+ .map(|DbUser(user)| user)
}
#[instrument(skip_all)]
@@ -142,80 +137,6 @@ impl Database for Sqlite {
}
#[instrument(skip_all)]
- async fn user_verified(&self, id: i64) -> DbResult<bool> {
- let res: (bool,) =
- sqlx::query_as("select verified_at is not null from users where id = $1")
- .bind(id)
- .fetch_one(&self.pool)
- .await
- .map_err(fix_error)?;
-
- Ok(res.0)
- }
-
- #[instrument(skip_all)]
- async fn verify_user(&self, id: i64) -> DbResult<()> {
- sqlx::query(
- "update users set verified_at = (current_timestamp at time zone 'utc') where id=$1",
- )
- .bind(id)
- .execute(&self.pool)
- .await
- .map_err(fix_error)?;
-
- Ok(())
- }
-
- #[instrument(skip_all)]
- async fn user_verification_token(&self, id: i64) -> DbResult<String> {
- const TOKEN_VALID_MINUTES: i64 = 15;
-
- // First we check if there is a verification token
- let token: Option<(String, sqlx::types::time::OffsetDateTime)> = sqlx::query_as(
- "select token, valid_until from user_verification_token where user_id = $1",
- )
- .bind(id)
- .fetch_optional(&self.pool)
- .await
- .map_err(fix_error)?;
-
- let token = if let Some((token, valid_until)) = token {
- // We have a token, AND it's still valid
- if valid_until > time::OffsetDateTime::now_utc() {
- token
- } else {
- // token has expired. generate a new one, return it
- let token = crypto_random_string::<24>();
-
- sqlx::query("update user_verification_token set token = $2, valid_until = $3 where user_id=$1")
- .bind(id)
- .bind(&token)
- .bind(time::OffsetDateTime::now_utc() + time::Duration::minutes(TOKEN_VALID_MINUTES))
- .execute(&self.pool)
- .await
- .map_err(fix_error)?;
-
- token
- }
- } else {
- // No token in the database! Generate one, insert it
- let token = crypto_random_string::<24>();
-
- sqlx::query("insert into user_verification_token (user_id, token, valid_until) values ($1, $2, $3)")
- .bind(id)
- .bind(&token)
- .bind(time::OffsetDateTime::now_utc() + time::Duration::minutes(TOKEN_VALID_MINUTES))
- .execute(&self.pool)
- .await
- .map_err(fix_error)?;
-
- token
- };
-
- Ok(token)
- }
-
- #[instrument(skip_all)]
async fn update_user_password(&self, user: &User) -> DbResult<()> {
sqlx::query(
"update users
diff --git a/crates/atuin-server-sqlite/src/wrappers.rs b/crates/atuin-server-sqlite/src/wrappers.rs
index 3f2262c3..2f1230c2 100644
--- a/crates/atuin-server-sqlite/src/wrappers.rs
+++ b/crates/atuin-server-sqlite/src/wrappers.rs
@@ -15,7 +15,6 @@ impl<'a> FromRow<'a, SqliteRow> for DbUser {
username: row.try_get("username")?,
email: row.try_get("email")?,
password: row.try_get("password")?,
- verified: row.try_get("verified_at")?,
}))
}
}
diff --git a/crates/atuin-server/Cargo.toml b/crates/atuin-server/Cargo.toml
index 915ceb14..ea647f38 100644
--- a/crates/atuin-server/Cargo.toml
+++ b/crates/atuin-server/Cargo.toml
@@ -32,4 +32,3 @@ argon2 = "0.5"
semver = { workspace = true }
metrics-exporter-prometheus = "0.18"
metrics = "0.24"
-postmark = {version= "0.11", features=["reqwest", "reqwest-rustls-tls"]}
diff --git a/crates/atuin-server/src/handlers/user.rs b/crates/atuin-server/src/handlers/user.rs
index 4edd1787..c6fec51e 100644
--- a/crates/atuin-server/src/handlers/user.rs
+++ b/crates/atuin-server/src/handlers/user.rs
@@ -13,8 +13,6 @@ use axum::{
};
use metrics::counter;
-use postmark::{Query, reqwest::PostmarkClient};
-
use rand::rngs::OsRng;
use tracing::{debug, error, info, instrument};
@@ -178,109 +176,6 @@ pub async fn delete<DB: Database>(
Ok(Json(DeleteUserResponse {}))
}
-#[instrument(skip_all, fields(user.id = user.id))]
-pub async fn send_verification<DB: Database>(
- UserAuth(user): UserAuth,
- state: State<AppState<DB>>,
-) -> Result<Json<SendVerificationResponse>, ErrorResponseStatus<'static>> {
- let settings = state.0.settings;
- debug!("request to verify user {}", user.username);
-
- if !settings.mail.enabled {
- return Ok(Json(SendVerificationResponse {
- email_sent: false,
- verified: false,
- }));
- }
-
- if user.verified.is_some() {
- return Ok(Json(SendVerificationResponse {
- email_sent: false,
- verified: true,
- }));
- }
-
- // TODO: if we ever add another mail provider, can match on them all here.
- let postmark_token = match settings.mail.postmark.token {
- Some(token) => token,
- _ => {
- error!("Failed to verify email: got None for postmark token");
- return Err(ErrorResponse::reply("mail not configured")
- .with_status(StatusCode::INTERNAL_SERVER_ERROR));
- }
- };
-
- let db = &state.0.database;
-
- let verification_token = db
- .user_verification_token(user.id)
- .await
- .expect("Failed to verify");
-
- debug!("Generated verification token, emailing user");
-
- let client = PostmarkClient::builder()
- .base_url("https://api.postmarkapp.com/")
- .server_token(postmark_token)
- .build();
-
- let req = postmark::api::email::SendEmailRequest::builder()
- .from(settings.mail.verification.from)
- .subject(settings.mail.verification.subject)
- .to(user.email)
- .body(postmark::api::Body::text(format!(
- "Please run the following command to finalize your Atuin account verification. It is valid for 15 minutes:\n\natuin account verify --token '{verification_token}'"
- )))
- .build();
-
- req.execute(&client)
- .await
- .expect("postmark email request failed");
-
- debug!("Email sent");
-
- Ok(Json(SendVerificationResponse {
- email_sent: true,
- verified: false,
- }))
-}
-
-#[instrument(skip_all, fields(user.id = user.id))]
-pub async fn verify_user<DB: Database>(
- UserAuth(user): UserAuth,
- state: State<AppState<DB>>,
- Json(token_request): Json<VerificationTokenRequest>,
-) -> Result<Json<VerificationTokenResponse>, ErrorResponseStatus<'static>> {
- let db = state.0.database;
-
- if user.verified.is_some() {
- return Ok(Json(VerificationTokenResponse { verified: true }));
- }
-
- let token = db.user_verification_token(user.id).await.map_err(|e| {
- error!("Failed to read user token: {e}");
-
- ErrorResponse::reply("Failed to verify").with_status(StatusCode::INTERNAL_SERVER_ERROR)
- })?;
-
- if token_request.token == token {
- db.verify_user(user.id).await.map_err(|e| {
- error!("Failed to verify user: {e}");
-
- ErrorResponse::reply("Failed to verify").with_status(StatusCode::INTERNAL_SERVER_ERROR)
- })?;
- } else {
- info!(
- "Incorrect verification token {} vs {}",
- token_request.token, token
- );
-
- return Ok(Json(VerificationTokenResponse { verified: false }));
- }
-
- Ok(Json(VerificationTokenResponse { verified: true }))
-}
-
#[instrument(skip_all, fields(user.id = user.id, change_password))]
pub async fn change_password<DB: Database>(
UserAuth(mut user): UserAuth,
diff --git a/crates/atuin-server/src/router.rs b/crates/atuin-server/src/router.rs
index 9d4f7d44..0c41d5e6 100644
--- a/crates/atuin-server/src/router.rs
+++ b/crates/atuin-server/src/router.rs
@@ -134,11 +134,6 @@ pub fn router<DB: Database>(database: DB, settings: Settings) -> Router {
.route("/record", get(handlers::record::index))
.route("/record/next", get(handlers::record::next))
.route("/api/v0/me", get(handlers::v0::me::get))
- .route("/api/v0/account/verify", post(handlers::user::verify_user))
- .route(
- "/api/v0/account/send-verification",
- post(handlers::user::send_verification),
- )
.route("/api/v0/record", post(handlers::v0::record::post))
.route("/api/v0/record", get(handlers::v0::record::index))
.route("/api/v0/record/next", get(handlers::v0::record::next))
diff --git a/crates/atuin-server/src/settings.rs b/crates/atuin-server/src/settings.rs
index 98d1d69f..3a612be9 100644
--- a/crates/atuin-server/src/settings.rs
+++ b/crates/atuin-server/src/settings.rs
@@ -8,33 +8,6 @@ use serde::{Deserialize, Serialize};
static EXAMPLE_CONFIG: &str = include_str!("../server.toml");
-#[derive(Default, Clone, Debug, Deserialize, Serialize)]
-pub struct Mail {
- #[serde(alias = "enable")]
- pub enabled: bool,
-
- /// Configuration for the postmark api client
- /// This is what we use for Atuin Cloud, the forum, etc.
- #[serde(default)]
- pub postmark: Postmark,
-
- #[serde(default)]
- pub verification: MailVerification,
-}
-
-#[derive(Default, Clone, Debug, Deserialize, Serialize)]
-pub struct Postmark {
- #[serde(alias = "token")]
- pub token: Option<String>,
-}
-
-#[derive(Default, Clone, Debug, Deserialize, Serialize)]
-pub struct MailVerification {
- #[serde(alias = "enable")]
- pub from: String,
- pub subject: String,
-}
-
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Metrics {
#[serde(alias = "enabled")]
@@ -65,7 +38,6 @@ pub struct Settings {
pub register_webhook_url: Option<String>,
pub register_webhook_username: String,
pub metrics: Metrics,
- pub mail: Mail,
/// Enable legacy sync v1 routes (history-based sync)
/// Set to false to use only the newer record-based sync
@@ -108,7 +80,6 @@ impl Settings {
.set_default("metrics.enable", false)?
.set_default("metrics.host", "127.0.0.1")?
.set_default("metrics.port", 9001)?
- .set_default("mail.enable", false)?
.set_default("sync_v1_enabled", true)?
.add_source(
Environment::with_prefix("atuin")
diff --git a/crates/atuin/src/command/client/account.rs b/crates/atuin/src/command/client/account.rs
index 011d7c69..e99c9593 100644
--- a/crates/atuin/src/command/client/account.rs
+++ b/crates/atuin/src/command/client/account.rs
@@ -9,7 +9,6 @@ pub mod delete;
pub mod login;
pub mod logout;
pub mod register;
-pub mod verify;
#[derive(Args, Debug)]
pub struct Cmd {
@@ -33,9 +32,6 @@ pub enum Commands {
/// Change your password
ChangePassword(change_password::Cmd),
-
- /// Verify your account
- Verify(verify::Cmd),
}
impl Cmd {
@@ -46,7 +42,6 @@ impl Cmd {
Commands::Logout => logout::run(&settings),
Commands::Delete => delete::run(&settings).await,
Commands::ChangePassword(c) => c.run(&settings).await,
- Commands::Verify(c) => c.run(&settings).await,
}
}
}
diff --git a/crates/atuin/src/command/client/account/verify.rs b/crates/atuin/src/command/client/account/verify.rs
deleted file mode 100644
index 1533c283..00000000
--- a/crates/atuin/src/command/client/account/verify.rs
+++ /dev/null
@@ -1,51 +0,0 @@
-use clap::Parser;
-use eyre::Result;
-
-use atuin_client::{api_client, settings::Settings};
-
-#[derive(Parser, Debug)]
-pub struct Cmd {
- #[clap(long, short)]
- pub token: Option<String>,
-}
-
-impl Cmd {
- pub async fn run(self, settings: &Settings) -> Result<()> {
- run(settings, self.token).await
- }
-}
-
-pub async fn run(settings: &Settings, token: Option<String>) -> Result<()> {
- let client = api_client::Client::new(
- &settings.sync_address,
- settings.session_token()?.as_str(),
- settings.network_connect_timeout,
- settings.network_timeout,
- )?;
-
- let (email_sent, verified) = client.verify(token).await?;
-
- match (email_sent, verified) {
- (true, false) => {
- println!("Verification sent! Please check your inbox");
- }
-
- (false, true) => {
- println!("Your account is verified");
- }
-
- (false, false) => {
- println!(
- "Your Atuin server does not have mail set up. This is not required, though your account cannot be verified. Speak to your admin."
- );
- }
-
- _ => {
- println!(
- "Invalid email and verification status. This is a bug. Please open an issue: https://github.com/atuinsh/atuin"
- );
- }
- }
-
- Ok(())
-}
diff --git a/crates/atuin/tests/common/mod.rs b/crates/atuin/tests/common/mod.rs
index 6cc4e443..fa663ef3 100644
--- a/crates/atuin/tests/common/mod.rs
+++ b/crates/atuin/tests/common/mod.rs
@@ -42,7 +42,6 @@ pub async fn start_server(path: &str) -> (String, oneshot::Sender<()>, JoinHandl
read_db_uri: None,
},
metrics: atuin_server::settings::Metrics::default(),
- mail: atuin_server::settings::Mail::default(),
fake_version: None,
};
diff --git a/docs/docs/faq.md b/docs/docs/faq.md
index d016ef29..c4f51c6f 100644
--- a/docs/docs/faq.md
+++ b/docs/docs/faq.md
@@ -32,11 +32,8 @@ This will delete your account, and all history from the remote server. It will n
## I've forgotten my password! How can I reset it?
-We don't (yet) have a password reset system, as we don't verify emails. This
-may change soon, but in the meantime so long as you're still logged in on at
-least one account, it's safe to delete and re-create the account.
-
-We're aware this isn't optimal.
+We don't currently have a password reset system. So long as you're still logged
+in on at least one machine, it's safe to delete and re-create your account.
## I did not set up sync, and now I have to reinstall my system!