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")}