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.
189 lines
5.7 KiB
189 lines
5.7 KiB
|
5 years ago
|
use std::sync::Arc;
|
||
|
|
|
||
|
|
// use super::State;
|
||
|
|
use crate::{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: &rocket::State<Arc<RwLock<Database>>>,
|
||
|
|
redirectUrl: &str,
|
||
|
|
mut cookies: &CookieJar<'_>,
|
||
|
|
) -> Redirect {
|
||
|
|
let db_lock = db.read().await;
|
||
|
|
|
||
|
|
let (_key, client) = db_lock.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]
|
||
|
|
pub async fn get_sso_return<'a>(
|
||
|
|
db: &rocket::State<Arc<RwLock<Database>>>,
|
||
|
|
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 db_lock = db.read().await;
|
||
|
|
|
||
|
|
let (_key, client) = db_lock.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_lock.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()))
|
||
|
|
}
|