Log in with Mastodon
Dependencies
- [2]
5XW3RJKJAdd nodeinfo to well known - [3]
HBDTKI2BAdd auth info to password reset - [4]
EKDGFVDQUpdate game password from personal page - [5]
IEFJTEIIUpdate handlebars dependency - [6]
V47NQLKFUse individual redirect url for each Mastodon domain - [7]
EVP2FSBHSplit index page - [8]
YQFDKZIUPut FreeOrion version to config for simple update - [9]
MFJBQU5FFix check if web password wasn't set - [10]
LWCZDLBIAdd buttons to accept and remove cookies - [11]
WW3KRXX6Add page for reset game password - [12]
HDHALX3UAdd Cookies Policy page - [13]
KDYHFAE3Add and remove cookies - [14]
CMA5SKJ3Copy turns Atom generator - [15]
S6MX4MFOAdd handlers for accepting and removing cookies - [16]
BVCWJKEXGet auth info for slow game page - [17]
D3RL62X5Implement revoking delegation - [18]
H6GGDVHWShow auth info in reset password page - [19]
MCF5COULAdd personal page - [20]
6NYILMKIAdd page for slow game - [21]
TRBYOQBICheck CSRF and user existence - [22]
LTQCLSBUSplit database usage in pages - [23]
ZQIIC7C3Add field to store timestamp of joining game - [24]
YDWTHWAIShow form to revoke delegation - [25]
WLWTNO4YCreate form to request game password change link - [26]
RPAQDOZ4Move atom pages to separate module - [27]
FUCFD4UVAdd log in and log out support - [28]
V5ULHL43Implement delegation support - [29]
6CFNBL5LAdd headers for better security - [30]
JG2BQCRDImplement query for delegation - [31]
BCMU6UYKStart login mastodon form - [32]
MUTHALNPDetect user and domain in Mastodon fediverse - [33]
G4JCZ5F7Store try to register on Mastodon domain - [34]
RSIBXP3SFix redirect URL - [35]
WEVOADLSRequire to accept cookies policy for log in - [36]
7QCJHYB6Show contacts in personal page - [37]
B5D2IKSBUse Cow for user name - [38]
5RQCVFRHStart leave game form - [39]
OJO4B4QOAdd login form and empty handler - [40]
HTYEGVBUAdd data to reset password page - [41]
XTHO73VKImplement leave game - [42]
MGRTVGLJRedirect to Mastodon domain - [43]
5UYVIBUMUpdate game password from page - [44]
HZDCKIXQUse constants for templates - [45]
2MPJPGRYPopulate cookies jar - [46]
H7NQUYI6Record join to the game - [47]
YUFPADNOAdd team info to slow game page data - [48]
KDKRTAYJRegister application on Mastodon domain - [49]
WVHXYKCVAdd postgresql pools - [50]
NY766BOQAccept form to add player - [*]
4MZ4VIR7Initial commit - [*]
3AKTNR3AUpdate deadpool-postgres dependency - [*]
BCXEUKX6Add config, static files and web server
Change contents
- replacement in src/pages/slow_game.rs at line 136
let jar = request_to_jar(request);let jar = request_to_jar(&request); - replacement in src/pages/slow_game.rs at line 423
let jar = request_to_jar(request);let jar = request_to_jar(&request); - replacement in src/pages/slow_game.rs at line 491
let jar = request_to_jar(request);let jar = request_to_jar(&request); - replacement in src/pages/slow_game.rs at line 559
let jar = request_to_jar(request);let jar = request_to_jar(&request); - replacement in src/pages/slow_game.rs at line 628
let jar = request_to_jar(request);let jar = request_to_jar(&request); - replacement in src/pages/slow_game.rs at line 695
let jar = request_to_jar(request);let jar = request_to_jar(&request); - replacement in src/pages/reset_game_pwd.rs at line 35
let jar = request_to_jar(request);let jar = request_to_jar(&request); - replacement in src/pages/reset_game_pwd.rs at line 140
let jar = request_to_jar(request);let jar = request_to_jar(&request); - replacement in src/pages/query_reset_game_pwd.rs at line 29
let jar = request_to_jar(request);let jar = request_to_jar(&request); - replacement in src/pages/my.rs at line 39
let jar = request_to_jar(request);let jar = request_to_jar(&request); - replacement in src/pages/mod.rs at line 82
pub fn request_to_jar(request: HttpRequest) -> cookie::CookieJar {pub fn request_to_jar(request: &HttpRequest) -> cookie::CookieJar { - replacement in src/pages/log_in.rs at line 40
let jar = request_to_jar(request);let jar = request_to_jar(&request); - edit in src/pages/log_in.rs at line 61
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} - edit in src/pages/log_in.rs at line 149
}let mut jar = request_to_jar(request);if !jar.private(&data.cookies_key).get("i_accept_cookie").map_or(false, |x| x.value() == "yes"){return HttpResponse::Found().append_header((header::LOCATION, "cookies-policy.html")).finish(); - replacement in src/pages/log_in.rs at line 151
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,)} - replacement in src/pages/log_in.rs at line 160[7.5737]→[7.5737:5782](∅→∅),[7.5782]→[7.3922:3976](∅→∅),[7.3976]→[7.5839:6027](∅→∅),[7.5839]→[7.5839:6027](∅→∅)
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);}}responsefn get_mastodon_redirect_url(base_proto: &str, base_domain: &str, domain: &str) -> String {format!("{}://{}/mastodon-redirect-{}.html",base_proto, base_domain, domain) - replacement in src/pages/log_in.rs at line 180
let website_redirect = format!("{}mastodon-redirect-{}.html", website, domain);let website_redirect = get_mastodon_redirect_url(&data.base_proto, &data.base_domain, domain); - edit in src/pages/log_in.rs at line 237
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); - edit in src/pages/log_in.rs at line 348
}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))) - replacement in src/pages/log_in.rs at line 413
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"); - replacement in src/pages/log_in.rs at line 419
};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);Err(()) => { - edit in src/pages/log_in.rs at line 423
let row = match rows {Some(row) => row,None => {return HttpResponse::NotFound().body("Not found");}}; - edit in src/pages/log_in.rs at line 424
let player_name = row.get::<_, &str>(0); - replacement in src/pages/log_in.rs at line 547
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(),); - edit in src/pages/log_in.rs at line 556
}#[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")} - replacement in src/pages/index.rs at line 14
let jar = request_to_jar(request);let jar = request_to_jar(&request); - replacement in src/pages/cookies_policy.rs at line 14
let jar = request_to_jar(request);let jar = request_to_jar(&request); - replacement in src/pages/cookies_policy.rs at line 37
let mut jar = request_to_jar(request);let mut jar = request_to_jar(&request); - replacement in src/main.rs at line 16
use pages::log_in::{get_log_in, post_log_in, post_log_in_mastodon};use pages::log_in::{get_log_in, get_log_in_mastodon_redirect, post_log_in, post_log_in_mastodon}; - edit in src/main.rs at line 302
.service(get_log_in_mastodon_redirect) - edit in Cargo.toml at line 21
deadpool = "0.12" - edit in Cargo.lock at line 869[54.20503][7.4003]
"deadpool",