refactor(api): hashing and database changes
1. changed password hashing algorithm from pbkdf2-sha256 to argon2id. 2. storing emails as base64 encoded sha256 hash instead of plain text
This commit is contained in:
parent
4524f601ab
commit
452d2d2015
|
@ -3,3 +3,5 @@
|
|||
|
||||
# Rust stuff
|
||||
target
|
||||
|
||||
start.sh
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT\n id,\n username,\n email,\n salt,\n password,\n joined,\n verified as \"verified!: bool\",\n follows,\n followers,\n permissions\n FROM Accounts WHERE id = $1;",
|
||||
"query": "SELECT\n id,\n username,\n email,\n salt,\n password,\n joined,\n verified as \"verified!: bool\",\n follows,\n followers,\n flags\n FROM Accounts WHERE id = $1;",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
|
@ -16,7 +16,7 @@
|
|||
{
|
||||
"ordinal": 2,
|
||||
"name": "email",
|
||||
"type_info": "Text"
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
|
@ -50,8 +50,8 @@
|
|||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "permissions",
|
||||
"type_info": "Int8"
|
||||
"name": "flags",
|
||||
"type_info": "Int2"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
|
@ -72,5 +72,5 @@
|
|||
false
|
||||
]
|
||||
},
|
||||
"hash": "7009cdfba0b6c8d7c8ed9d49bf91905d159c3bbfe0c8e78876a8cd2b952e28b5"
|
||||
"hash": "0d30b5067f0a9c1c9929882a7c8b7cb5511d4808b6ee0ffd3201ed3772f0b191"
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT\n id,\n username,\n email,\n salt,\n password,\n joined,\n verified as \"verified!: bool\",\n follows,\n followers,\n permissions\n FROM Accounts WHERE email = $1;",
|
||||
"query": "SELECT\n id,\n username,\n email,\n salt,\n password,\n joined,\n verified as \"verified!: bool\",\n follows,\n followers,\n flags\n FROM Accounts WHERE email = $1;",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
|
@ -16,7 +16,7 @@
|
|||
{
|
||||
"ordinal": 2,
|
||||
"name": "email",
|
||||
"type_info": "Text"
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
|
@ -50,8 +50,8 @@
|
|||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "permissions",
|
||||
"type_info": "Int8"
|
||||
"name": "flags",
|
||||
"type_info": "Int2"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
|
@ -72,5 +72,5 @@
|
|||
false
|
||||
]
|
||||
},
|
||||
"hash": "71b834dfe4384a536257b888e293b046a70aefd4ac2dcbf3268e34c9576e903e"
|
||||
"hash": "128da462eb065680f50c9c3fef93d49974ba1e0697d5a996736e3715e335e37d"
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT\n id,\n username,\n email,\n salt,\n password,\n joined,\n verified as \"verified!: bool\",\n follows,\n followers,\n permissions\n FROM Accounts WHERE username = $1;",
|
||||
"query": "SELECT\n id,\n username,\n email,\n salt,\n password,\n joined,\n verified as \"verified!: bool\",\n follows,\n followers,\n flags\n FROM Accounts WHERE username = $1;",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
|
@ -16,7 +16,7 @@
|
|||
{
|
||||
"ordinal": 2,
|
||||
"name": "email",
|
||||
"type_info": "Text"
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
|
@ -50,8 +50,8 @@
|
|||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "permissions",
|
||||
"type_info": "Int8"
|
||||
"name": "flags",
|
||||
"type_info": "Int2"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
|
@ -72,5 +72,5 @@
|
|||
false
|
||||
]
|
||||
},
|
||||
"hash": "0e894c84befe397687d4820ea313aa937cf76286e98f7e66c342fb87a3896265"
|
||||
"hash": "294103135f5b8550f1de72a04ee9ff7569e89b2e72c2e9628c4b8a493468f99d"
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO Accounts (username, email, salt, password, joined, verified, permissions) VALUES ($1, $2, $3, $4, $5, false, 0);",
|
||||
"query": "INSERT INTO Accounts (username, email, salt, password, joined, verified, flags) VALUES ($1, $2, $3, $4, $5, false, 0);",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Timestamp"
|
||||
|
@ -14,5 +14,5 @@
|
|||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "8b37c356c1df9961b47359a56f69b658adafa9c58b79e6dd867f6e4f62fbd144"
|
||||
"hash": "4919f945175894aac7c9a49c948876996b35dc3ffe772856de59443c2a1b9b64"
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n CREATE TABLE IF NOT EXISTS Accounts (\n id SERIAL8 NOT NULL,\n\t username VARCHAR(32) NOT NULL,\n email TEXT NOT NULL,\n\t salt VARCHAR(22) NOT NULL,\n\t password VARCHAR(96) NOT NULL,\n joined TIMESTAMP NOT NULL,\n verified BOOLEAN NOT NULL,\n follows BIGINT[],\n followers BIGINT[],\n flags INT2 NOT NULL,\n\t PRIMARY KEY(id)\n );\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "50c529b57bcd8d46a5160f07fe11ce40e1db91fab254f3604e5b08587fe7caf2"
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n CREATE TABLE IF NOT EXISTS Accounts (\n id SERIAL8 NOT NULL,\n\t username VARCHAR(32) NOT NULL,\n email VARCHAR(44) NOT NULL,\n\t salt VARCHAR(22) NOT NULL,\n\t password VARCHAR(128) NOT NULL,\n joined TIMESTAMP NOT NULL,\n verified BOOLEAN NOT NULL,\n follows BIGINT[],\n followers BIGINT[],\n flags INT2 NOT NULL,\n\t PRIMARY KEY(id)\n );\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "56757ca46f6c90c12658dcaf2a64aca3d15ae6540bfafac0db23848c1aa32ba4"
|
||||
}
|
|
@ -30,7 +30,7 @@ dependencies = [
|
|||
"actix-service",
|
||||
"actix-utils",
|
||||
"ahash 0.8.3",
|
||||
"base64 0.21.2",
|
||||
"base64 0.21.3",
|
||||
"bitflags 1.3.2",
|
||||
"brotli",
|
||||
"bytes",
|
||||
|
@ -347,6 +347,18 @@ version = "1.0.72"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854"
|
||||
|
||||
[[package]]
|
||||
name = "argon2"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17ba4cac0a46bc1d2912652a751c47f2a9f3a7fe89bcae2275d418f5270402f9"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"blake2",
|
||||
"cpufeatures",
|
||||
"password-hash 0.5.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.73"
|
||||
|
@ -402,9 +414,9 @@ checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5"
|
|||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.21.2"
|
||||
version = "0.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d"
|
||||
checksum = "414dcefbc63d77c526a76b3afcf6fbb9b5e2791c19c3aa2297733208750c6e53"
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
|
@ -449,6 +461,15 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blake2"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
|
||||
dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
|
@ -1603,13 +1624,14 @@ dependencies = [
|
|||
"actix-web",
|
||||
"actix-web-httpauth",
|
||||
"anyhow",
|
||||
"argon2",
|
||||
"base64 0.21.3",
|
||||
"chrono",
|
||||
"clap",
|
||||
"env_logger",
|
||||
"libinjection",
|
||||
"log",
|
||||
"mail-send",
|
||||
"pbkdf2 0.12.2",
|
||||
"regex",
|
||||
"serde",
|
||||
"sha2",
|
||||
|
@ -1782,18 +1804,6 @@ dependencies = [
|
|||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pbkdf2"
|
||||
version = "0.12.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
|
||||
dependencies = [
|
||||
"digest",
|
||||
"hmac",
|
||||
"password-hash 0.5.0",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "peeking_take_while"
|
||||
version = "0.1.2"
|
||||
|
@ -2092,7 +2102,7 @@ version = "1.0.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2"
|
||||
dependencies = [
|
||||
"base64 0.21.2",
|
||||
"base64 0.21.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2410,7 +2420,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "8ca69bf415b93b60b80dc8fda3cb4ef52b2336614d8da2de5456cc942a110482"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"base64 0.21.2",
|
||||
"base64 0.21.3",
|
||||
"bitflags 2.4.0",
|
||||
"byteorder",
|
||||
"bytes",
|
||||
|
@ -2453,7 +2463,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "a0db2df1b8731c3651e204629dd55e52adbae0462fa1bdcbed56a2302c18181e"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"base64 0.21.2",
|
||||
"base64 0.21.3",
|
||||
"bitflags 2.4.0",
|
||||
"byteorder",
|
||||
"chrono",
|
||||
|
@ -3157,7 +3167,7 @@ dependencies = [
|
|||
"crossbeam-utils",
|
||||
"flate2",
|
||||
"hmac",
|
||||
"pbkdf2 0.11.0",
|
||||
"pbkdf2",
|
||||
"sha1",
|
||||
"time 0.3.25",
|
||||
"zstd 0.11.2+zstd.1.5.2",
|
||||
|
|
|
@ -12,7 +12,6 @@ actix-web = "4"
|
|||
serde = { version = "1.0.183", features = ["derive"] }
|
||||
libinjection = "0.3.2"
|
||||
sha2 = "0.10.2"
|
||||
pbkdf2 = { version = "0.12", features = ["simple"] }
|
||||
env_logger = "0.10"
|
||||
log = "0.4"
|
||||
clap = { version = "4.3.21", features = ["derive"] }
|
||||
|
@ -22,3 +21,5 @@ uuid = { version = "1.4.1", features = ["v4"] }
|
|||
chrono = "0.4"
|
||||
mail-send = "0.4.0"
|
||||
regex = "1.9.3"
|
||||
argon2 = "0.5.2"
|
||||
base64 = "0.21.3"
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
use anyhow::{Error, Result};
|
||||
use pbkdf2::{
|
||||
use argon2::{
|
||||
password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
|
||||
Pbkdf2,
|
||||
Argon2,
|
||||
};
|
||||
use base64;
|
||||
use sha2::{Digest, Sha256};
|
||||
use sqlx::{postgres::PgPool, types::chrono as sqlx_chrono};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
|
@ -22,7 +24,7 @@ pub struct Account {
|
|||
pub verified: bool,
|
||||
pub follows: Option<Vec<i64>>,
|
||||
pub followers: Option<Vec<i64>>,
|
||||
pub permissions: i64,
|
||||
pub flags: i16,
|
||||
}
|
||||
|
||||
impl From<i64> for ID {
|
||||
|
@ -64,19 +66,25 @@ impl Account {
|
|||
password: &String,
|
||||
) -> Result<Self> {
|
||||
let salt = SaltString::generate(&mut OsRng);
|
||||
let hash = Pbkdf2
|
||||
let password_hash = Argon2::default()
|
||||
.hash_password(password.as_bytes(), &salt)
|
||||
.map_err(|_| anyhow::Error::msg("Failed to hash the password"))?
|
||||
.to_string();
|
||||
|
||||
let email_hash = {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(email.as_bytes());
|
||||
base64::encode(hasher.finalize())
|
||||
};
|
||||
|
||||
let joined = sqlx_chrono::Utc::now().naive_utc();
|
||||
|
||||
sqlx::query!(
|
||||
r#"INSERT INTO Accounts (username, email, salt, password, joined, verified, permissions) VALUES ($1, $2, $3, $4, $5, false, 0);"#,
|
||||
r#"INSERT INTO Accounts (username, email, salt, password, joined, verified, flags) VALUES ($1, $2, $3, $4, $5, false, 0);"#,
|
||||
username,
|
||||
email,
|
||||
email_hash,
|
||||
salt.to_string(),
|
||||
hash,
|
||||
password_hash,
|
||||
joined,
|
||||
)
|
||||
.execute(pool)
|
||||
|
@ -103,7 +111,7 @@ impl Account {
|
|||
verified as "verified!: bool",
|
||||
follows,
|
||||
followers,
|
||||
permissions
|
||||
flags
|
||||
FROM Accounts WHERE username = $1;"#,
|
||||
username
|
||||
)
|
||||
|
@ -129,7 +137,7 @@ impl Account {
|
|||
verified as "verified!: bool",
|
||||
follows,
|
||||
followers,
|
||||
permissions
|
||||
flags
|
||||
FROM Accounts WHERE id = $1;"#,
|
||||
id
|
||||
)
|
||||
|
@ -143,6 +151,12 @@ impl Account {
|
|||
}
|
||||
|
||||
pub async fn from_email(pool: &PgPool, email: &String) -> Result<Option<Self>> {
|
||||
let email_hash = {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(email.as_bytes());
|
||||
base64::encode(hasher.finalize())
|
||||
};
|
||||
|
||||
match sqlx::query_as!(
|
||||
Self,
|
||||
r#"SELECT
|
||||
|
@ -155,9 +169,9 @@ impl Account {
|
|||
verified as "verified!: bool",
|
||||
follows,
|
||||
followers,
|
||||
permissions
|
||||
flags
|
||||
FROM Accounts WHERE email = $1;"#,
|
||||
email
|
||||
email_hash
|
||||
)
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
|
@ -172,16 +186,19 @@ impl Account {
|
|||
let hash = PasswordHash::new(self.password.as_str())
|
||||
.map_err(|_| anyhow::Error::msg("Failed to parse the password hash"))?;
|
||||
|
||||
match Pbkdf2.verify_password(password.as_bytes(), &hash) {
|
||||
match Argon2::default().verify_password(password.as_bytes(), &hash) {
|
||||
Ok(_) => Ok(true),
|
||||
Err(_) => Ok(false),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn delete(&self, pool: &PgPool) -> Result<()> {
|
||||
sqlx::query!(r#"DELETE FROM AuthTokens WHERE account = $1;"#, self.id.id())
|
||||
.execute(pool)
|
||||
.await?;
|
||||
sqlx::query!(
|
||||
r#"DELETE FROM AuthTokens WHERE account = $1;"#,
|
||||
self.id.id()
|
||||
)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(r#"DELETE FROM Accounts WHERE id = $1;"#, self.id.id())
|
||||
.execute(pool)
|
||||
|
|
|
@ -25,7 +25,7 @@ pub async fn register(
|
|||
return Ok(data::RegisterResponse::MalformedEmail);
|
||||
}
|
||||
|
||||
// Check if the usernam is already taken
|
||||
// Check if the username is already taken
|
||||
if let Some(account) = Account::from_username(pool, &request.username).await? {
|
||||
// Check if the account that has taken the username is verified or has an open verification request.
|
||||
if account.verified
|
||||
|
|
|
@ -16,16 +16,16 @@ pub async fn start(port: u16, pool: PgPool) -> Result<()> {
|
|||
sqlx::query!(
|
||||
r#"
|
||||
CREATE TABLE IF NOT EXISTS Accounts (
|
||||
id SERIAL8 NOT NULL,
|
||||
username VARCHAR(32) NOT NULL,
|
||||
email TEXT NOT NULL,
|
||||
salt VARCHAR(22) NOT NULL,
|
||||
password VARCHAR(96) NOT NULL,
|
||||
joined TIMESTAMP NOT NULL,
|
||||
verified BOOLEAN NOT NULL,
|
||||
id SERIAL8 NOT NULL,
|
||||
username VARCHAR(32) NOT NULL,
|
||||
email VARCHAR(44) NOT NULL,
|
||||
salt VARCHAR(22) NOT NULL,
|
||||
password VARCHAR(128) NOT NULL,
|
||||
joined TIMESTAMP NOT NULL,
|
||||
verified BOOLEAN NOT NULL,
|
||||
follows BIGINT[],
|
||||
followers BIGINT[],
|
||||
flags INT2 NOT NULL,
|
||||
flags INT2 NOT NULL,
|
||||
PRIMARY KEY(id)
|
||||
);
|
||||
"#
|
||||
|
|
Loading…
Reference in New Issue