forked from lavina/lavina
1
0
Fork 0

Compare commits

..

No commits in common. "d2617a90a4d385ee15f73f72d95c33e68d51b2b4" and "eb34ac65ae43fc23b146e4f747abb0ffe7633519" have entirely different histories.

6 changed files with 48 additions and 82 deletions

View File

@ -1,47 +0,0 @@
use anyhow::Result;
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);
};
let Some(expected_password) = stored_user.password else {
log::debug!("Password not defined for user '{}'", login);
return Ok(Verdict::InvalidPassword);
};
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(_) = self.storage.retrieve_user_by_name(login).await? else {
return Ok(UpdatePasswordResult::UserNotFound);
};
self.storage.set_password(login, provided_password).await?;
log::info!("Password changed for player {login}");
Ok(UpdatePasswordResult::PasswordUpdated)
}
}

View File

@ -7,7 +7,6 @@ use crate::player::PlayerRegistry;
use crate::repo::Storage;
use crate::room::RoomRegistry;
pub mod auth;
pub mod dialog;
pub mod player;
pub mod prelude;

View File

@ -39,7 +39,7 @@ impl Storage {
Ok(Storage { conn })
}
pub async fn retrieve_user_by_name(&self, name: &str) -> Result<Option<StoredUser>> {
pub async fn retrieve_user_by_name(&mut self, name: &str) -> Result<Option<StoredUser>> {
let mut executor = self.conn.lock().await;
let res = sqlx::query_as(
"select u.id, u.name, c.password
@ -137,7 +137,7 @@ impl Storage {
Ok(())
}
pub async fn set_password<'a>(&'a self, name: &'a str, pwd: &'a str) -> Result<Option<()>> {
pub async fn set_password<'a>(&'a mut self, name: &'a str, pwd: &'a 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;")
.bind(name)

View File

@ -14,7 +14,6 @@ use tokio::net::tcp::{ReadHalf, WriteHalf};
use tokio::net::{TcpListener, TcpStream};
use tokio::sync::mpsc::channel;
use lavina_core::auth::{Authenticator, Verdict};
use lavina_core::player::*;
use lavina_core::prelude::*;
use lavina_core::repo::Storage;
@ -406,13 +405,24 @@ fn sasl_fail_message(sender: Str, nick: Str, text: Str) -> ServerMessage {
}
async fn auth_user(storage: &mut Storage, login: &str, plain_password: &str) -> Result<()> {
let verdict = Authenticator::new(storage).authenticate(login, plain_password).await?;
// TODO properly map these onto protocol messages
match verdict {
Verdict::Authenticated => Ok(()),
Verdict::UserNotFound => Err(anyhow!("no user found")),
Verdict::InvalidPassword => Err(anyhow!("incorrect credentials")),
let stored_user = storage.retrieve_user_by_name(login).await?;
let stored_user = match stored_user {
Some(u) => u,
None => {
log::info!("User '{}' not found", login);
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>(

View File

@ -9,7 +9,6 @@ use std::net::SocketAddr;
use std::path::PathBuf;
use std::sync::Arc;
use anyhow::anyhow;
use futures_util::future::join_all;
use prometheus::Registry as MetricsRegistry;
use quick_xml::events::{BytesDecl, Event};
@ -22,7 +21,6 @@ use tokio::sync::mpsc::channel;
use tokio_rustls::rustls::{Certificate, PrivateKey};
use tokio_rustls::TlsAcceptor;
use lavina_core::auth::{Authenticator, Verdict};
use lavina_core::player::{PlayerConnection, PlayerId, PlayerRegistry};
use lavina_core::prelude::*;
use lavina_core::repo::Storage;
@ -302,18 +300,28 @@ async fn socket_auth(
match AuthBody::from_str(&auth.body) {
Ok(logopass) => {
let name = &logopass.login;
let verdict = Authenticator::new(storage).authenticate(name, &logopass.password).await?;
let stored_user = storage.retrieve_user_by_name(name).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
match verdict {
Verdict::Authenticated => {}
Verdict::UserNotFound => {
return Err(anyhow!("no user found"));
}
Verdict::InvalidPassword => {
return Err(anyhow!("incorrect credentials"));
if stored_user.password.is_none() {
log::info!("Password not defined for user '{}'", name);
return Err(fail("password is not defined"));
}
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();
Ok(Authenticated {
player_id: PlayerId::from(name.clone())?,
xmpp_name: Name(name.clone()),

View File

@ -12,7 +12,6 @@ use prometheus::{Encoder, Registry as MetricsRegistry, TextEncoder};
use serde::{Deserialize, Serialize};
use tokio::net::TcpListener;
use lavina_core::auth::{Authenticator, UpdatePasswordResult};
use lavina_core::prelude::*;
use lavina_core::repo::Storage;
use lavina_core::room::RoomRegistry;
@ -142,10 +141,7 @@ async fn endpoint_set_password(
*response.status_mut() = StatusCode::BAD_REQUEST;
return Ok(response);
};
let verdict = Authenticator::new(&storage).set_password(&res.player_name, &res.password).await?;
match verdict {
UpdatePasswordResult::PasswordUpdated => {}
UpdatePasswordResult::UserNotFound => {
let Some(_) = storage.set_password(&res.player_name, &res.password).await? else {
let payload = ErrorResponse {
code: errors::PLAYER_NOT_FOUND,
message: "No such player exists",
@ -154,8 +150,8 @@ async fn endpoint_set_password(
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());
*response.status_mut() = StatusCode::NO_CONTENT;
Ok(response)