forked from lavina/lavina
implement irc auth via PASS
This commit is contained in:
parent
c39928799d
commit
f8151699db
|
@ -1,10 +1,12 @@
|
||||||
//! Storage and persistence logic.
|
//! Storage and persistence logic.
|
||||||
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use sqlx::sqlite::SqliteConnectOptions;
|
use sqlx::sqlite::SqliteConnectOptions;
|
||||||
use sqlx::{ConnectOptions, SqliteConnection};
|
use sqlx::{ConnectOptions, Connection, FromRow, SqliteConnection};
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
@ -13,8 +15,9 @@ pub struct StorageConfig {
|
||||||
pub db_path: String,
|
pub db_path: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct Storage {
|
pub struct Storage {
|
||||||
conn: SqliteConnection,
|
conn: Arc<Mutex<SqliteConnection>>,
|
||||||
}
|
}
|
||||||
impl Storage {
|
impl Storage {
|
||||||
pub async fn open(config: StorageConfig) -> Result<Storage> {
|
pub async fn open(config: StorageConfig) -> Result<Storage> {
|
||||||
|
@ -26,6 +29,38 @@ impl Storage {
|
||||||
migrator.run(&mut conn).await?;
|
migrator.run(&mut conn).await?;
|
||||||
log::info!("Migrations passed");
|
log::info!("Migrations passed");
|
||||||
|
|
||||||
|
let conn = Arc::new(Mutex::new(conn));
|
||||||
Ok(Storage { conn })
|
Ok(Storage { conn })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
from users u left join challenges_plain_password c on u.id = c.user_id
|
||||||
|
where u.name = ?;",
|
||||||
|
)
|
||||||
|
.bind(name)
|
||||||
|
.fetch_optional(&mut *executor)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn close(mut self) -> Result<()> {
|
||||||
|
let res = match Arc::try_unwrap(self.conn) {
|
||||||
|
Ok(e) => e,
|
||||||
|
Err(_) => return Err(fail("failed to acquire DB ownership on shutdown")),
|
||||||
|
};
|
||||||
|
let res = res.into_inner();
|
||||||
|
res.close().await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromRow)]
|
||||||
|
pub struct StoredUser {
|
||||||
|
pub id: u32,
|
||||||
|
pub name: String,
|
||||||
|
pub password: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,7 +57,7 @@ async fn main() -> Result<()> {
|
||||||
let players = PlayerRegistry::empty(rooms.clone(), &mut metrics)?;
|
let players = PlayerRegistry::empty(rooms.clone(), &mut metrics)?;
|
||||||
let telemetry_terminator =
|
let telemetry_terminator =
|
||||||
util::telemetry::launch(telemetry_config, metrics.clone(), rooms.clone()).await?;
|
util::telemetry::launch(telemetry_config, metrics.clone(), rooms.clone()).await?;
|
||||||
let irc = projections::irc::launch(irc_config, players.clone(), rooms.clone(), metrics.clone()).await?;
|
let irc = projections::irc::launch(irc_config, players.clone(), rooms.clone(), metrics.clone(), storage.clone()).await?;
|
||||||
let xmpp = projections::xmpp::launch(xmpp_config, players, rooms.clone(), metrics.clone()).await?;
|
let xmpp = projections::xmpp::launch(xmpp_config, players, rooms.clone(), metrics.clone()).await?;
|
||||||
tracing::info!("Started");
|
tracing::info!("Started");
|
||||||
|
|
||||||
|
@ -66,6 +66,7 @@ async fn main() -> Result<()> {
|
||||||
tracing::info!("Begin shutdown");
|
tracing::info!("Begin shutdown");
|
||||||
xmpp.terminate().await?;
|
xmpp.terminate().await?;
|
||||||
irc.terminate().await?;
|
irc.terminate().await?;
|
||||||
|
storage.close().await?;
|
||||||
telemetry_terminator.terminate().await?;
|
telemetry_terminator.terminate().await?;
|
||||||
tracing::info!("Shutdown complete");
|
tracing::info!("Shutdown complete");
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -10,6 +10,7 @@ use tokio::net::{TcpListener, TcpStream};
|
||||||
use tokio::sync::mpsc::channel;
|
use tokio::sync::mpsc::channel;
|
||||||
|
|
||||||
use crate::core::player::*;
|
use crate::core::player::*;
|
||||||
|
use crate::core::repo::Storage;
|
||||||
use crate::core::room::{RoomId, RoomInfo, RoomRegistry};
|
use crate::core::room::{RoomId, RoomInfo, RoomRegistry};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::protos::irc::client::{client_message, ClientMessage};
|
use crate::protos::irc::client::{client_message, ClientMessage};
|
||||||
|
@ -45,6 +46,7 @@ async fn handle_socket(
|
||||||
players: PlayerRegistry,
|
players: PlayerRegistry,
|
||||||
rooms: RoomRegistry,
|
rooms: RoomRegistry,
|
||||||
termination: Deferred<()>, // TODO use it to stop the connection gracefully
|
termination: Deferred<()>, // TODO use it to stop the connection gracefully
|
||||||
|
mut storage: Storage,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let (reader, writer) = stream.split();
|
let (reader, writer) = stream.split();
|
||||||
let mut reader: BufReader<ReadHalf> = BufReader::new(reader);
|
let mut reader: BufReader<ReadHalf> = BufReader::new(reader);
|
||||||
|
@ -64,7 +66,8 @@ async fn handle_socket(
|
||||||
writer.flush().await?;
|
writer.flush().await?;
|
||||||
|
|
||||||
let registered_user: Result<RegisteredUser> =
|
let registered_user: Result<RegisteredUser> =
|
||||||
handle_registration(&mut reader, &mut writer).await;
|
handle_registration(&mut reader, &mut writer, &mut storage).await;
|
||||||
|
|
||||||
match registered_user {
|
match registered_user {
|
||||||
Ok(user) => {
|
Ok(user) => {
|
||||||
handle_registered_socket(config, players, rooms, &mut reader, &mut writer, user)
|
handle_registered_socket(config, players, rooms, &mut reader, &mut writer, user)
|
||||||
|
@ -80,13 +83,16 @@ async fn handle_socket(
|
||||||
async fn handle_registration<'a>(
|
async fn handle_registration<'a>(
|
||||||
reader: &mut BufReader<ReadHalf<'a>>,
|
reader: &mut BufReader<ReadHalf<'a>>,
|
||||||
writer: &mut BufWriter<WriteHalf<'a>>,
|
writer: &mut BufWriter<WriteHalf<'a>>,
|
||||||
|
storage: &mut Storage,
|
||||||
) -> Result<RegisteredUser> {
|
) -> Result<RegisteredUser> {
|
||||||
let mut buffer = vec![];
|
let mut buffer = vec![];
|
||||||
|
|
||||||
let mut future_nickname: Option<Str> = None;
|
let mut future_nickname: Option<Str> = None;
|
||||||
let mut future_username: Option<(Str, Str)> = None;
|
let mut future_username: Option<(Str, Str)> = None;
|
||||||
|
|
||||||
loop {
|
let mut pass: Option<Str> = None;
|
||||||
|
|
||||||
|
let user = loop {
|
||||||
let res = reader.read_until(b'\n', &mut buffer).await;
|
let res = reader.read_until(b'\n', &mut buffer).await;
|
||||||
let res = match res {
|
let res = match res {
|
||||||
Ok(len) => {
|
Ok(len) => {
|
||||||
|
@ -110,6 +116,9 @@ async fn handle_registration<'a>(
|
||||||
Ok((_, msg)) => {
|
Ok((_, msg)) => {
|
||||||
log::debug!("Incoming IRC message: {msg:?}");
|
log::debug!("Incoming IRC message: {msg:?}");
|
||||||
match msg {
|
match msg {
|
||||||
|
ClientMessage::Pass { password } => {
|
||||||
|
pass = Some(password);
|
||||||
|
}
|
||||||
ClientMessage::Nick { nickname } => {
|
ClientMessage::Nick { nickname } => {
|
||||||
if let Some((username, realname)) = future_username {
|
if let Some((username, realname)) = future_username {
|
||||||
break Ok(RegisteredUser {
|
break Ok(RegisteredUser {
|
||||||
|
@ -140,7 +149,29 @@ async fn handle_registration<'a>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
buffer.clear();
|
buffer.clear();
|
||||||
|
}?;
|
||||||
|
|
||||||
|
|
||||||
|
let stored_user = storage.retrieve_user_by_name(&*user.nickname).await?;
|
||||||
|
|
||||||
|
let stored_user = match stored_user {
|
||||||
|
Some(u) => u,
|
||||||
|
None => {
|
||||||
|
log::info!("User '{}' not found", user.nickname);
|
||||||
|
return Err(fail("no user found"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if stored_user.password.is_none() {
|
||||||
|
log::info!("Password not defined for user '{}'", user.nickname);
|
||||||
|
return Err(fail("password is not defined"));
|
||||||
}
|
}
|
||||||
|
if stored_user.password.as_deref() != pass.as_deref() {
|
||||||
|
log::info!("Incorrect password supplied for user '{}'", user.nickname);
|
||||||
|
return Err(fail("passwords do not match"));
|
||||||
|
}
|
||||||
|
// TODO properly implement session temination
|
||||||
|
|
||||||
|
Ok(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_registered_socket<'a>(
|
async fn handle_registered_socket<'a>(
|
||||||
|
@ -663,6 +694,7 @@ pub async fn launch(
|
||||||
players: PlayerRegistry,
|
players: PlayerRegistry,
|
||||||
rooms: RoomRegistry,
|
rooms: RoomRegistry,
|
||||||
metrics: MetricsRegistry,
|
metrics: MetricsRegistry,
|
||||||
|
storage: Storage,
|
||||||
) -> Result<Terminator> {
|
) -> Result<Terminator> {
|
||||||
log::info!("Starting IRC projection");
|
log::info!("Starting IRC projection");
|
||||||
let (stopped_tx, mut stopped_rx) = channel(32);
|
let (stopped_tx, mut stopped_rx) = channel(32);
|
||||||
|
@ -708,8 +740,9 @@ pub async fn launch(
|
||||||
let rooms = rooms.clone();
|
let rooms = rooms.clone();
|
||||||
let current_connections_clone = current_connections.clone();
|
let current_connections_clone = current_connections.clone();
|
||||||
let stopped_tx = stopped_tx.clone();
|
let stopped_tx = stopped_tx.clone();
|
||||||
|
let storage = storage.clone();
|
||||||
async move {
|
async move {
|
||||||
match handle_socket(config, stream, &socket_addr, players, rooms, termination).await {
|
match handle_socket(config, stream, &socket_addr, players, rooms, termination, storage).await {
|
||||||
Ok(_) => log::info!("Connection terminated"),
|
Ok(_) => log::info!("Connection terminated"),
|
||||||
Err(err) => log::warn!("Connection failed: {err}"),
|
Err(err) => log::warn!("Connection failed: {err}"),
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,10 @@ pub enum ClientMessage {
|
||||||
Nick {
|
Nick {
|
||||||
nickname: Str,
|
nickname: Str,
|
||||||
},
|
},
|
||||||
|
/// PASS <password>
|
||||||
|
Pass {
|
||||||
|
password: Str,
|
||||||
|
},
|
||||||
/// USER <username> 0 * :<realname>
|
/// USER <username> 0 * :<realname>
|
||||||
User {
|
User {
|
||||||
username: Str,
|
username: Str,
|
||||||
|
@ -62,6 +66,7 @@ pub fn client_message(input: &str) -> IResult<&str, ClientMessage> {
|
||||||
client_message_ping,
|
client_message_ping,
|
||||||
client_message_pong,
|
client_message_pong,
|
||||||
client_message_nick,
|
client_message_nick,
|
||||||
|
client_message_pass,
|
||||||
client_message_user,
|
client_message_user,
|
||||||
client_message_join,
|
client_message_join,
|
||||||
client_message_mode,
|
client_message_mode,
|
||||||
|
@ -115,6 +120,21 @@ fn client_message_nick(input: &str) -> IResult<&str, ClientMessage> {
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
fn client_message_pass(input: &str) -> IResult<&str, ClientMessage> {
|
||||||
|
let (input, _) = tag("PASS ")(input)?;
|
||||||
|
let (input, r) = opt(tag(":"))(input)?;
|
||||||
|
let (input, password) = match r {
|
||||||
|
Some(_) => token(input)?,
|
||||||
|
None => receiver(input)?,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
input,
|
||||||
|
ClientMessage::Pass {
|
||||||
|
password: password.into(),
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
fn client_message_user(input: &str) -> IResult<&str, ClientMessage> {
|
fn client_message_user(input: &str) -> IResult<&str, ClientMessage> {
|
||||||
let (input, _) = tag("USER ")(input)?;
|
let (input, _) = tag("USER ")(input)?;
|
||||||
|
|
Loading…
Reference in New Issue