forked from lavina/lavina
Compare commits
2 Commits
6c08d69f41
...
784711f99b
Author | SHA1 | Date |
---|---|---|
Nikita Vilunov | 784711f99b | |
Nikita Vilunov | d805061d5b |
|
@ -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
|
||||||
|
);
|
|
@ -0,0 +1,64 @@
|
||||||
|
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::repo::Storage;
|
||||||
|
|
||||||
|
pub enum Verdict {
|
||||||
|
Authenticated,
|
||||||
|
UserNotFound,
|
||||||
|
InvalidPassword,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum UpdatePasswordResult {
|
||||||
|
PasswordUpdated,
|
||||||
|
UserNotFound,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Authenticator<'a> {
|
||||||
|
storage: &'a Storage,
|
||||||
|
}
|
||||||
|
impl<'a> Authenticator<'a> {
|
||||||
|
pub fn new(storage: &'a Storage) -> Self {
|
||||||
|
Self { storage }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn authenticate(&self, login: &str, provided_password: &str) -> Result<Verdict> {
|
||||||
|
let Some(stored_user) = self.storage.retrieve_user_by_name(login).await? else {
|
||||||
|
return Ok(Verdict::UserNotFound);
|
||||||
|
};
|
||||||
|
if let Some(argon2_hash) = stored_user.argon2_hash {
|
||||||
|
let argon2 = Argon2::default();
|
||||||
|
let password_hash =
|
||||||
|
PasswordHash::new(&argon2_hash).map_err(|e| anyhow!("Failed to parse password hash: {e:?}"))?;
|
||||||
|
let password_verifier = argon2.verify_password(provided_password.as_bytes(), &password_hash);
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_password(&self, login: &str, provided_password: &str) -> Result<UpdatePasswordResult> {
|
||||||
|
let Some(u) = self.storage.retrieve_user_by_name(login).await? else {
|
||||||
|
return Ok(UpdatePasswordResult::UserNotFound);
|
||||||
|
};
|
||||||
|
|
||||||
|
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}");
|
||||||
|
Ok(UpdatePasswordResult::PasswordUpdated)
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ use crate::player::PlayerRegistry;
|
||||||
use crate::repo::Storage;
|
use crate::repo::Storage;
|
||||||
use crate::room::RoomRegistry;
|
use crate::room::RoomRegistry;
|
||||||
|
|
||||||
|
pub mod auth;
|
||||||
pub mod player;
|
pub mod player;
|
||||||
pub mod prelude;
|
pub mod prelude;
|
||||||
pub mod repo;
|
pub mod repo;
|
||||||
|
|
|
@ -233,14 +233,21 @@ 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(id.clone(), inner.room_registry.clone(), inner.storage.clone()).await;
|
drop(inner);
|
||||||
inner.players.insert(id.clone(), (handle.clone(), fiber));
|
let mut inner = self.0.write().await;
|
||||||
inner.metric_active_players.inc();
|
if let Some((handle, _)) = inner.players.get(id) {
|
||||||
handle
|
handle.clone()
|
||||||
|
} else {
|
||||||
|
let (handle, fiber) =
|
||||||
|
Player::launch(id.clone(), inner.room_registry.clone(), 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 room;
|
mod room;
|
||||||
mod user;
|
mod user;
|
||||||
|
|
||||||
|
@ -38,11 +39,12 @@ impl Storage {
|
||||||
Ok(Storage { conn })
|
Ok(Storage { conn })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn retrieve_user_by_name(&mut 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)
|
||||||
|
@ -136,7 +138,7 @@ impl Storage {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn set_password<'a>(&'a mut self, name: &'a str, pwd: &'a str) -> Result<Option<()>> {
|
pub async fn set_password<'a>(&'a self, name: &'a str, pwd: &'a str) -> Result<Option<()>> {
|
||||||
async fn inner(txn: &mut Transaction<'_, Sqlite>, name: &str, pwd: &str) -> Result<Option<()>> {
|
async fn inner(txn: &mut Transaction<'_, Sqlite>, name: &str, pwd: &str) -> Result<Option<()>> {
|
||||||
let id: Option<(u32,)> = sqlx::query_as("select * from users where name = ? limit 1;")
|
let id: Option<(u32,)> = sqlx::query_as("select * from users where name = ? limit 1;")
|
||||||
.bind(name)
|
.bind(name)
|
||||||
|
@ -174,6 +176,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)]
|
||||||
|
|
|
@ -14,6 +14,7 @@ use tokio::net::tcp::{ReadHalf, WriteHalf};
|
||||||
use tokio::net::{TcpListener, TcpStream};
|
use tokio::net::{TcpListener, TcpStream};
|
||||||
use tokio::sync::mpsc::channel;
|
use tokio::sync::mpsc::channel;
|
||||||
|
|
||||||
|
use lavina_core::auth::{Authenticator, Verdict};
|
||||||
use lavina_core::player::*;
|
use lavina_core::player::*;
|
||||||
use lavina_core::prelude::*;
|
use lavina_core::prelude::*;
|
||||||
use lavina_core::repo::Storage;
|
use lavina_core::repo::Storage;
|
||||||
|
@ -405,24 +406,13 @@ fn sasl_fail_message(sender: Str, nick: Str, text: Str) -> ServerMessage {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn auth_user(storage: &mut Storage, login: &str, plain_password: &str) -> Result<()> {
|
async fn auth_user(storage: &mut Storage, login: &str, plain_password: &str) -> Result<()> {
|
||||||
let stored_user = storage.retrieve_user_by_name(login).await?;
|
let verdict = Authenticator::new(storage).authenticate(login, plain_password).await?;
|
||||||
|
// TODO properly map these onto protocol messages
|
||||||
let stored_user = match stored_user {
|
match verdict {
|
||||||
Some(u) => u,
|
Verdict::Authenticated => Ok(()),
|
||||||
None => {
|
Verdict::UserNotFound => Err(anyhow!("no user found")),
|
||||||
log::info!("User '{}' not found", login);
|
Verdict::InvalidPassword => Err(anyhow!("incorrect credentials")),
|
||||||
return Err(anyhow!("no user found"));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let Some(expected_password) = stored_user.password else {
|
|
||||||
log::info!("Password not defined for user '{}'", login);
|
|
||||||
return Err(anyhow!("password is not defined"));
|
|
||||||
};
|
|
||||||
if expected_password != plain_password {
|
|
||||||
log::info!("Incorrect password supplied for user '{}'", login);
|
|
||||||
return Err(anyhow!("passwords do not match"));
|
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_registered_socket<'a>(
|
async fn handle_registered_socket<'a>(
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -9,6 +9,7 @@ use std::net::SocketAddr;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use anyhow::anyhow;
|
||||||
use futures_util::future::join_all;
|
use futures_util::future::join_all;
|
||||||
use prometheus::Registry as MetricsRegistry;
|
use prometheus::Registry as MetricsRegistry;
|
||||||
use quick_xml::events::{BytesDecl, Event};
|
use quick_xml::events::{BytesDecl, Event};
|
||||||
|
@ -21,6 +22,7 @@ use tokio::sync::mpsc::channel;
|
||||||
use tokio_rustls::rustls::{Certificate, PrivateKey};
|
use tokio_rustls::rustls::{Certificate, PrivateKey};
|
||||||
use tokio_rustls::TlsAcceptor;
|
use tokio_rustls::TlsAcceptor;
|
||||||
|
|
||||||
|
use lavina_core::auth::{Authenticator, Verdict};
|
||||||
use lavina_core::player::{PlayerConnection, PlayerId, PlayerRegistry};
|
use lavina_core::player::{PlayerConnection, PlayerId, PlayerRegistry};
|
||||||
use lavina_core::prelude::*;
|
use lavina_core::prelude::*;
|
||||||
use lavina_core::repo::Storage;
|
use lavina_core::repo::Storage;
|
||||||
|
@ -300,28 +302,18 @@ async fn socket_auth(
|
||||||
match AuthBody::from_str(&auth.body) {
|
match AuthBody::from_str(&auth.body) {
|
||||||
Ok(logopass) => {
|
Ok(logopass) => {
|
||||||
let name = &logopass.login;
|
let name = &logopass.login;
|
||||||
let stored_user = storage.retrieve_user_by_name(name).await?;
|
let verdict = Authenticator::new(storage).authenticate(name, &logopass.password).await?;
|
||||||
|
|
||||||
let stored_user = match stored_user {
|
|
||||||
Some(u) => u,
|
|
||||||
None => {
|
|
||||||
log::info!("User '{}' not found", name);
|
|
||||||
return Err(fail("no user found"));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// TODO return proper XML errors to the client
|
// TODO return proper XML errors to the client
|
||||||
|
match verdict {
|
||||||
if stored_user.password.is_none() {
|
Verdict::Authenticated => {}
|
||||||
log::info!("Password not defined for user '{}'", name);
|
Verdict::UserNotFound => {
|
||||||
return Err(fail("password is not defined"));
|
return Err(anyhow!("no user found"));
|
||||||
|
}
|
||||||
|
Verdict::InvalidPassword => {
|
||||||
|
return Err(anyhow!("incorrect credentials"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if stored_user.password.as_deref() != Some(&logopass.password) {
|
|
||||||
log::info!("Incorrect password supplied for user '{}'", name);
|
|
||||||
return Err(fail("passwords do not match"));
|
|
||||||
}
|
|
||||||
|
|
||||||
let name: Str = name.as_str().into();
|
let name: Str = name.as_str().into();
|
||||||
|
|
||||||
Ok(Authenticated {
|
Ok(Authenticated {
|
||||||
player_id: PlayerId::from(name.clone())?,
|
player_id: PlayerId::from(name.clone())?,
|
||||||
xmpp_name: Name(name.clone()),
|
xmpp_name: Name(name.clone()),
|
||||||
|
|
|
@ -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);
|
||||||
|
|
24
src/http.rs
24
src/http.rs
|
@ -12,6 +12,7 @@ use prometheus::{Encoder, Registry as MetricsRegistry, TextEncoder};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::net::TcpListener;
|
use tokio::net::TcpListener;
|
||||||
|
|
||||||
|
use lavina_core::auth::{Authenticator, UpdatePasswordResult};
|
||||||
use lavina_core::prelude::*;
|
use lavina_core::prelude::*;
|
||||||
use lavina_core::repo::Storage;
|
use lavina_core::repo::Storage;
|
||||||
use lavina_core::room::RoomRegistry;
|
use lavina_core::room::RoomRegistry;
|
||||||
|
@ -141,17 +142,20 @@ async fn endpoint_set_password(
|
||||||
*response.status_mut() = StatusCode::BAD_REQUEST;
|
*response.status_mut() = StatusCode::BAD_REQUEST;
|
||||||
return Ok(response);
|
return Ok(response);
|
||||||
};
|
};
|
||||||
let Some(_) = storage.set_password(&res.player_name, &res.password).await? else {
|
let verdict = Authenticator::new(&storage).set_password(&res.player_name, &res.password).await?;
|
||||||
let payload = ErrorResponse {
|
match verdict {
|
||||||
code: errors::PLAYER_NOT_FOUND,
|
UpdatePasswordResult::PasswordUpdated => {}
|
||||||
message: "No such player exists",
|
UpdatePasswordResult::UserNotFound => {
|
||||||
|
let payload = ErrorResponse {
|
||||||
|
code: errors::PLAYER_NOT_FOUND,
|
||||||
|
message: "No such player exists",
|
||||||
|
}
|
||||||
|
.to_body();
|
||||||
|
let mut response = Response::new(payload);
|
||||||
|
*response.status_mut() = StatusCode::UNPROCESSABLE_ENTITY;
|
||||||
|
return Ok(response);
|
||||||
}
|
}
|
||||||
.to_body();
|
}
|
||||||
let mut response = Response::new(payload);
|
|
||||||
*response.status_mut() = StatusCode::UNPROCESSABLE_ENTITY;
|
|
||||||
return Ok(response);
|
|
||||||
};
|
|
||||||
log::info!("Password changed for player {}", res.player_name);
|
|
||||||
let mut response = Response::new(Full::<Bytes>::default());
|
let mut response = Response::new(Full::<Bytes>::default());
|
||||||
*response.status_mut() = StatusCode::NO_CONTENT;
|
*response.status_mut() = StatusCode::NO_CONTENT;
|
||||||
Ok(response)
|
Ok(response)
|
||||||
|
|
Loading…
Reference in New Issue