W2AVMCLON3ANLDK6LSDEYLOLMC3VYX267ESPG4SYWJUQ2QLNWDEQC
5XW3RJKJK74VHJ2RRB64NL7PYKFLI6RMEUQCJYAU2CLITX5SGNRQC
HBDTKI2BJX5JI3ALM5KQTDXFTI6HY2JZHEP4D6Y23WHY4ZUCX6LQC
EKDGFVDQAKIDYILJ6FCHNCD5TF27SPUWZPNKCC3UQ4GZCJTHWDYAC
IEFJTEIIX52L5HYV7PT2NPCC4HLSJ4WUFMIKQDYUGJDO3XFBJCSQC
V47NQLKFHWLZMRGEVV2K6IBBKRSPHR672ATFGARCCM7Z7LJZKQHAC
BVCWJKEXMTMFIK3UUR6GRQYIYJK25V5IX4HWFFDRXCJMDTRAWY6QC
6NYILMKIHYQMGMCRZANU2FDO66DGINOMCX2J4JFTUI6VL3HZ6D5QC
NY766BOQIWOQUA44QDD7YKQB5GK735ZUJ3YNXXVUUE2VQZP2YWPQC
5RQCVFRHI353OFKZPVCNJLJLQPEKJVNEDCTM2224U4WABT2YLMQQC
D3RL62X5NQTWDZXMDHAXS7K5GSTCCEH7L3YT5JMTPWJON3WKRKUAC
JG2BQCRDWKCGVC56DCRBFCOXPHGT33LJ4FHWLIPOM64W6343D7CQC
V5ULHL43GPCVBOTCC4MBRG7KJ7O4QQVFAWUXLJX6E5VDMJCW6BNQC
WVHXYKCVPKAFVMXBEMD3IHG54RKOIDSOCVNR3OIPEZQG36IGYJZQC
H6GGDVHWCRHNOSL6R3CPL35CYGH5CPUXESIMTVJG6WMQCNOQMCBAC
WW3KRXX63EYWW4563QM3VSEN4PGCPR76KJQBYIKCKOCNMDG6JW4QC
HZDCKIXQ3LCD7YPL7ZZBCRMD7YMKDJ2QAALETTG3FYMBF4TNFUBAC
MCF5COULF5S6UFLIN2LN652D2ROAJGEBZN4Y3TYAQDV4OI26Y7FQC
FUCFD4UVRUXDHG24MYEHF2E5E23BIFVKTRGLNBPBVKFLONVSMHTAC
EVP2FSBHQUCAXQ6IIMBD6IS24ODKHP6HFWYCHIMYG6KOFRQG3RVQC
WEVOADLSI5FOOXBM5YPAWEIYN2CXROAQ44CLGYOVTJ2OCLSOONYQC
YDWTHWAI7RPLW3KGM32J2JVUOSFTW4WOV3XPUMNTU3MDNGMQIOZAC
BCMU6UYKFTBIYNJAZVGIQ45BXLTCL7GLW4O4HDMUTCRRWYH2ZQKAC
KDKRTAYJBTOXY75T2U5B236L3SEHC7RPCDNYD2H3JZ3ZNQD3NSEAC
MUTHALNPGO4X2H2CGG7JBQXFYNBUC2ZSZLMFPHSKQ7LAGR3ZGSBAC
MGRTVGLJX4D5YAJGKTVETEKYNLTQZRSV7EJSFWFTWQOTYXTCA2XAC
G4JCZ5F77PDA3ZLXDECLYSS76BRLSDEKU7KMILS5N57ETO2VJO6QC
KDYHFAE3GPCISJYGND4OIPAHUJNPRPEERNJIEY7WSEUCR6IE7TJAC
HDHALX3UK5VSU7WBDWSVF37QG5OBCIOLFGP5PQTGBQFOARZOKWMQC
LWCZDLBI3HN4RF6BVQMW2S7DA3YYQXRDHAXKEFZTAH57WOQZAMPQC
4MZ4VIR7FU3PQ3WKJI6TJIKYOIIBODFEPKLMQ32S4AKPZSDFO6AQC
WLWTNO4YJ4VBMJYVTP2LGPFQAJTOB524BD5INJFA44X2FKAX76DQC
3AKTNR3AY7FRYJN3ZZ7NWZ5O7S5UM726G4NGUCW4UD2LWGX5ZUXQC
BCXEUKX6RVE4UQE46ITSQY2NXYK3MJT7HHL7CJOM7T4OOOCADMIAC
fn log_in_response(
player_name: &str,
request: &HttpRequest,
cookies_key: &actix_web::cookie::Key,
set_cookie_domain: bool,
base_domain: &str,
) -> HttpResponse {
let mut jar = request_to_jar(request);
if !jar
.private(cookies_key)
.get("i_accept_cookie")
.map_or(false, |x| x.value() == "yes")
{
return HttpResponse::Found()
.append_header((header::LOCATION, "cookies-policy.html"))
.finish();
}
let mut builder = cookie::Cookie::build("auth", player_name.to_lowercase())
.path("/")
.secure(true)
.http_only(true)
.same_site(cookie::SameSite::Strict)
.max_age(cookie::time::Duration::weeks(4));
if set_cookie_domain {
builder = builder.domain(base_domain.to_string());
}
jar.private_mut(cookies_key).add(builder.finish());
let mut response = HttpResponse::Found()
.append_header((header::LOCATION, "my.html"))
.finish();
if let Some(c) = jar.get("auth") {
if let Err(e) = response.add_cookie(c) {
log::error!("Cann't set cookie {}", e);
}
}
response
}
let mut builder = cookie::Cookie::build("auth", form.login.to_lowercase())
.path("/")
.secure(true)
.http_only(true)
.same_site(cookie::SameSite::Strict)
.max_age(cookie::time::Duration::weeks(4));
if data.set_cookie_domain {
builder = builder.domain(data.base_domain.to_string());
}
jar.private_mut(&data.cookies_key).add(builder.finish());
log_in_response(
&form.login,
&request,
&data.cookies_key,
data.set_cookie_domain,
&data.base_domain,
)
}
let mut response = HttpResponse::Found()
.append_header((header::LOCATION, "my.html"))
.finish();
if let Some(c) = jar.get("auth") {
if let Err(e) = response.add_cookie(c) {
log::error!("Cann't set cookie {}", e);
}
}
response
fn get_mastodon_redirect_url(base_proto: &str, base_domain: &str, domain: &str) -> String {
format!(
"{}://{}/mastodon-redirect-{}.html",
base_proto, base_domain, domain
)
Err(())
}
}
}
async fn obtain_token_mastodon(
base_proto: &str,
base_domain: &str,
domain: &str,
client_id: &str,
client_secret: &str,
code: &str,
) -> Result<String, ()> {
let client = awc::Client::default();
let website_redirect = get_mastodon_redirect_url(base_proto, base_domain, domain);
match client
.post(format!("https://{}/oauth/token", domain))
.content_type("application/x-www-form-urlencoded")
.send_body(format!(
"client_id={}&client_secret={}&redirect_uri={}&grant_type=authorization_code&code={}&scopes=read:accounts",
pct_str::PctString::encode(client_id.chars(), pct_str::URIReserved).as_str(),
pct_str::PctString::encode(client_secret.chars(), pct_str::URIReserved).as_str(),
pct_str::PctString::encode(website_redirect.chars(), pct_str::URIReserved).as_str(),
pct_str::PctString::encode(code.chars(), pct_str::URIReserved).as_str()
))
.await {
Ok(mut resp) => match resp.json::<serde_json::Value>().await {
Ok(serde_json::Value::Object(resp_value_obj)) => {
let access_token_value = resp_value_obj.get("access_token");
if let Some(serde_json::Value::String(access_token)) = access_token_value {
Ok(access_token.clone())
} else {
log::error!(
"Mastodon domain {} login failed missing fields JSON {:?}",
domain,
resp_value_obj
);
Err(())
}
}
Ok(resp_value) => {
log::error!(
"Mastodon domain {} login failed non-object JSON {:?}",
domain,
resp_value
);
Err(())
}
Err(err) => {
log::error!(
"Mastodon domain {} login failed JSON {:?}",
domain,
err
);
Err(())
}
}
Err(err) => {
log::error!(
"Mastodon domain {} login failed {:?}",
domain,
err
);
Err(())
}
}
}
async fn check_token_mastodon(domain: &str, token: &str) -> Result<String, ()> {
let client = awc::Client::default();
match client
.get(format!(
"https://{}/api/v1/accounts/verify_credentials",
domain
))
.insert_header(("Authorization", format!("Bearer {}", token)))
.send()
.await
{
Ok(mut resp) => match resp.json::<serde_json::Value>().await {
Ok(serde_json::Value::Object(resp_value_obj)) => {
let username_value = resp_value_obj.get("username");
if let Some(serde_json::Value::String(username)) = username_value {
Ok(username.clone())
} else {
log::error!(
"Mastodon domain {} login failed missing fields JSON {:?}",
domain,
resp_value_obj
);
Err(())
}
}
Ok(resp_value) => {
log::error!(
"Mastodon domain {} login failed non-object JSON {:?}",
domain,
resp_value
);
Err(())
}
Err(err) => {
log::error!("Mastodon domain {} login failed JSON {:?}", domain, err);
Err(())
}
},
Err(err) => {
log::error!("Mastodon domain {} login failed {:?}", domain, err);
}
async fn player_name_by_mastodon(
dbclient: &deadpool::managed::Object<deadpool_postgres::Manager>,
user: &str,
domain: &str,
) -> Result<Option<String>, ()> {
let stmt = match dbclient.prepare("select player_name from auth.contacts where protocol = 'mastodon' and is_active = true and delete_ts is null and address = $1 limit 1;").await {
Ok(stmt) => stmt,
Err(e) => {
log::error!("Pool RO statement error {}", e);
return Err(())
}
};
let address = format!("{}@{}", user, domain);
let rows = match dbclient.query_opt(&stmt, &[&address]).await {
Ok(rows) => rows,
Err(e) => {
log::error!("Pool RO query error {}", e);
return Err(());
}
};
let row = match rows {
Some(row) => row,
None => {
return Ok(None);
}
};
Ok(Some(row.get::<_, String>(0)))
let stmt = match dbclient.prepare("select player_name from auth.contacts where protocol = 'mastodon' and is_active = true and delete_ts is null and address = $1 limit 1;").await {
Ok(stmt) => stmt,
Err(e) => {
log::error!("Pool RO statement error {}", e);
return HttpResponse::ServiceUnavailable().body(actix_web::body::None::new());
let player_name = match player_name_by_mastodon(&dbclient, &user, &domain).await {
Ok(Some(player_name)) => player_name,
Ok(None) => {
return HttpResponse::NotFound().body("Not found");
let website = format!("{}://{}/", data.base_proto, data.base_domain);
let website_redirect = format!("{}mastodon-redirect-{}.html", website, domain);
let location = format!("https://{}/oauth/authorize?client_id={}&scope=read:accounts&redirect_uri={}&response_type=code", domain, pct_str::PctString::encode(client_id.chars(), pct_str::URIReserved).as_str(), pct_str::PctString::encode(website_redirect.chars(), pct_str::URIReserved).as_str());
let website_redirect = get_mastodon_redirect_url(&data.base_proto, &data.base_domain, &domain);
let location = format!("https://{}/oauth/authorize?client_id={}&scope=read:accounts&redirect_uri={}&response_type=code",
domain,
pct_str::PctString::encode(client_id.chars(), pct_str::URIReserved).as_str(),
pct_str::PctString::encode(website_redirect.chars(), pct_str::URIReserved).as_str(),);
}
#[actix_web::get("/mastodon-redirect-{domain}.html")]
pub async fn get_log_in_mastodon_redirect(
req: HttpRequest,
domain: web::Path<String>,
data: web::Data<WebData<'_>>,
data_ro: web::Data<DataBaseRo>,
) -> HttpResponse {
let domain = domain.into_inner();
let domain = match domain.char_indices().nth(128) {
None => domain,
Some((idx, _)) => domain[..idx].to_string(),
};
let mut code = None;
for params in req.query_string().split('&') {
if let Some((k, v)) = params.split_once('=') {
if k == "code" {
code = Some(v);
}
}
}
let code = match code {
Some(r) => r,
None => {
log::warn!(
"Mastodon not redirected with code in {}",
req.query_string()
);
return HttpResponse::BadRequest().body("Incorrect query");
}
};
let dbclient = match data_ro.0.get().await {
Ok(client) => client,
Err(e) => {
log::error!("{}", e);
return HttpResponse::ServiceUnavailable().body(actix_web::body::None::new());
}
};
let stmt = match dbclient
.prepare("select client_id, client_secret from auth.mastodon_apps where domain = $1;")
.await
{
Ok(stmt) => stmt,
Err(e) => {
log::error!("{}", e);
return HttpResponse::ServiceUnavailable().body(actix_web::body::None::new());
}
};
let rows = match dbclient.query_opt(&stmt, &[&domain]).await {
Ok(rows) => rows,
Err(e) => {
log::error!("Pool RO query error {}", e);
return HttpResponse::ServiceUnavailable().body(actix_web::body::None::new());
}
};
let row = match rows {
Some(r) => r,
None => {
return HttpResponse::NotFound().body("Not found");
}
};
let opt_client_id = row.get::<_, Option<&str>>(0);
let opt_client_secret = row.get::<_, Option<&str>>(1);
if let (Some(client_id), Some(client_secret)) = (opt_client_id, opt_client_secret) {
match obtain_token_mastodon(
&data.base_proto,
&data.base_domain,
&domain,
client_id,
client_secret,
code,
)
.await
{
Ok(token) => match check_token_mastodon(&domain, &token).await {
Ok(user) => match player_name_by_mastodon(&dbclient, &user, &domain).await {
Ok(Some(player_name)) => log_in_response(
&player_name,
&req,
&data.cookies_key,
data.set_cookie_domain,
&data.base_domain,
),
Ok(None) => HttpResponse::NotFound().body("Not found"),
Err(()) => {
HttpResponse::ServiceUnavailable().body(actix_web::body::None::new())
}
},
Err(()) => HttpResponse::NotFound().body("Not found"),
},
Err(()) => HttpResponse::NotFound().body("Not found"),
}
} else {
log::warn!("Missing mastodon client data for domain {}", domain);
HttpResponse::NotFound().body("Not found")
}