changes
All checks were successful
CI / ci (push) Successful in 19m38s

This commit is contained in:
2026-08-26 20:55:30 +02:00
parent a557c183e9
commit 23d052278a
523 changed files with 24448 additions and 2005 deletions

View File

@@ -12,3 +12,9 @@ uuid.workspace = true
serde.workspace = true
jsonwebtoken.workspace = true
argon2.workspace = true
rand.workspace = true
base64.workspace = true
sha2.workspace = true
[dev-dependencies]
domain = { workspace = true, features = ["test-helpers"] }

View File

@@ -0,0 +1,31 @@
use base64::Engine;
use rand::RngCore;
use sha2::{Digest, Sha256};
use domain::api_token::TokenDigest;
const SECRET_PREFIX: &str = "kmood_";
const SECRET_BYTES: usize = 32;
pub struct ApiTokenSecret;
impl domain::ports::ApiTokenSecretPort for ApiTokenSecret {
fn mint(&self) -> String {
let mut bytes = [0u8; SECRET_BYTES];
rand::rng().fill_bytes(&mut bytes);
let body = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(bytes);
format!("{SECRET_PREFIX}{body}")
}
fn digest(&self, secret: &str) -> TokenDigest {
let digested = Sha256::digest(secret.as_bytes());
TokenDigest::from_persistence(hex(&digested))
}
}
fn hex(bytes: &[u8]) -> String {
bytes.iter().map(|byte| format!("{byte:02x}")).collect()
}

View File

@@ -1,5 +1,7 @@
mod api_token_secret;
mod jwt_service;
mod password_hasher;
pub use api_token_secret::ApiTokenSecret;
pub use jwt_service::JwtAuthService;
pub use password_hasher::Argon2PasswordHasher;

View File

@@ -0,0 +1,62 @@
use domain::ports::ApiTokenSecretPort;
use auth::ApiTokenSecret;
const HEX_CHARACTERS_IN_A_SHA256: usize = 64;
#[test]
fn a_minted_secret_announces_itself_so_a_scanner_can_spot_it() {
let minted = ApiTokenSecret.mint();
assert!(minted.starts_with("kmood_"), "got {minted}");
assert!(minted.len() > 40, "a secret needs real entropy: {minted}");
}
#[test]
fn no_two_secrets_are_alike() {
let secrets: Vec<String> = (0..50).map(|_| ApiTokenSecret.mint()).collect();
let mut unique = secrets.clone();
unique.sort();
unique.dedup();
assert_eq!(unique.len(), secrets.len());
}
#[test]
fn the_same_secret_always_digests_the_same_way() {
let secret = ApiTokenSecret.mint();
assert_eq!(
ApiTokenSecret.digest(&secret).value(),
ApiTokenSecret.digest(&secret).value()
);
}
#[test]
fn different_secrets_digest_differently() {
let first = ApiTokenSecret.digest("kmood_one");
let second = ApiTokenSecret.digest("kmood_two");
assert_ne!(first.value(), second.value());
}
#[test]
fn a_digest_gives_nothing_of_the_secret_away() {
let secret = ApiTokenSecret.mint();
let digest = ApiTokenSecret.digest(&secret);
assert_eq!(digest.value().len(), HEX_CHARACTERS_IN_A_SHA256);
assert!(!digest.value().contains(&secret));
assert!(!secret.contains(digest.value()));
assert!(digest.value().chars().all(|c| c.is_ascii_hexdigit()));
}
#[test]
fn the_digest_matches_the_published_value_for_a_known_input() {
let digest = ApiTokenSecret.digest("abc");
assert_eq!(
digest.value(),
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
);
}