added delay anti enumeration for register
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 10m26s

This commit is contained in:
Dmitri 2026-04-28 21:08:10 +02:00
parent ff4c9eee87
commit 02eb0d7cf5
Signed by: kanopo
GPG Key ID: 759ADD40E3132AC7
6 changed files with 193 additions and 34 deletions

154
Cargo.lock generated
View File

@ -40,7 +40,7 @@ checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072"
dependencies = [
"base64ct",
"blake2",
"cpufeatures",
"cpufeatures 0.2.17",
"password-hash",
]
@ -144,7 +144,7 @@ version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
dependencies = [
"digest",
"digest 0.10.7",
]
[[package]]
@ -156,6 +156,15 @@ dependencies = [
"generic-array",
]
[[package]]
name = "block-buffer"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be"
dependencies = [
"hybrid-array",
]
[[package]]
name = "bumpalo"
version = "3.20.2"
@ -190,6 +199,17 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "chacha20"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601"
dependencies = [
"cfg-if",
"cpufeatures 0.3.0",
"rand_core 0.10.1",
]
[[package]]
name = "chrono"
version = "0.4.44"
@ -219,6 +239,12 @@ version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
[[package]]
name = "const-oid"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
@ -234,6 +260,15 @@ dependencies = [
"libc",
]
[[package]]
name = "cpufeatures"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
dependencies = [
"libc",
]
[[package]]
name = "crc"
version = "3.4.0"
@ -283,13 +318,22 @@ dependencies = [
"typenum",
]
[[package]]
name = "crypto-common"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710"
dependencies = [
"hybrid-array",
]
[[package]]
name = "der"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
dependencies = [
"const-oid",
"const-oid 0.9.6",
"pem-rfc7468",
"zeroize",
]
@ -309,12 +353,23 @@ version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"const-oid",
"crypto-common",
"block-buffer 0.10.4",
"const-oid 0.9.6",
"crypto-common 0.1.7",
"subtle",
]
[[package]]
name = "digest"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4850db49bf08e663084f7fb5c87d202ef91a3907271aff24a94eb97ff039153c"
dependencies = [
"block-buffer 0.12.0",
"const-oid 0.10.2",
"crypto-common 0.2.1",
]
[[package]]
name = "displaydoc"
version = "0.2.5"
@ -512,6 +567,7 @@ dependencies = [
"cfg-if",
"libc",
"r-efi",
"rand_core 0.10.1",
"wasip2",
"wasip3",
]
@ -569,7 +625,7 @@ version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
dependencies = [
"digest",
"digest 0.10.7",
]
[[package]]
@ -626,6 +682,15 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "hybrid-array"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d46837a0ed51fe95bd3b05de33cd64a1ee88fc797477ca48446872504507c5"
dependencies = [
"typenum",
]
[[package]]
name = "hyper"
version = "1.9.0"
@ -832,7 +897,7 @@ dependencies = [
"getrandom 0.2.17",
"js-sys",
"pem",
"rand",
"rand 0.8.6",
"serde",
"serde_json",
"signature",
@ -931,7 +996,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
dependencies = [
"cfg-if",
"digest",
"digest 0.10.7",
]
[[package]]
@ -987,7 +1052,7 @@ dependencies = [
"num-integer",
"num-iter",
"num-traits",
"rand",
"rand 0.8.6",
"smallvec",
"zeroize",
]
@ -1070,7 +1135,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
dependencies = [
"base64ct",
"rand_core",
"rand_core 0.6.4",
"subtle",
]
@ -1204,7 +1269,18 @@ checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
"rand_core 0.6.4",
]
[[package]]
name = "rand"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207"
dependencies = [
"chacha20",
"getrandom 0.4.2",
"rand_core 0.10.1",
]
[[package]]
@ -1214,7 +1290,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
"rand_core 0.6.4",
]
[[package]]
@ -1226,6 +1302,12 @@ dependencies = [
"getrandom 0.2.17",
]
[[package]]
name = "rand_core"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
[[package]]
name = "redox_syscall"
version = "0.5.18"
@ -1269,9 +1351,12 @@ dependencies = [
"axum",
"chrono",
"dotenvy",
"hex",
"jsonwebtoken",
"rand 0.10.1",
"serde",
"serde_json",
"sha2 0.11.0",
"sqlx",
"thiserror",
"tokio",
@ -1288,14 +1373,14 @@ version = "0.9.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d"
dependencies = [
"const-oid",
"digest",
"const-oid 0.9.6",
"digest 0.10.7",
"num-bigint-dig",
"num-integer",
"num-traits",
"pkcs1",
"pkcs8",
"rand_core",
"rand_core 0.6.4",
"signature",
"spki",
"subtle",
@ -1399,8 +1484,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
"cpufeatures 0.2.17",
"digest 0.10.7",
]
[[package]]
@ -1410,8 +1495,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
"cpufeatures 0.2.17",
"digest 0.10.7",
]
[[package]]
name = "sha2"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
dependencies = [
"cfg-if",
"cpufeatures 0.3.0",
"digest 0.11.2",
]
[[package]]
@ -1445,8 +1541,8 @@ version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
dependencies = [
"digest",
"rand_core",
"digest 0.10.7",
"rand_core 0.6.4",
]
[[package]]
@ -1544,7 +1640,7 @@ dependencies = [
"percent-encoding",
"serde",
"serde_json",
"sha2",
"sha2 0.10.9",
"smallvec",
"thiserror",
"tokio",
@ -1582,7 +1678,7 @@ dependencies = [
"quote",
"serde",
"serde_json",
"sha2",
"sha2 0.10.9",
"sqlx-core",
"sqlx-mysql",
"sqlx-postgres",
@ -1605,7 +1701,7 @@ dependencies = [
"bytes",
"chrono",
"crc",
"digest",
"digest 0.10.7",
"dotenvy",
"either",
"futures-channel",
@ -1622,11 +1718,11 @@ dependencies = [
"memchr",
"once_cell",
"percent-encoding",
"rand",
"rand 0.8.6",
"rsa",
"serde",
"sha1",
"sha2",
"sha2 0.10.9",
"smallvec",
"sqlx-core",
"stringprep",
@ -1662,10 +1758,10 @@ dependencies = [
"md-5",
"memchr",
"once_cell",
"rand",
"rand 0.8.6",
"serde",
"serde_json",
"sha2",
"sha2 0.10.9",
"smallvec",
"sqlx-core",
"stringprep",

View File

@ -19,3 +19,6 @@ argon2 = "0.5.3"
jsonwebtoken = { version = "10.3.0", features = ["rand"] }
chrono = { version = "0.4.44", features = ["serde"] }
uuid = { version = "1.23.1", features = ["serde", "v4"] }
rand = "0.10.1"
sha2 = "0.11.0"
hex = "0.4.3"

View File

@ -1,11 +1,17 @@
use std::time::Instant;
use axum::Json;
use chrono::{Duration, Utc};
use crate::controller::model::auth_model::*;
use crate::db::repository::refresh_token_repository::create_refresh_token;
use crate::db::repository::user_repository;
use crate::errors::AppError;
use crate::state::AppState;
use crate::utils::anti_enumeration::anti_enumeration_delay;
use crate::utils::hash;
use crate::utils::jwt::generate_access_token;
use crate::utils::refresh_token::generate_refresh_token;
pub async fn login(state: &AppState, req: LoginRequest) -> Result<Json<AuthResponse>, AppError> {
todo!()
@ -14,16 +20,29 @@ pub async fn register(
state: &AppState,
req: RegisterRequest,
) -> Result<Json<AuthResponse>, AppError> {
let start = Instant::now();
let mut tx = state.db.begin().await?;
let user = user_repository::get_user_by_email(&mut *tx, &req.email).await?;
if user.is_some() {
// user already registered
return Err(AppError::Internal);
{
let user = user_repository::get_user_by_email(&mut *tx, &req.email).await?;
if user.is_some() {
// user already registered
anti_enumeration_delay(start, 150, 300).await;
return Err(AppError::Internal);
}
}
let h = hash::hash(&req.password)?;
let user = user_repository::create_user(&mut *tx, req.email, h).await?;
let access_token = generate_access_token(user.id, &state.jwt_secret)?;
let (refresh_plain, refresh_hash) = generate_refresh_token();
let expires_at = chrono::Utc::now() + Duration::days(7);
create_refresh_token(&mut *tx, user.id, refresh_hash, expires_at).await?;
tx.commit().await?;
todo!("generate jwt and refresh token")
anti_enumeration_delay(start, 150, 300).await;
// TODO: put refresh token in cookie
Ok(Json(AuthResponse {
access_token: access_token,
refresh_token: refresh_plain,
}))
}

View File

@ -0,0 +1,21 @@
use std::time::{Duration, Instant};
use rand::RngExt;
use tokio::time::sleep;
/// Anti-enumeration: ensures consistent response timing regardless of outcome.
/// Call at the end of request handler, before returning.
/// Range: 150-350ms (configurable)
///
/// # Arguments
/// * `start` - The Instant when request processing began
/// * `min_ms` - Minimum delay in milliseconds (default: 150)
/// * `max_ms` - Maximum delay in milliseconds (default: 350)
pub async fn anti_enumeration_delay(start: Instant, min_ms: u64, max_ms: u64) {
let target = min_ms + rand::rng().random::<u64>() % (max_ms - min_ms);
let target_duration = Duration::from_millis(target);
if let Some(remaining) = target_duration.checked_sub(start.elapsed()) {
sleep(remaining).await;
}
}

View File

@ -1,2 +1,4 @@
pub mod anti_enumeration;
pub mod hash;
pub mod jwt;
pub mod refresh_token;

View File

@ -0,0 +1,18 @@
use rand::Rng;
use sha2::{Digest, Sha256};
pub fn generate_refresh_token() -> (String, String) {
let mut bytes = [0u8; 32];
let mut thread_rng = rand::rng();
thread_rng.fill_bytes(&mut bytes);
let plain = hex::encode(&bytes); // 64 hex chars for user
let hash = {
// SHA-256 for DB storage
let mut hasher = Sha256::new();
hasher.update(&plain);
hex::encode(hasher.finalize())
};
(plain, hash)
}