diff --git a/Cargo.lock b/Cargo.lock index acba7f0..dda5c7f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -101,6 +101,18 @@ version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" +[[package]] +name = "argon2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" +dependencies = [ + "base64ct", + "blake2", + "cpufeatures", + "password-hash", +] + [[package]] name = "async-stream" version = "0.3.5" @@ -176,6 +188,12 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + [[package]] name = "binascii" version = "0.1.4" @@ -194,6 +212,15 @@ version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +[[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" @@ -626,6 +653,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] @@ -1534,6 +1562,14 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "onlytoken" +version = "0.1.0" +dependencies = [ + "argon2", + "rand", +] + [[package]] name = "oorandom" version = "11.1.3" @@ -1573,6 +1609,17 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + [[package]] name = "pear" version = "0.2.8" @@ -2339,6 +2386,12 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + [[package]] name = "syn" version = "1.0.109" diff --git a/Cargo.toml b/Cargo.toml index 6ac5c23..1c4e572 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,9 +22,11 @@ members = [ "members/rest-queued", "members/fluent-demo", "members/fluent-webserv", + "members/onlytoken", ] default-members = [ ".", + "members/onlytoken", "members/fluent-webserv", "members/fluent-demo", "members/echargs", diff --git a/members/onlytoken/Cargo.toml b/members/onlytoken/Cargo.toml new file mode 100644 index 0000000..3bbc5e2 --- /dev/null +++ b/members/onlytoken/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "onlytoken" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +argon2 = "0.5.3" +rand = "0.8.5" diff --git a/members/onlytoken/src/main.rs b/members/onlytoken/src/main.rs new file mode 100644 index 0000000..3f0c275 --- /dev/null +++ b/members/onlytoken/src/main.rs @@ -0,0 +1,123 @@ +use argon2::{ + password_hash::{rand_core::OsRng, PasswordHasher, SaltString}, + Argon2, +}; +use rand::seq::SliceRandom; +use std::{collections::HashMap, hash::Hash}; + +pub const ALPHABET: &str = "qwertzuiopasdfghjklyxcvbnmQWERTZUIOPASDFGHJKLYXCVBNM"; +pub const TOK_LEN: usize = 40; +pub type HashedToken = String; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Token { + inner: String, +} + +impl Token { + pub fn new() -> Self { + let mut rng = rand::thread_rng(); + let mut data = ALPHABET.to_string().into_bytes(); + data.repeat(TOK_LEN); + data.shuffle(&mut rng); + Self { + inner: String::from_utf8(data[..TOK_LEN].to_vec()).unwrap(), + } + } + + #[must_use] + pub fn len(&self) -> usize { + self.inner.len() + } + + #[must_use] + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + #[must_use] + pub fn hash(&self, salt: &SaltString) -> HashedToken { + let argon2 = Argon2::default(); + let hash = argon2 + .hash_password(self.as_bytes(), salt) + .expect("could not hash") + .to_string(); + println!("hashed token: {hash}"); + hash + } + + fn as_bytes(&self) -> &[u8] { + self.inner.as_bytes() + } +} + +impl Default for Token { + fn default() -> Self { + Self::new() + } +} + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +struct User { + data: String, + hashed_tok: String, +} + +impl User { + fn new(data: String, salt: &SaltString) -> (Self, Token, HashedToken) { + let token = Token::new(); + let hashed_tok = token.hash(salt); + ( + User { + data, + hashed_tok: hashed_tok.clone(), + }, + token, + hashed_tok, + ) + } +} + +pub struct Store { + users: HashMap, + salt: SaltString, +} + +impl Store { + fn new() -> Self { + let salt = SaltString::generate(&mut OsRng); + Self { + salt, + users: HashMap::new(), + } + } + fn register(&mut self) -> Token { + let (user, token, hashed_tok) = User::new("garbage data".into(), &self.salt); + self.users.insert(hashed_tok, user); + token + } + fn login(&self, tok: Token) -> Option<&User> { + self.users.get(&tok.hash(&self.salt)) + } +} + +fn main() { + // create the user store + // In a real application, this would be deserialized from a file or lazy-loaded from a database + let mut store = Store::new(); + for _ in 0..4 { + // register a few users as noise + let _ = store.register(); + } + // create our user and keep the token for that user this time + let tok = store.register(); + + println!("token of our user is: {tok:?}"); + + // try to log in with our user + let logged_in = store.login(tok.clone()); + + // did it work? + assert!(logged_in.is_some()); + dbg!(logged_in.unwrap()); +}