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

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