refactor(db): moved from mariadb to postgresql

This commit is contained in:
antifallobst 2023-08-17 21:09:30 +02:00
parent 9c3a3b8e26
commit 51c8a7f7fa
Signed by: antifallobst
GPG Key ID: 2B4F402172791BAF
7 changed files with 67 additions and 68 deletions

2
.env
View File

@ -1 +1 @@
DATABASE_URL="mysql://nerdcult_api:test@localhost/NC_Accounts" DATABASE_URL="postgres://postgres:postgres@localhost/test-db"

View File

@ -17,7 +17,7 @@ env_logger = "0.10"
log = "0.4" log = "0.4"
clap = { version = "4.3.21", features = ["derive"] } clap = { version = "4.3.21", features = ["derive"] }
actix-web-httpauth = "0.8.0" actix-web-httpauth = "0.8.0"
sqlx = { version = "0.7.1", features = ["runtime-tokio", "mysql", "chrono"] } sqlx = { version = "0.7.1", features = ["runtime-tokio", "postgres", "chrono"] }
uuid = { version = "1.4.1", features = ["v4"] } uuid = { version = "1.4.1", features = ["v4"] }
chrono = "0.4" chrono = "0.4"
mail-send = "0.4.0" mail-send = "0.4.0"

View File

@ -3,11 +3,11 @@ use pbkdf2::{
password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString}, password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
Pbkdf2, Pbkdf2,
}; };
use sqlx::{mysql::MySqlPool, types::chrono as sqlx_chrono}; use sqlx::{postgres::PgPool, types::chrono as sqlx_chrono};
#[derive(Debug)] #[derive(Debug)]
pub struct Account { pub struct Account {
pub id: u64, pub id: i64,
pub username: String, pub username: String,
pub email: String, pub email: String,
pub salt: String, pub salt: String,
@ -19,7 +19,7 @@ pub struct Account {
impl Account { impl Account {
/// This doesn't check if an account with that name is already existing! /// This doesn't check if an account with that name is already existing!
pub async fn new( pub async fn new(
pool: &MySqlPool, pool: &PgPool,
username: &String, username: &String,
email: &String, email: &String,
password: &String, password: &String,
@ -33,7 +33,7 @@ impl Account {
let joined = sqlx_chrono::Utc::now().naive_utc(); let joined = sqlx_chrono::Utc::now().naive_utc();
sqlx::query!( sqlx::query!(
r#"INSERT INTO Accounts (username, email, salt, password, joined, verified) VALUES (?, ?, ?, ?, ?, false);"#, r#"INSERT INTO Accounts (username, email, salt, password, joined, verified) VALUES ($1, $2, $3, $4, $5, false);"#,
username, username,
email, email,
salt.to_string(), salt.to_string(),
@ -51,10 +51,10 @@ impl Account {
} }
} }
pub async fn from_username(pool: &MySqlPool, username: &String) -> Result<Option<Self>> { pub async fn from_username(pool: &PgPool, username: &String) -> Result<Option<Self>> {
match sqlx::query_as!( match sqlx::query_as!(
Self, Self,
r#"SELECT id, username, email, salt, password, joined, verified as `verified: bool` FROM Accounts WHERE username = ?;"#, r#"SELECT id, username, email, salt, password, joined, verified as "verified!: bool" FROM Accounts WHERE username = $1;"#,
username username
) )
.fetch_one(pool) .fetch_one(pool)
@ -66,10 +66,10 @@ impl Account {
} }
} }
pub async fn from_id(pool: &MySqlPool, id: u64) -> Result<Option<Self>> { pub async fn from_id(pool: &PgPool, id: i64) -> Result<Option<Self>> {
match sqlx::query_as!( match sqlx::query_as!(
Self, Self,
r#"SELECT id, username, email, salt, password, joined, verified as `verified: bool` FROM Accounts WHERE id = ?;"#, r#"SELECT id, username, email, salt, password, joined, verified as "verified!: bool" FROM Accounts WHERE id = $1;"#,
id id
) )
.fetch_one(pool) .fetch_one(pool)
@ -81,10 +81,10 @@ impl Account {
} }
} }
pub async fn from_email(pool: &MySqlPool, email: &String) -> Result<Option<Self>> { pub async fn from_email(pool: &PgPool, email: &String) -> Result<Option<Self>> {
match sqlx::query_as!( match sqlx::query_as!(
Self, Self,
r#"SELECT id, username, email, salt, password, joined, verified as `verified: bool` FROM Accounts WHERE email = ?;"#, r#"SELECT id, username, email, salt, password, joined, verified as "verified!: bool" FROM Accounts WHERE email = $1;"#,
email email
) )
.fetch_one(pool) .fetch_one(pool)

View File

@ -6,7 +6,7 @@ use crate::{
use anyhow::Result; use anyhow::Result;
use log::info; use log::info;
use mail_send::{mail_builder::MessageBuilder, SmtpClientBuilder}; use mail_send::{mail_builder::MessageBuilder, SmtpClientBuilder};
use sqlx::MySqlPool; use sqlx::PgPool;
fn is_sql_injection(string: &String) -> bool { fn is_sql_injection(string: &String) -> bool {
match libinjection::sqli(string) { match libinjection::sqli(string) {
@ -26,7 +26,7 @@ impl AlphaExt for String {
} }
pub async fn register( pub async fn register(
pool: &MySqlPool, pool: &PgPool,
request: data::RegisterRequest, request: data::RegisterRequest,
) -> Result<data::RegisterResponse> { ) -> Result<data::RegisterResponse> {
if is_sql_injection(&request.username) || is_sql_injection(&request.email) { if is_sql_injection(&request.username) || is_sql_injection(&request.email) {
@ -84,10 +84,7 @@ pub async fn register(
Ok(data::RegisterResponse::Success) Ok(data::RegisterResponse::Success)
} }
pub async fn verify( pub async fn verify(pool: &PgPool, request: data::VerifyRequest) -> Result<data::VerifyResponse> {
pool: &MySqlPool,
request: data::VerifyRequest,
) -> Result<data::VerifyResponse> {
if !request.token.is_alpha() { if !request.token.is_alpha() {
return Ok(data::VerifyResponse::Blocked); return Ok(data::VerifyResponse::Blocked);
} }
@ -103,7 +100,7 @@ pub async fn verify(
} }
pub async fn authenticate( pub async fn authenticate(
pool: &MySqlPool, pool: &PgPool,
request: data::AuthenticateRequest, request: data::AuthenticateRequest,
) -> Result<data::AuthenticateResponse> { ) -> Result<data::AuthenticateResponse> {
if is_sql_injection(&request.username) { if is_sql_injection(&request.username) {
@ -130,7 +127,7 @@ pub async fn authenticate(
)) ))
} }
pub async fn delete(pool: &MySqlPool, auth: String) -> Result<data::DeleteResponse> { pub async fn delete(pool: &PgPool, auth: String) -> Result<data::DeleteResponse> {
if !auth.is_alpha() { if !auth.is_alpha() {
return Ok(data::DeleteResponse::Blocked); return Ok(data::DeleteResponse::Blocked);
} }
@ -141,13 +138,13 @@ pub async fn delete(pool: &MySqlPool, auth: String) -> Result<data::DeleteRespon
}; };
sqlx::query!( sqlx::query!(
r#"DELETE FROM AuthTokens WHERE account = ?;"#, r#"DELETE FROM AuthTokens WHERE account = $1;"#,
token.account token.account
) )
.execute(pool) .execute(pool)
.await?; .await?;
sqlx::query!(r#"DELETE FROM Accounts WHERE id = ?;"#, token.account) sqlx::query!(r#"DELETE FROM Accounts WHERE id = $1;"#, token.account)
.execute(pool) .execute(pool)
.await?; .await?;
@ -155,7 +152,7 @@ pub async fn delete(pool: &MySqlPool, auth: String) -> Result<data::DeleteRespon
} }
pub async fn token_delete( pub async fn token_delete(
pool: &MySqlPool, pool: &PgPool,
auth: String, auth: String,
request: data::TokenDeleteRequest, request: data::TokenDeleteRequest,
) -> Result<data::TokenDeleteResponse> { ) -> Result<data::TokenDeleteResponse> {
@ -184,7 +181,7 @@ pub async fn token_delete(
Ok(data::TokenDeleteResponse::Success) Ok(data::TokenDeleteResponse::Success)
} }
pub async fn token_list(pool: &MySqlPool, auth: String) -> Result<data::TokenListResponse> { pub async fn token_list(pool: &PgPool, auth: String) -> Result<data::TokenListResponse> {
if !auth.is_alpha() { if !auth.is_alpha() {
return Ok(data::TokenListResponse::Blocked); return Ok(data::TokenListResponse::Blocked);
} }
@ -196,7 +193,7 @@ pub async fn token_list(pool: &MySqlPool, auth: String) -> Result<data::TokenLis
let list: Vec<(String, i64)> = sqlx::query_as!( let list: Vec<(String, i64)> = sqlx::query_as!(
AuthToken, AuthToken,
r#"SELECT * FROM AuthTokens WHERE account = ?;"#, r#"SELECT * FROM AuthTokens WHERE account = $1;"#,
token.account token.account
) )
.fetch_all(pool) .fetch_all(pool)
@ -205,5 +202,7 @@ pub async fn token_list(pool: &MySqlPool, auth: String) -> Result<data::TokenLis
.map(|t| (t.token.clone(), t.expire.timestamp())) .map(|t| (t.token.clone(), t.expire.timestamp()))
.collect(); .collect();
Ok(data::TokenListResponse::Success(data::TokenListSuccess {tokens: list})) Ok(data::TokenListResponse::Success(data::TokenListSuccess {
tokens: list,
}))
} }

View File

@ -3,25 +3,25 @@ mod account;
use actix_web::{web, App, HttpServer}; use actix_web::{web, App, HttpServer};
use anyhow::Result; use anyhow::Result;
use log::info; use log::info;
use sqlx::mysql::MySqlPool; use sqlx::postgres::PgPool;
struct ApiState { struct ApiState {
pool: MySqlPool, pool: PgPool,
} }
pub async fn start(port: u16, pool: MySqlPool) -> Result<()> { pub async fn start(port: u16, pool: PgPool) -> Result<()> {
info!("HTTP server starting on port {} ...", port); info!("HTTP server starting on port {} ...", port);
sqlx::query!( sqlx::query!(
r#" r#"
CREATE TABLE IF NOT EXISTS Accounts ( CREATE TABLE IF NOT EXISTS Accounts (
id INT8 UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, id SERIAL8 NOT NULL,
username VARCHAR(32) NOT NULL, username VARCHAR(32) NOT NULL,
email TEXT NOT NULL, email TEXT NOT NULL,
salt VARCHAR(22) NOT NULL, salt VARCHAR(22) NOT NULL,
password VARCHAR(96) NOT NULL, password VARCHAR(96) NOT NULL,
joined DATETIME NOT NULL, joined TIMESTAMP NOT NULL,
verified BOOLEAN NOT NULL, verified BOOLEAN NOT NULL,
PRIMARY KEY(id) PRIMARY KEY(id)
); );
"# "#
@ -32,10 +32,10 @@ pub async fn start(port: u16, pool: MySqlPool) -> Result<()> {
sqlx::query!( sqlx::query!(
r#" r#"
CREATE TABLE IF NOT EXISTS AuthTokens ( CREATE TABLE IF NOT EXISTS AuthTokens (
token VARCHAR(32) NOT NULL, token VARCHAR(32) NOT NULL,
account INT8 UNSIGNED NOT NULL, account SERIAL8 NOT NULL,
expire DATETIME NOT NULL expire TIMESTAMP NOT NULL
); );
"# "#
) )
.execute(&pool) .execute(&pool)
@ -44,10 +44,10 @@ pub async fn start(port: u16, pool: MySqlPool) -> Result<()> {
sqlx::query!( sqlx::query!(
r#" r#"
CREATE TABLE IF NOT EXISTS VerificationTokens ( CREATE TABLE IF NOT EXISTS VerificationTokens (
token VARCHAR(32) NOT NULL, token VARCHAR(32) NOT NULL,
account INT8 UNSIGNED NOT NULL, account SERIAL8 NOT NULL,
expire DATETIME NOT NULL expire TIMESTAMP NOT NULL
); );
"# "#
) )
.execute(&pool) .execute(&pool)

View File

@ -5,7 +5,7 @@ mod tokens;
use anyhow::Result; use anyhow::Result;
use clap::Parser; use clap::Parser;
use log::info; use log::info;
use sqlx::mysql::MySqlPool; use sqlx::postgres::PgPool;
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)] #[command(author, version, about, long_about = None)]
@ -28,7 +28,7 @@ async fn main() -> Result<()> {
port = p; port = p;
} }
let pool = MySqlPool::connect(&std::env::var("DATABASE_URL").map_err(|_| { let pool = PgPool::connect(&std::env::var("DATABASE_URL").map_err(|_| {
anyhow::Error::msg("Environment variable DATABASE_URL needs to be specified!") anyhow::Error::msg("Environment variable DATABASE_URL needs to be specified!")
})?) })?)
.await?; .await?;

View File

@ -1,17 +1,17 @@
use anyhow::{Error, Result}; use anyhow::{Error, Result};
use sqlx::{mysql::MySqlPool, types::chrono as sqlx_chrono}; use sqlx::{postgres::PgPool, types::chrono as sqlx_chrono};
#[derive(Debug)] #[derive(Debug)]
pub struct AuthToken { pub struct AuthToken {
pub token: String, pub token: String,
pub account: u64, pub account: i64,
pub expire: sqlx_chrono::NaiveDateTime, pub expire: sqlx_chrono::NaiveDateTime,
} }
impl AuthToken { impl AuthToken {
pub async fn new( pub async fn new(
pool: &MySqlPool, pool: &PgPool,
account_id: u64, account_id: i64,
lifetime: chrono::Duration, lifetime: chrono::Duration,
) -> Result<Self> { ) -> Result<Self> {
let expire = match sqlx_chrono::Utc::now() let expire = match sqlx_chrono::Utc::now()
@ -28,7 +28,7 @@ impl AuthToken {
let mut token = Self { let mut token = Self {
token: uuid::Uuid::new_v4().simple().to_string(), token: uuid::Uuid::new_v4().simple().to_string(),
account: account_id as u64, account: account_id,
expire, expire,
}; };
@ -38,7 +38,7 @@ impl AuthToken {
sqlx::query!( sqlx::query!(
r#" r#"
INSERT INTO AuthTokens (token, account, expire) VALUES (?, ?, ?); INSERT INTO AuthTokens (token, account, expire) VALUES ($1, $2, $3);
"#, "#,
token.token, token.token,
token.account, token.account,
@ -50,10 +50,10 @@ impl AuthToken {
Ok(token) Ok(token)
} }
pub async fn check(pool: &MySqlPool, alphanumeric_token: &String) -> Result<Option<Self>> { pub async fn check(pool: &PgPool, alphanumeric_token: &String) -> Result<Option<Self>> {
let query_result = sqlx::query_as!( let query_result = sqlx::query_as!(
Self, Self,
r#"SELECT * FROM AuthTokens WHERE token = ?;"#, r#"SELECT * FROM AuthTokens WHERE token = $1;"#,
alphanumeric_token alphanumeric_token
) )
.fetch_one(pool) .fetch_one(pool)
@ -73,8 +73,8 @@ impl AuthToken {
} }
} }
pub async fn delete(&self, pool: &MySqlPool) -> Result<()> { pub async fn delete(&self, pool: &PgPool) -> Result<()> {
sqlx::query!(r#"DELETE FROM AuthTokens WHERE token = ?;"#, self.token) sqlx::query!(r#"DELETE FROM AuthTokens WHERE token = $1;"#, self.token)
.execute(pool) .execute(pool)
.await?; .await?;
Ok(()) Ok(())
@ -84,14 +84,14 @@ impl AuthToken {
#[derive(Debug)] #[derive(Debug)]
pub struct VerificationToken { pub struct VerificationToken {
pub token: String, pub token: String,
pub account: u64, pub account: i64,
pub expire: sqlx_chrono::NaiveDateTime, pub expire: sqlx_chrono::NaiveDateTime,
} }
impl VerificationToken { impl VerificationToken {
pub async fn new( pub async fn new(
pool: &MySqlPool, pool: &PgPool,
account_id: u64, account_id: i64,
lifetime: chrono::Duration, lifetime: chrono::Duration,
) -> Result<Self> { ) -> Result<Self> {
let expire = match sqlx_chrono::Utc::now() let expire = match sqlx_chrono::Utc::now()
@ -108,7 +108,7 @@ impl VerificationToken {
let mut token = Self { let mut token = Self {
token: uuid::Uuid::new_v4().simple().to_string(), token: uuid::Uuid::new_v4().simple().to_string(),
account: account_id as u64, account: account_id,
expire, expire,
}; };
@ -121,8 +121,8 @@ impl VerificationToken {
sqlx::query!( sqlx::query!(
r#" r#"
INSERT INTO VerificationTokens (token, account, expire) VALUES (?, ?, ?); INSERT INTO VerificationTokens (token, account, expire) VALUES ($1, $2, $3);
"#, "#,
token.token, token.token,
token.account, token.account,
token.expire, token.expire,
@ -133,10 +133,10 @@ impl VerificationToken {
Ok(token) Ok(token)
} }
pub async fn check(pool: &MySqlPool, alphanumeric_token: &String) -> Result<Option<Self>> { pub async fn check(pool: &PgPool, alphanumeric_token: &String) -> Result<Option<Self>> {
let query_result = sqlx::query_as!( let query_result = sqlx::query_as!(
Self, Self,
r#"SELECT * FROM VerificationTokens WHERE token = ?;"#, r#"SELECT * FROM VerificationTokens WHERE token = $1;"#,
alphanumeric_token alphanumeric_token
) )
.fetch_one(pool) .fetch_one(pool)
@ -156,9 +156,9 @@ impl VerificationToken {
} }
} }
pub async fn delete(&self, pool: &MySqlPool) -> Result<()> { pub async fn delete(&self, pool: &PgPool) -> Result<()> {
sqlx::query!( sqlx::query!(
r#"DELETE FROM VerificationTokens WHERE token = ?;"#, r#"DELETE FROM VerificationTokens WHERE token = $1;"#,
self.token self.token
) )
.execute(pool) .execute(pool)
@ -166,11 +166,11 @@ impl VerificationToken {
Ok(()) Ok(())
} }
pub async fn apply(&self, pool: &MySqlPool) -> Result<()> { pub async fn apply(&self, pool: &PgPool) -> Result<()> {
self.delete(pool).await?; self.delete(pool).await?;
sqlx::query!( sqlx::query!(
r#"UPDATE Accounts SET verified=true WHERE id = ?;"#, r#"UPDATE Accounts SET verified=true WHERE id = $1;"#,
self.account self.account
) )
.execute(pool) .execute(pool)