use std::sync::Arc;
use resp_async::response::RespError;
use resp_async::{Cmd, State, Value};
use sqlx::mysql::MySqlPoolOptions;
use sqlx::{Connection, MySqlConnection};
use crate::config::TenantError;
use crate::handlers::util::{arg_to_string, ok, wrong_arity};
use crate::state::{AppState, AuthContext, SessionHandle};
use crate::storage::map_sql_err;
pub async fn auth(
Cmd(cmd): Cmd,
State(state): State<AppState>,
SessionHandle(session): SessionHandle,
) -> Result<Value, RespError> {
let (user, password) = parse_auth_args(&cmd)?;
let tenant_id = match state.config.auth.tenant_id(&user) {
Ok(value) => value,
Err(TenantError::NoMatch) => return Err(RespError::NoAuth),
};
let dsn = state.config.mysql_dsn(&user, &password);
if MySqlConnection::connect(&dsn).await.is_err() {
return Err(RespError::NoAuth);
}
let pool = match state.pools.get(&user) {
Some(pool) => pool,
None => {
let pool = MySqlPoolOptions::new()
.max_connections(state.config.pool.max_connections)
.acquire_timeout(state.config.pool.connect_timeout)
.connect(&dsn)
.await
.map_err(map_sql_err)?;
state.pools.insert_if_absent(user.clone(), Arc::new(pool))
}
};
state.pools.touch(&user);
session
.set_auth(AuthContext {
user,
tenant_id,
pool,
})
.await;
Ok(ok())
}
fn parse_auth_args(cmd: &resp_async::Command) -> Result<(String, String), RespError> {
match cmd.args.len() {
1 => {
let combined = arg_to_string(&cmd.args[0])?;
let mut parts = combined.splitn(2, ':');
let user = parts.next().unwrap_or_default();
let password = parts.next().unwrap_or_default();
if user.is_empty() || password.is_empty() {
return Err(RespError::invalid_data("ERR invalid username/password"));
}
Ok((user.to_string(), password.to_string()))
}
2 => {
let user = arg_to_string(&cmd.args[0])?;
let password = arg_to_string(&cmd.args[1])?;
if user.is_empty() || password.is_empty() {
return Err(RespError::invalid_data("ERR invalid username/password"));
}
Ok((user, password))
}
_ => Err(wrong_arity("AUTH")),
}
}