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?") )] 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, 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, first_name: Option, last_name: Option, email: Option, image_url: Option, activated: bool, lang_key: Option, authorities: Vec, } #[derive(Debug, Responder)] pub enum ExampleResponse<'a> { Redirect(Redirect), Unauthorized(rocket::response::status::Unauthorized<&'a str>), } #[cfg_attr( feature = "conduit_bin", get("/sso_return?&&") )] #[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())) }