postgres via sqlx - workable?
This commit is contained in:
163
src/users/mod.rs
163
src/users/mod.rs
@@ -3,8 +3,8 @@ use axum::{
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
use chrono::{DateTime, NaiveDate};
|
||||
use rusqlite::{Connection, OptionalExtension, ffi::SQLITE_CONSTRAINT_UNIQUE};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::{PgConnection, Row};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
@@ -45,65 +45,87 @@ pub enum UserError {
|
||||
}
|
||||
|
||||
impl User {
|
||||
pub fn total_count(conn: &Connection) -> Result<i64, UserError> {
|
||||
Ok(conn.query_row("SELECT COUNT(*) FROM users", (), |r| r.get(0))?)
|
||||
pub async fn total_count(conn: &mut PgConnection) -> Result<i64, UserError> {
|
||||
Ok(sqlx::query_scalar("SELECT COUNT(*) FROM users")
|
||||
.fetch_one(conn)
|
||||
.await?)
|
||||
}
|
||||
pub fn get_by_id(conn: &Connection, id: Uuid) -> Result<User, UserError> {
|
||||
let res = conn
|
||||
.prepare("SELECT handle FROM users WHERE id = ?1")?
|
||||
.query_one((&id,), |r| {
|
||||
|
||||
pub async fn get_by_id(conn: &mut PgConnection, id: Uuid) -> Result<User, UserError> {
|
||||
let res = sqlx::query("SELECT handle FROM users WHERE id = $1")
|
||||
.bind(id)
|
||||
.fetch_optional(conn)
|
||||
.await?;
|
||||
|
||||
match res {
|
||||
Some(r) => {
|
||||
let handle_str: String = r.try_get("handle")?;
|
||||
Ok(User {
|
||||
id,
|
||||
handle: r.get(0)?,
|
||||
handle: UserHandle::new(&handle_str)?,
|
||||
})
|
||||
})
|
||||
.optional()?;
|
||||
match res {
|
||||
Some(u) => Ok(u),
|
||||
}
|
||||
None => Err(UserError::NoUserWithId(id)),
|
||||
}
|
||||
}
|
||||
pub fn get_by_handle(conn: &Connection, handle: UserHandle) -> Result<User, UserError> {
|
||||
let res = conn
|
||||
.prepare("SELECT id, handle FROM users WHERE handle = ?1")?
|
||||
.query_one((&handle,), |r| {
|
||||
Ok(User {
|
||||
id: r.get(0)?,
|
||||
handle: r.get(1)?,
|
||||
})
|
||||
})
|
||||
.optional()?;
|
||||
|
||||
pub async fn get_by_handle(
|
||||
conn: &mut PgConnection,
|
||||
handle: UserHandle,
|
||||
) -> Result<User, UserError> {
|
||||
let res = sqlx::query("SELECT id FROM users WHERE handle = $1")
|
||||
.bind(handle.as_str())
|
||||
.fetch_optional(conn)
|
||||
.await?;
|
||||
|
||||
match res {
|
||||
Some(u) => Ok(u),
|
||||
Some(r) => Ok(User {
|
||||
id: r.try_get("id")?,
|
||||
handle,
|
||||
}),
|
||||
None => Err(UserError::NoUserWithHandle(handle)),
|
||||
}
|
||||
}
|
||||
pub fn get_all(conn: &Connection) -> Result<Vec<User>, UserError> {
|
||||
Ok(conn
|
||||
.prepare("SELECT id, handle FROM users")?
|
||||
.query_map((), |r| {
|
||||
Ok(User {
|
||||
id: r.get(0)?,
|
||||
handle: r.get(1)?,
|
||||
})
|
||||
})?
|
||||
.collect::<Result<Vec<User>, _>>()?)
|
||||
|
||||
pub async fn get_all(conn: &mut PgConnection) -> Result<Vec<User>, UserError> {
|
||||
let rows = sqlx::query("SELECT id, handle FROM users")
|
||||
.fetch_all(conn)
|
||||
.await?;
|
||||
|
||||
let mut users = Vec::with_capacity(rows.len());
|
||||
for r in rows {
|
||||
let handle_str: String = r.try_get("handle")?;
|
||||
users.push(User {
|
||||
id: r.try_get("id")?,
|
||||
handle: UserHandle::new(&handle_str)?,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(users)
|
||||
}
|
||||
|
||||
pub fn create(conn: &Connection, handle: UserHandle) -> Result<User, UserError> {
|
||||
pub async fn create(conn: &mut PgConnection, handle: UserHandle) -> Result<User, UserError> {
|
||||
let id = Uuid::now_v7();
|
||||
conn.prepare("INSERT INTO users(id, handle) VALUES (?1, ?2)")?
|
||||
.execute((&id, &handle))?;
|
||||
sqlx::query("INSERT INTO users(id, handle) VALUES ($1, $2)")
|
||||
.bind(id)
|
||||
.bind(handle.as_str())
|
||||
.execute(conn)
|
||||
.await?;
|
||||
|
||||
Ok(User { id, handle })
|
||||
}
|
||||
|
||||
pub fn set_handle(
|
||||
pub async fn set_handle(
|
||||
&mut self,
|
||||
conn: &Connection,
|
||||
conn: &mut PgConnection,
|
||||
new_handle: UserHandle,
|
||||
) -> Result<(), UserError> {
|
||||
conn.prepare("UPDATE users SET handle = ?1 WHERE id = ?2")?
|
||||
.execute((&new_handle, self.id))?;
|
||||
sqlx::query("UPDATE users SET handle = $1 WHERE id = $2")
|
||||
.bind(new_handle.as_str())
|
||||
.bind(self.id)
|
||||
.execute(conn)
|
||||
.await?;
|
||||
|
||||
self.handle = new_handle;
|
||||
Ok(())
|
||||
}
|
||||
@@ -118,21 +140,26 @@ impl User {
|
||||
|
||||
// DANGEROUS: AUTH
|
||||
impl User {
|
||||
pub fn set_password(
|
||||
pub async fn set_password(
|
||||
&mut self,
|
||||
conn: &Connection,
|
||||
conn: &mut PgConnection,
|
||||
passw: Option<&str>,
|
||||
) -> Result<(), UserError> {
|
||||
match passw {
|
||||
None => {
|
||||
conn.prepare("UPDATE users SET password = NULL WHERE id = ?1")?
|
||||
.execute((self.id,))?;
|
||||
sqlx::query("UPDATE users SET password = NULL WHERE id = $1")
|
||||
.bind(self.id)
|
||||
.execute(conn)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
Some(passw) => {
|
||||
let hashed = User::hash_password(passw)?;
|
||||
conn.prepare("UPDATE users SET password = ?1 WHERE id = ?2")?
|
||||
.execute((hashed, self.id))?;
|
||||
sqlx::query("UPDATE users SET password = $1 WHERE id = $2")
|
||||
.bind(hashed)
|
||||
.bind(self.id)
|
||||
.execute(conn)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -148,14 +175,18 @@ impl User {
|
||||
/// to do everything and probably should not be used as a regular account
|
||||
/// due to the ramifications of compromise. But it could be used for that,
|
||||
/// and have its name changed.
|
||||
pub fn create_infradmin(conn: &Connection) -> Result<User, UserError> {
|
||||
pub async fn create_infradmin(conn: &mut PgConnection) -> Result<User, UserError> {
|
||||
let mut u = User {
|
||||
id: Uuid::max(),
|
||||
handle: UserHandle::new("Infradmin")?,
|
||||
};
|
||||
conn.prepare("INSERT INTO users(id, handle) VALUES (?1, ?2)")?
|
||||
.execute((&u.id, &u.handle))?;
|
||||
u.regenerate_infradmin_password(conn)?;
|
||||
sqlx::query("INSERT INTO users(id, handle) VALUES ($1, $2)")
|
||||
.bind(u.id)
|
||||
.bind(u.handle.as_str())
|
||||
.execute(&mut *conn)
|
||||
.await?;
|
||||
|
||||
u.regenerate_infradmin_password(conn).await?;
|
||||
|
||||
Ok(u)
|
||||
}
|
||||
@@ -178,9 +209,12 @@ impl User {
|
||||
/// to do everything and probably should not be used as a regular account
|
||||
/// due to the ramifications of compromise. But it could be used for that,
|
||||
/// and have its name changed.
|
||||
pub fn regenerate_infradmin_password(&mut self, conn: &Connection) -> Result<(), UserError> {
|
||||
pub async fn regenerate_infradmin_password(
|
||||
&mut self,
|
||||
conn: &mut PgConnection,
|
||||
) -> Result<(), UserError> {
|
||||
let passw = auth::generate_token(auth::TokenSize::Char16);
|
||||
self.set_password(conn, Some(&passw))?;
|
||||
self.set_password(conn, Some(&passw)).await?;
|
||||
log::info!("[USERS] The infradmin account password has been (re)generated.");
|
||||
log::info!("[USERS] Handle: {}", self.handle.as_str());
|
||||
log::info!("[USERS] Password: {}", passw);
|
||||
@@ -194,13 +228,16 @@ impl User {
|
||||
/// for actions performed by Mnemosyne internally.
|
||||
/// It shall not be available for log-in.
|
||||
/// It should not have its name changed, and should be protected from that.
|
||||
pub fn create_systemuser(conn: &Connection) -> Result<User, UserError> {
|
||||
pub async fn create_systemuser(conn: &mut PgConnection) -> Result<User, UserError> {
|
||||
let u = User {
|
||||
id: Uuid::nil(),
|
||||
handle: UserHandle::new("Mnemosyne")?,
|
||||
};
|
||||
conn.prepare("INSERT INTO users(id, handle) VALUES (?1, ?2)")?
|
||||
.execute((&u.id, &u.handle))?;
|
||||
sqlx::query("INSERT INTO users(id, handle) VALUES ($1, $2)")
|
||||
.bind(u.id)
|
||||
.bind(u.handle.as_str())
|
||||
.execute(conn)
|
||||
.await?;
|
||||
|
||||
Ok(u)
|
||||
}
|
||||
@@ -216,22 +253,24 @@ impl User {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<rusqlite::Error> for UserError {
|
||||
fn from(error: rusqlite::Error) -> Self {
|
||||
if let rusqlite::Error::SqliteFailure(err, Some(msg)) = &error
|
||||
&& err.extended_code == SQLITE_CONSTRAINT_UNIQUE
|
||||
&& msg.contains("handle")
|
||||
{
|
||||
return UserError::HandleAlreadyExists;
|
||||
impl From<sqlx::Error> for UserError {
|
||||
fn from(error: sqlx::Error) -> Self {
|
||||
if let sqlx::Error::Database(err) = &error {
|
||||
// Check for Postgres unique constraint violation (code 23505)
|
||||
if err.is_unique_violation() && err.message().contains("handle") {
|
||||
return UserError::HandleAlreadyExists;
|
||||
}
|
||||
}
|
||||
UserError::DatabaseError(DatabaseError::from(error))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<argon2::password_hash::Error> for UserError {
|
||||
fn from(err: argon2::password_hash::Error) -> Self {
|
||||
UserError::PassHashError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for UserError {
|
||||
fn into_response(self) -> Response {
|
||||
match self {
|
||||
|
||||
Reference in New Issue
Block a user