forked from lavina/lavina
				
			
							parent
							
								
									799da8366c
								
							
						
					
					
						commit
						d305f5bf77
					
				|  | @ -114,6 +114,18 @@ version = "1.0.82" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" | checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" | ||||||
| 
 | 
 | ||||||
|  | [[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]] | [[package]] | ||||||
| name = "assert_matches" | name = "assert_matches" | ||||||
| version = "1.5.0" | version = "1.5.0" | ||||||
|  | @ -192,6 +204,15 @@ dependencies = [ | ||||||
|  "serde", |  "serde", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "blake2" | ||||||
|  | version = "0.10.6" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" | ||||||
|  | dependencies = [ | ||||||
|  |  "digest", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "block-buffer" | name = "block-buffer" | ||||||
| version = "0.10.4" | version = "0.10.4" | ||||||
|  | @ -882,8 +903,10 @@ name = "lavina-core" | ||||||
| version = "0.0.2-dev" | version = "0.0.2-dev" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "anyhow", |  "anyhow", | ||||||
|  |  "argon2", | ||||||
|  "chrono", |  "chrono", | ||||||
|  "prometheus", |  "prometheus", | ||||||
|  |  "rand_core", | ||||||
|  "serde", |  "serde", | ||||||
|  "sqlx", |  "sqlx", | ||||||
|  "tokio", |  "tokio", | ||||||
|  | @ -1126,6 +1149,17 @@ dependencies = [ | ||||||
|  "windows-targets 0.48.5", |  "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]] | [[package]] | ||||||
| name = "paste" | name = "paste" | ||||||
| version = "1.0.14" | version = "1.0.14" | ||||||
|  |  | ||||||
|  | @ -11,3 +11,5 @@ tokio.workspace = true | ||||||
| tracing.workspace = true | tracing.workspace = true | ||||||
| prometheus.workspace = true | prometheus.workspace = true | ||||||
| chrono.workspace = true | chrono.workspace = true | ||||||
|  | argon2 = { version = "0.5.3" } | ||||||
|  | rand_core = { version = "0.6.4", features = ["getrandom"] } | ||||||
|  |  | ||||||
|  | @ -0,0 +1,4 @@ | ||||||
|  | create table challenges_argon2_password( | ||||||
|  |     user_id integer primary key not null, | ||||||
|  |     hash string not null | ||||||
|  | ); | ||||||
|  | @ -1,4 +1,7 @@ | ||||||
| use anyhow::Result; | use anyhow::{anyhow, Result}; | ||||||
|  | use argon2::password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString}; | ||||||
|  | use argon2::Argon2; | ||||||
|  | use rand_core::OsRng; | ||||||
| 
 | 
 | ||||||
| use crate::prelude::log; | use crate::prelude::log; | ||||||
| use crate::repo::Storage; | use crate::repo::Storage; | ||||||
|  | @ -26,21 +29,35 @@ impl<'a> Authenticator<'a> { | ||||||
|         let Some(stored_user) = self.storage.retrieve_user_by_name(login).await? else { |         let Some(stored_user) = self.storage.retrieve_user_by_name(login).await? else { | ||||||
|             return Ok(Verdict::UserNotFound); |             return Ok(Verdict::UserNotFound); | ||||||
|         }; |         }; | ||||||
|         let Some(expected_password) = stored_user.password else { |         if let Some(argon2_hash) = stored_user.argon2_hash { | ||||||
|             log::debug!("Password not defined for user '{}'", login); |             let argon2 = Argon2::default(); | ||||||
|             return Ok(Verdict::InvalidPassword); |             let password_hash = | ||||||
|         }; |                 PasswordHash::new(&argon2_hash).map_err(|e| anyhow!("Failed to parse password hash: {e:?}"))?; | ||||||
|         if expected_password == provided_password { |             let password_verifier = argon2.verify_password(provided_password.as_bytes(), &password_hash); | ||||||
|             return Ok(Verdict::Authenticated); |             if password_verifier.is_ok() { | ||||||
|  |                 return Ok(Verdict::Authenticated); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         if let Some(expected_password) = stored_user.password { | ||||||
|  |             if expected_password == provided_password { | ||||||
|  |                 return Ok(Verdict::Authenticated); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|         Ok(Verdict::InvalidPassword) |         Ok(Verdict::InvalidPassword) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub async fn set_password(&self, login: &str, provided_password: &str) -> Result<UpdatePasswordResult> { |     pub async fn set_password(&self, login: &str, provided_password: &str) -> Result<UpdatePasswordResult> { | ||||||
|         let Some(_) = self.storage.retrieve_user_by_name(login).await? else { |         let Some(u) = self.storage.retrieve_user_by_name(login).await? else { | ||||||
|             return Ok(UpdatePasswordResult::UserNotFound); |             return Ok(UpdatePasswordResult::UserNotFound); | ||||||
|         }; |         }; | ||||||
|         self.storage.set_password(login, provided_password).await?; | 
 | ||||||
|  |         let salt = SaltString::generate(&mut OsRng); | ||||||
|  |         let argon2 = Argon2::default(); | ||||||
|  |         let password_hash = argon2 | ||||||
|  |             .hash_password(provided_password.as_bytes(), &salt) | ||||||
|  |             .map_err(|e| anyhow!("Failed to hash password: {e:?}"))?; | ||||||
|  | 
 | ||||||
|  |         self.storage.set_argon2_challenge(u.id, password_hash.to_string().as_str()).await?; | ||||||
|         log::info!("Password changed for player {login}"); |         log::info!("Password changed for player {login}"); | ||||||
|         Ok(UpdatePasswordResult::PasswordUpdated) |         Ok(UpdatePasswordResult::PasswordUpdated) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -264,20 +264,26 @@ impl PlayerRegistry { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub async fn get_or_launch_player(&mut self, id: &PlayerId) -> PlayerHandle { |     pub async fn get_or_launch_player(&mut self, id: &PlayerId) -> PlayerHandle { | ||||||
|         let mut inner = self.0.write().await; |         let inner = self.0.read().await; | ||||||
|         if let Some((handle, _)) = inner.players.get(id) { |         if let Some((handle, _)) = inner.players.get(id) { | ||||||
|             handle.clone() |             handle.clone() | ||||||
|         } else { |         } else { | ||||||
|             let (handle, fiber) = Player::launch( |             drop(inner); | ||||||
|                 id.clone(), |             let mut inner = self.0.write().await; | ||||||
|                 inner.room_registry.clone(), |             if let Some((handle, _)) = inner.players.get(id) { | ||||||
|                 inner.dialogs.clone(), |                 handle.clone() | ||||||
|                 inner.storage.clone(), |             } else { | ||||||
|             ) |                 let (handle, fiber) = Player::launch( | ||||||
|             .await; |                     id.clone(), | ||||||
|             inner.players.insert(id.clone(), (handle.clone(), fiber)); |                     inner.room_registry.clone(), | ||||||
|             inner.metric_active_players.inc(); |                     inner.dialogs.clone(), | ||||||
|             handle |                     inner.storage.clone(), | ||||||
|  |                 ) | ||||||
|  |                 .await; | ||||||
|  |                 inner.players.insert(id.clone(), (handle.clone(), fiber)); | ||||||
|  |                 inner.metric_active_players.inc(); | ||||||
|  |                 handle | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -0,0 +1,19 @@ | ||||||
|  | use anyhow::Result; | ||||||
|  | 
 | ||||||
|  | use crate::repo::Storage; | ||||||
|  | 
 | ||||||
|  | impl Storage { | ||||||
|  |     pub async fn set_argon2_challenge(&self, user_id: u32, hash: &str) -> Result<()> { | ||||||
|  |         let mut executor = self.conn.lock().await; | ||||||
|  |         sqlx::query( | ||||||
|  |             "insert into challenges_argon2_password(user_id, hash)
 | ||||||
|  |             values (?, ?) | ||||||
|  |             on conflict(user_id) do update set hash = excluded.hash;",
 | ||||||
|  |         ) | ||||||
|  |         .bind(user_id) | ||||||
|  |         .bind(hash) | ||||||
|  |         .execute(&mut *executor) | ||||||
|  |         .await?; | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -12,6 +12,7 @@ use tokio::sync::Mutex; | ||||||
| 
 | 
 | ||||||
| use crate::prelude::*; | use crate::prelude::*; | ||||||
| 
 | 
 | ||||||
|  | mod auth; | ||||||
| mod dialog; | mod dialog; | ||||||
| mod room; | mod room; | ||||||
| mod user; | mod user; | ||||||
|  | @ -42,8 +43,9 @@ impl Storage { | ||||||
|     pub async fn retrieve_user_by_name(&self, name: &str) -> Result<Option<StoredUser>> { |     pub async fn retrieve_user_by_name(&self, name: &str) -> Result<Option<StoredUser>> { | ||||||
|         let mut executor = self.conn.lock().await; |         let mut executor = self.conn.lock().await; | ||||||
|         let res = sqlx::query_as( |         let res = sqlx::query_as( | ||||||
|             "select u.id, u.name, c.password
 |             "select u.id, u.name, c.password, a.hash as argon2_hash
 | ||||||
|             from users u left join challenges_plain_password c on u.id = c.user_id |             from users u left join challenges_plain_password c on u.id = c.user_id | ||||||
|  |             left join challenges_argon2_password a on u.id = a.user_id | ||||||
|             where u.name = ?;",
 |             where u.name = ?;",
 | ||||||
|         ) |         ) | ||||||
|         .bind(name) |         .bind(name) | ||||||
|  | @ -175,6 +177,7 @@ pub struct StoredUser { | ||||||
|     pub id: u32, |     pub id: u32, | ||||||
|     pub name: String, |     pub name: String, | ||||||
|     pub password: Option<String>, |     pub password: Option<String>, | ||||||
|  |     pub argon2_hash: Option<Box<str>>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(FromRow)] | #[derive(FromRow)] | ||||||
|  |  | ||||||
|  | @ -8,6 +8,7 @@ use tokio::io::{AsyncReadExt, AsyncWriteExt, BufReader}; | ||||||
| use tokio::net::tcp::{ReadHalf, WriteHalf}; | use tokio::net::tcp::{ReadHalf, WriteHalf}; | ||||||
| use tokio::net::TcpStream; | use tokio::net::TcpStream; | ||||||
| 
 | 
 | ||||||
|  | use lavina_core::auth::Authenticator; | ||||||
| use lavina_core::player::{JoinResult, PlayerId, SendMessageResult}; | use lavina_core::player::{JoinResult, PlayerId, SendMessageResult}; | ||||||
| use lavina_core::repo::{Storage, StorageConfig}; | use lavina_core::repo::{Storage, StorageConfig}; | ||||||
| use lavina_core::room::RoomId; | use lavina_core::room::RoomId; | ||||||
|  | @ -27,7 +28,7 @@ impl<'a> TestScope<'a> { | ||||||
|         let (reader, writer) = stream.split(); |         let (reader, writer) = stream.split(); | ||||||
|         let reader = BufReader::new(reader); |         let reader = BufReader::new(reader); | ||||||
|         let buffer = vec![]; |         let buffer = vec![]; | ||||||
|         let timeout = Duration::from_millis(100); |         let timeout = Duration::from_millis(1000); | ||||||
|         TestScope { |         TestScope { | ||||||
|             reader, |             reader, | ||||||
|             writer, |             writer, | ||||||
|  | @ -159,7 +160,7 @@ async fn scenario_basic() -> Result<()> { | ||||||
|     // test scenario
 |     // test scenario
 | ||||||
| 
 | 
 | ||||||
|     server.storage.create_user("tester").await?; |     server.storage.create_user("tester").await?; | ||||||
|     server.storage.set_password("tester", "password").await?; |     Authenticator::new(&server.storage).set_password("tester", "password").await?; | ||||||
| 
 | 
 | ||||||
|     let mut stream = TcpStream::connect(server.server.addr).await?; |     let mut stream = TcpStream::connect(server.server.addr).await?; | ||||||
|     let mut s = TestScope::new(&mut stream); |     let mut s = TestScope::new(&mut stream); | ||||||
|  | @ -188,7 +189,7 @@ async fn scenario_join_and_reboot() -> Result<()> { | ||||||
|     // test scenario
 |     // test scenario
 | ||||||
| 
 | 
 | ||||||
|     server.storage.create_user("tester").await?; |     server.storage.create_user("tester").await?; | ||||||
|     server.storage.set_password("tester", "password").await?; |     Authenticator::new(&server.storage).set_password("tester", "password").await?; | ||||||
| 
 | 
 | ||||||
|     let mut stream = TcpStream::connect(server.server.addr).await?; |     let mut stream = TcpStream::connect(server.server.addr).await?; | ||||||
|     let mut s = TestScope::new(&mut stream); |     let mut s = TestScope::new(&mut stream); | ||||||
|  | @ -258,7 +259,7 @@ async fn scenario_force_join_msg() -> Result<()> { | ||||||
|     // test scenario
 |     // test scenario
 | ||||||
| 
 | 
 | ||||||
|     server.storage.create_user("tester").await?; |     server.storage.create_user("tester").await?; | ||||||
|     server.storage.set_password("tester", "password").await?; |     Authenticator::new(&server.storage).set_password("tester", "password").await?; | ||||||
| 
 | 
 | ||||||
|     let mut stream1 = TcpStream::connect(server.server.addr).await?; |     let mut stream1 = TcpStream::connect(server.server.addr).await?; | ||||||
|     let mut s1 = TestScope::new(&mut stream1); |     let mut s1 = TestScope::new(&mut stream1); | ||||||
|  | @ -324,9 +325,9 @@ async fn scenario_two_users() -> Result<()> { | ||||||
|     // test scenario
 |     // test scenario
 | ||||||
| 
 | 
 | ||||||
|     server.storage.create_user("tester1").await?; |     server.storage.create_user("tester1").await?; | ||||||
|     server.storage.set_password("tester1", "password").await?; |     Authenticator::new(&server.storage).set_password("tester1", "password").await?; | ||||||
|     server.storage.create_user("tester2").await?; |     server.storage.create_user("tester2").await?; | ||||||
|     server.storage.set_password("tester2", "password").await?; |     Authenticator::new(&server.storage).set_password("tester2", "password").await?; | ||||||
| 
 | 
 | ||||||
|     let mut stream1 = TcpStream::connect(server.server.addr).await?; |     let mut stream1 = TcpStream::connect(server.server.addr).await?; | ||||||
|     let mut s1 = TestScope::new(&mut stream1); |     let mut s1 = TestScope::new(&mut stream1); | ||||||
|  | @ -388,7 +389,7 @@ async fn scenario_cap_full_negotiation() -> Result<()> { | ||||||
|     // test scenario
 |     // test scenario
 | ||||||
| 
 | 
 | ||||||
|     server.storage.create_user("tester").await?; |     server.storage.create_user("tester").await?; | ||||||
|     server.storage.set_password("tester", "password").await?; |     Authenticator::new(&server.storage).set_password("tester", "password").await?; | ||||||
| 
 | 
 | ||||||
|     let mut stream = TcpStream::connect(server.server.addr).await?; |     let mut stream = TcpStream::connect(server.server.addr).await?; | ||||||
|     let mut s = TestScope::new(&mut stream); |     let mut s = TestScope::new(&mut stream); | ||||||
|  | @ -428,7 +429,7 @@ async fn scenario_cap_full_negotiation_nick_last() -> Result<()> { | ||||||
|     // test scenario
 |     // test scenario
 | ||||||
| 
 | 
 | ||||||
|     server.storage.create_user("tester").await?; |     server.storage.create_user("tester").await?; | ||||||
|     server.storage.set_password("tester", "password").await?; |     Authenticator::new(&server.storage).set_password("tester", "password").await?; | ||||||
| 
 | 
 | ||||||
|     let mut stream = TcpStream::connect(server.server.addr).await?; |     let mut stream = TcpStream::connect(server.server.addr).await?; | ||||||
|     let mut s = TestScope::new(&mut stream); |     let mut s = TestScope::new(&mut stream); | ||||||
|  | @ -467,7 +468,7 @@ async fn scenario_cap_short_negotiation() -> Result<()> { | ||||||
|     // test scenario
 |     // test scenario
 | ||||||
| 
 | 
 | ||||||
|     server.storage.create_user("tester").await?; |     server.storage.create_user("tester").await?; | ||||||
|     server.storage.set_password("tester", "password").await?; |     Authenticator::new(&server.storage).set_password("tester", "password").await?; | ||||||
| 
 | 
 | ||||||
|     let mut stream = TcpStream::connect(server.server.addr).await?; |     let mut stream = TcpStream::connect(server.server.addr).await?; | ||||||
|     let mut s = TestScope::new(&mut stream); |     let mut s = TestScope::new(&mut stream); | ||||||
|  | @ -505,7 +506,7 @@ async fn scenario_cap_sasl_fail() -> Result<()> { | ||||||
|     // test scenario
 |     // test scenario
 | ||||||
| 
 | 
 | ||||||
|     server.storage.create_user("tester").await?; |     server.storage.create_user("tester").await?; | ||||||
|     server.storage.set_password("tester", "password").await?; |     Authenticator::new(&server.storage).set_password("tester", "password").await?; | ||||||
| 
 | 
 | ||||||
|     let mut stream = TcpStream::connect(server.server.addr).await?; |     let mut stream = TcpStream::connect(server.server.addr).await?; | ||||||
|     let mut s = TestScope::new(&mut stream); |     let mut s = TestScope::new(&mut stream); | ||||||
|  | @ -549,7 +550,7 @@ async fn terminate_socket_scenario() -> Result<()> { | ||||||
|     // test scenario
 |     // test scenario
 | ||||||
| 
 | 
 | ||||||
|     server.storage.create_user("tester").await?; |     server.storage.create_user("tester").await?; | ||||||
|     server.storage.set_password("tester", "password").await?; |     Authenticator::new(&server.storage).set_password("tester", "password").await?; | ||||||
| 
 | 
 | ||||||
|     let mut stream = TcpStream::connect(server.server.addr).await?; |     let mut stream = TcpStream::connect(server.server.addr).await?; | ||||||
|     let mut s = TestScope::new(&mut stream); |     let mut s = TestScope::new(&mut stream); | ||||||
|  | @ -574,7 +575,7 @@ async fn server_time_capability() -> Result<()> { | ||||||
|     // test scenario
 |     // test scenario
 | ||||||
| 
 | 
 | ||||||
|     server.storage.create_user("tester").await?; |     server.storage.create_user("tester").await?; | ||||||
|     server.storage.set_password("tester", "password").await?; |     Authenticator::new(&server.storage).set_password("tester", "password").await?; | ||||||
| 
 | 
 | ||||||
|     let mut stream = TcpStream::connect(server.server.addr).await?; |     let mut stream = TcpStream::connect(server.server.addr).await?; | ||||||
|     let mut s = TestScope::new(&mut stream); |     let mut s = TestScope::new(&mut stream); | ||||||
|  |  | ||||||
|  | @ -16,6 +16,7 @@ use tokio_rustls::rustls::client::ServerCertVerifier; | ||||||
| use tokio_rustls::rustls::{ClientConfig, ServerName}; | use tokio_rustls::rustls::{ClientConfig, ServerName}; | ||||||
| use tokio_rustls::TlsConnector; | use tokio_rustls::TlsConnector; | ||||||
| 
 | 
 | ||||||
|  | use lavina_core::auth::Authenticator; | ||||||
| use lavina_core::repo::{Storage, StorageConfig}; | use lavina_core::repo::{Storage, StorageConfig}; | ||||||
| use lavina_core::LavinaCore; | use lavina_core::LavinaCore; | ||||||
| use projection_xmpp::{launch, RunningServer, ServerConfig}; | use projection_xmpp::{launch, RunningServer, ServerConfig}; | ||||||
|  | @ -158,7 +159,7 @@ async fn scenario_basic() -> Result<()> { | ||||||
|     // test scenario
 |     // test scenario
 | ||||||
| 
 | 
 | ||||||
|     server.storage.create_user("tester").await?; |     server.storage.create_user("tester").await?; | ||||||
|     server.storage.set_password("tester", "password").await?; |     Authenticator::new(&server.storage).set_password("tester", "password").await?; | ||||||
| 
 | 
 | ||||||
|     let mut stream = TcpStream::connect(server.server.addr).await?; |     let mut stream = TcpStream::connect(server.server.addr).await?; | ||||||
|     let mut s = TestScope::new(&mut stream); |     let mut s = TestScope::new(&mut stream); | ||||||
|  | @ -210,7 +211,7 @@ async fn scenario_basic_without_headers() -> Result<()> { | ||||||
|     // test scenario
 |     // test scenario
 | ||||||
| 
 | 
 | ||||||
|     server.storage.create_user("tester").await?; |     server.storage.create_user("tester").await?; | ||||||
|     server.storage.set_password("tester", "password").await?; |     Authenticator::new(&server.storage).set_password("tester", "password").await?; | ||||||
| 
 | 
 | ||||||
|     let mut stream = TcpStream::connect(server.server.addr).await?; |     let mut stream = TcpStream::connect(server.server.addr).await?; | ||||||
|     let mut s = TestScope::new(&mut stream); |     let mut s = TestScope::new(&mut stream); | ||||||
|  | @ -260,7 +261,7 @@ async fn terminate_socket() -> Result<()> { | ||||||
|     // test scenario
 |     // test scenario
 | ||||||
| 
 | 
 | ||||||
|     server.storage.create_user("tester").await?; |     server.storage.create_user("tester").await?; | ||||||
|     server.storage.set_password("tester", "password").await?; |     Authenticator::new(&server.storage).set_password("tester", "password").await?; | ||||||
| 
 | 
 | ||||||
|     let mut stream = TcpStream::connect(server.server.addr).await?; |     let mut stream = TcpStream::connect(server.server.addr).await?; | ||||||
|     let mut s = TestScope::new(&mut stream); |     let mut s = TestScope::new(&mut stream); | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue