Conduit is a simple, fast and reliable chat server powered by Matrix https://conduit.rs
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
184 lines
5.6 KiB
184 lines
5.6 KiB
use std::sync::Arc; |
|
|
|
// use super::State; |
|
use crate::{database::DatabaseGuard, server_server, ConduitResult, Database, Error, Ruma}; |
|
use http::status; |
|
use macaroon::Macaroon; |
|
use openid::{Token, Userinfo}; |
|
use reqwest::Url; |
|
use rocket::{ |
|
http::{Cookie, CookieJar, Header, SameSite, Status}, |
|
response::{Redirect, Responder, Response}, |
|
}; |
|
|
|
#[cfg(feature = "conduit_bin")] |
|
use rocket::get; |
|
use tokio::sync::RwLock; |
|
|
|
const MAC_VALID_SECS: i64 = 10; |
|
|
|
#[cfg_attr( |
|
feature = "conduit_bin", |
|
get("/_matrix/client/r0/login/sso/redirect?<redirectUrl>") |
|
)] |
|
pub async fn get_sso_redirect( |
|
db: DatabaseGuard, |
|
redirectUrl: &str, |
|
mut cookies: &CookieJar<'_>, |
|
) -> Redirect { |
|
let (_key, client) = db.globals.openid_client.as_ref().unwrap(); |
|
|
|
let state = "value"; // TODO: random |
|
|
|
// cookies.add_private(Cookie::new("openid-state", state)); |
|
// cookies.add_private(Cookie::new("openid-redirect-url", redirectUrl.to_string())); |
|
|
|
let cookie1 = Cookie::build("openid-state", state) |
|
.secure(false) |
|
.http_only(true) |
|
.same_site(SameSite::None) |
|
.finish(); |
|
|
|
let cookie2 = Cookie::build("openid-redirect-url", redirectUrl.to_string()) |
|
.secure(false) |
|
.http_only(true) |
|
.same_site(SameSite::None) |
|
.finish(); |
|
|
|
cookies.add_private(cookie1); |
|
cookies.add_private(cookie2); |
|
|
|
// https://docs.rs/openid/0.4.0/openid/struct.Options.html |
|
let auth_url = client.auth_url(&openid::Options { |
|
scope: Some("email".into()), // TODO: openid only? |
|
//TODO: nonce? |
|
state: Some(state.to_string()), |
|
..Default::default() |
|
}); |
|
|
|
Redirect::to(auth_url.to_string()) |
|
} |
|
|
|
async fn request_token( |
|
oidc_client: &openid::DiscoveredClient, |
|
code: &str, |
|
) -> Result<Option<(Token, Userinfo)>, Error> { |
|
let mut token: Token = oidc_client.request_token(&code).await.unwrap().into(); |
|
if let Some(mut id_token) = token.id_token.as_mut() { |
|
oidc_client.decode_token(&mut id_token).unwrap(); |
|
oidc_client.validate_token(&id_token, None, None).unwrap(); |
|
// eprintln!("token: {:?}", id_token); |
|
} else { |
|
return Ok(None); |
|
} |
|
let userinfo = oidc_client.request_userinfo(&token).await.unwrap(); |
|
|
|
// eprintln!("user info: {:?}", userinfo); |
|
Ok(Some((token, userinfo))) |
|
} |
|
|
|
#[derive(Debug)] |
|
struct User { |
|
id: String, |
|
login: Option<String>, |
|
first_name: Option<String>, |
|
last_name: Option<String>, |
|
email: Option<String>, |
|
image_url: Option<String>, |
|
activated: bool, |
|
lang_key: Option<String>, |
|
authorities: Vec<String>, |
|
} |
|
|
|
#[derive(Debug, Responder)] |
|
pub enum ExampleResponse<'a> { |
|
Redirect(Redirect), |
|
Unauthorized(rocket::response::status::Unauthorized<&'a str>), |
|
} |
|
|
|
#[cfg_attr( |
|
feature = "conduit_bin", |
|
get("/sso_return?<session_state>&<state>&<code>") |
|
)] |
|
#[tracing::instrument(skip(db))] |
|
pub async fn get_sso_return<'a>( |
|
db: DatabaseGuard, |
|
session_state: &str, |
|
state: &str, |
|
code: &str, |
|
cookies: &CookieJar<'_>, |
|
) -> ExampleResponse<'a> { |
|
let state_is_valid = cookies |
|
.get_private("openid-state") |
|
.map_or(false, |v| v.value() == state); |
|
if !state_is_valid { |
|
return ExampleResponse::Unauthorized(rocket::response::status::Unauthorized(Some( |
|
"invalid state", |
|
))); |
|
} |
|
|
|
let (_key, client) = db.globals.openid_client.as_ref().unwrap(); |
|
|
|
let username; |
|
match request_token(client, code).await { |
|
Ok(Some((_token, userinfo))) => { |
|
/* |
|
let id = uuid::Uuid::new_v4().to_string(); |
|
|
|
let login = userinfo.preferred_username.clone(); |
|
let email = userinfo.email.clone(); |
|
|
|
let new_user = User { |
|
id: userinfo.sub.clone().unwrap_or_default(), |
|
login, |
|
last_name: userinfo.family_name.clone(), |
|
first_name: userinfo.name.clone(), |
|
email, |
|
activated: userinfo.email_verified, |
|
image_url: userinfo.picture.clone().map(|x| x.to_string()), |
|
lang_key: Some("en".to_string()), |
|
authorities: vec!["ROLE_USER".to_string()], //FIXME: read from token |
|
}; |
|
*/ |
|
|
|
// user = new_user.login.unwrap(); |
|
username = userinfo.preferred_username.unwrap(); |
|
} |
|
Ok(None) => { |
|
return ExampleResponse::Unauthorized(rocket::response::status::Unauthorized(Some( |
|
"no id_token found", |
|
))); |
|
} |
|
Err(err) => { |
|
eprintln!("login error in call: {:?}", err); |
|
return ExampleResponse::Unauthorized(rocket::response::status::Unauthorized(Some( |
|
"login error in call", |
|
))); |
|
} |
|
} |
|
|
|
let (key, _client) = db.globals.openid_client.as_ref().unwrap(); |
|
|
|
// Create our macaroon |
|
let mut macaroon = match Macaroon::create(Some("location".into()), &key, username.into()) { |
|
Ok(macaroon) => macaroon, |
|
Err(error) => panic!("Error creating macaroon: {:?}", error), |
|
}; |
|
|
|
let something = format!("time < {}", chrono::Utc::now().timestamp() + MAC_VALID_SECS).into(); |
|
macaroon.add_first_party_caveat(something); |
|
|
|
let serialized = macaroon.serialize(macaroon::Format::V2).unwrap(); |
|
let encoded = base64::encode_config(serialized, base64::URL_SAFE_NO_PAD); |
|
|
|
let url = cookies |
|
.get_private("openid-redirect-url") |
|
.map(|v| { |
|
let redirectUrl = |
|
Url::parse_with_params(v.value(), &[("loginToken", encoded)]).unwrap(); |
|
redirectUrl |
|
}) |
|
.unwrap(); |
|
|
|
ExampleResponse::Redirect(Redirect::to(url.to_string())) |
|
}
|
|
|