forked from lavina/lavina
Compare commits
No commits in common. "d2617a90a4d385ee15f73f72d95c33e68d51b2b4" and "eb34ac65ae43fc23b146e4f747abb0ffe7633519" have entirely different histories.
d2617a90a4
...
eb34ac65ae
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -7,7 +7,6 @@ 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 dialog;
|
pub mod dialog;
|
||||||
pub mod player;
|
pub mod player;
|
||||||
pub mod prelude;
|
pub mod prelude;
|
||||||
|
|
|
@ -39,7 +39,7 @@ impl Storage {
|
||||||
Ok(Storage { conn })
|
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 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
|
||||||
|
@ -137,7 +137,7 @@ impl Storage {
|
||||||
Ok(())
|
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<()>> {
|
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)
|
||||||
|
|
|
@ -14,7 +14,6 @@ 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;
|
||||||
|
@ -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<()> {
|
async fn auth_user(storage: &mut Storage, login: &str, plain_password: &str) -> Result<()> {
|
||||||
let verdict = Authenticator::new(storage).authenticate(login, plain_password).await?;
|
let stored_user = storage.retrieve_user_by_name(login).await?;
|
||||||
// TODO properly map these onto protocol messages
|
|
||||||
match verdict {
|
let stored_user = match stored_user {
|
||||||
Verdict::Authenticated => Ok(()),
|
Some(u) => u,
|
||||||
Verdict::UserNotFound => Err(anyhow!("no user found")),
|
None => {
|
||||||
Verdict::InvalidPassword => Err(anyhow!("incorrect credentials")),
|
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>(
|
async fn handle_registered_socket<'a>(
|
||||||
|
|
|
@ -9,7 +9,6 @@ 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};
|
||||||
|
@ -22,7 +21,6 @@ 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;
|
||||||
|
@ -302,18 +300,28 @@ 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 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
|
// TODO return proper XML errors to the client
|
||||||
match verdict {
|
|
||||||
Verdict::Authenticated => {}
|
if stored_user.password.is_none() {
|
||||||
Verdict::UserNotFound => {
|
log::info!("Password not defined for user '{}'", name);
|
||||||
return Err(anyhow!("no user found"));
|
return Err(fail("password is not defined"));
|
||||||
}
|
|
||||||
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()),
|
||||||
|
|
24
src/http.rs
24
src/http.rs
|
@ -12,7 +12,6 @@ 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;
|
||||||
|
@ -142,20 +141,17 @@ async fn endpoint_set_password(
|
||||||
*response.status_mut() = StatusCode::BAD_REQUEST;
|
*response.status_mut() = StatusCode::BAD_REQUEST;
|
||||||
return Ok(response);
|
return Ok(response);
|
||||||
};
|
};
|
||||||
let verdict = Authenticator::new(&storage).set_password(&res.player_name, &res.password).await?;
|
let Some(_) = storage.set_password(&res.player_name, &res.password).await? else {
|
||||||
match verdict {
|
let payload = ErrorResponse {
|
||||||
UpdatePasswordResult::PasswordUpdated => {}
|
code: errors::PLAYER_NOT_FOUND,
|
||||||
UpdatePasswordResult::UserNotFound => {
|
message: "No such player exists",
|
||||||
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