From 7527459446e15b1d4b2de5576e5d87d5d6646b19 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Mon, 8 Apr 2024 04:52:06 -0700 Subject: [PATCH] getting email from gitlab works --- Cargo.lock | 172 ++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 2 + src/app.rs | 109 ++++++++++++++++---------------- src/client.rs | 18 ++++++ src/config.rs | 9 ++- src/main.rs | 10 +-- 6 files changed, 257 insertions(+), 63 deletions(-) create mode 100644 src/client.rs diff --git a/Cargo.lock b/Cargo.lock index b03dd48..f804b68 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -887,6 +887,29 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "env_filter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -951,6 +974,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -1201,6 +1239,12 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "hyper" version = "0.14.28" @@ -1239,6 +1283,19 @@ dependencies = [ "tokio-rustls", ] +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "iana-time-zone" version = "0.1.60" @@ -1475,6 +1532,24 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "num-bigint-dig" version = "0.8.4" @@ -1611,6 +1686,50 @@ dependencies = [ "url", ] +[[package]] +name = "openssl" +version = "0.10.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" +dependencies = [ + "bitflags 2.5.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "ordered-float" version = "2.10.1" @@ -1874,10 +1993,12 @@ dependencies = [ "http-body", "hyper", "hyper-rustls", + "hyper-tls", "ipnet", "js-sys", "log", "mime", + "native-tls", "once_cell", "percent-encoding", "pin-project-lite", @@ -1889,6 +2010,7 @@ dependencies = [ "sync_wrapper", "system-configuration", "tokio", + "tokio-native-tls", "tokio-rustls", "tower-service", "url", @@ -2018,6 +2140,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -2048,6 +2179,29 @@ dependencies = [ "zeroize", ] +[[package]] +name = "security-framework" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "1.0.22" @@ -2283,12 +2437,14 @@ dependencies = [ "clap", "clio", "color-eyre", + "env_logger", "eyre", "futures", "indexmap 2.2.6", "listenfd", "log", "openidconnect", + "reqwest", "serde", "serde_json", "tinytemplate", @@ -2479,6 +2635,16 @@ dependencies = [ "syn 2.0.58", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.24.1" @@ -2665,6 +2831,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" diff --git a/Cargo.toml b/Cargo.toml index 7dfd291..807610c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,12 +11,14 @@ actix-web = { version = "4.5.1", features = ["secure-cookies"] } clap = { version = "4.5.4", features = ["derive"] } clio = { version = "0.3.5", features = ["clap-parse"] } color-eyre = "0.6.3" +env_logger = "0.11.3" eyre = "0.6.12" futures = "0.3.30" indexmap = { version = "2.2.6", features = ["serde"] } listenfd = "1.0.1" log = "0.4.21" openidconnect = "3.5.0" +reqwest = "0.11.27" serde = { version = "1.0.197", features = ["derive"] } serde_json = "1.0.115" tinytemplate = "1.2.1" diff --git a/src/app.rs b/src/app.rs index edac951..7186e7d 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,22 +1,21 @@ -use crate::config::{Config, OIDCProvider, OIDCProviderState}; +use crate::{ + client::async_http_client, + config::{Config, OIDCProvider}, +}; use actix_session::Session; use actix_web::{ - cookie::Cookie, get, - http::{ - header::{ContentType, LOCATION}, - uri::PathAndQuery, - StatusCode, Uri, - }, + http::header::{ContentType, LOCATION}, web::{self, ServiceConfig}, - HttpResponse, HttpResponseBuilder, Responder, + HttpResponse, Responder, }; +use eyre::{ensure, Context, OptionExt}; use openidconnect::{ - core::CoreAuthenticationFlow, http::HeaderValue, reqwest::async_http_client, url::Url, - AuthorizationCode, CsrfToken, EndUserEmail, Nonce, RedirectUrl, TokenResponse, + core::{CoreAuthenticationFlow, CoreUserInfoClaims}, + AuthorizationCode, CsrfToken, EndUserEmail, Nonce, OAuth2TokenResponse, }; use serde::{Deserialize, Serialize}; -use std::{borrow::Cow, fmt::Write, marker::PhantomData}; +use std::marker::PhantomData; use tinytemplate::TinyTemplate; pub trait DynTemplate { @@ -85,6 +84,14 @@ templates! { pub struct SubscriptionLoggedOutTemplate { pub providers: Vec, } + #[text = r#"Subscription + +

Logged in as {email}

+"#] + #[derive(Debug, Serialize)] + pub struct SubscriptionLoggedInTemplate { + pub email: EndUserEmail, + } } pub fn make_templates() -> TinyTemplate<'static> { @@ -146,49 +153,42 @@ pub async fn callback( if SessionState::get(&session).is_some() { return resp; } - let Ok(Some(csrf_token)) = session.get::(CSRF_TOKEN) else { - return resp; + let body = async { + let csrf_token = session + .get::(CSRF_TOKEN) + .context("invalid csrf token")? + .ok_or_eyre("missing csrf token")?; + let nonce = session + .get::(NONCE) + .context("invalid nonce")? + .ok_or_eyre("missing nonce")?; + session.remove(CSRF_TOKEN); + session.remove(NONCE); + ensure!( + csrf_token.secret() == query.state.secret(), + "csrf token doesn't match" + ); + let provider = config.oidc.get(&path.0).ok_or_eyre("unknown provider")?; + let token = provider + .state() + .client + .exchange_code(query.into_inner().code) + .request_async(async_http_client) + .await?; + let user_info: CoreUserInfoClaims = provider + .state() + .client + .user_info(token.access_token().clone(), None)? + .request_async(async_http_client) + .await?; + let email = user_info.email().ok_or_eyre("no email provided")?.clone(); + SessionState { email }.set(&session); + Ok(()) }; - let Ok(Some(nonce)) = session.get::(NONCE) else { - return resp; - }; - session.remove(CSRF_TOKEN); - session.remove(NONCE); - if csrf_token.secret() != query.state.secret() { - return resp; + let result: eyre::Result<()> = body.await; + if let Err(e) = result { + log::warn!("callback error: {e}"); } - let Some(provider) = config.oidc.get(&path.0) else { - return resp; - }; - let token = match provider - .state() - .client - .exchange_code(query.into_inner().code) - .request_async(async_http_client) - .await - { - Ok(token) => token, - Err(e) => { - log::warn!("Error getting token: {e}"); - return resp; - } - }; - dbg!(&token); - let claims = match token - .id_token() - .unwrap() - .claims(&provider.state().client.id_token_verifier(), &nonce) - { - Ok(claims) => claims, - Err(e) => { - log::warn!("Error verifying token: {e}"); - return resp; - } - }; - let Some(email) = claims.email().cloned() else { - return resp; - }; - SessionState { email }.set(&session); resp } @@ -227,7 +227,10 @@ pub async fn login( #[get("/subscription")] pub async fn subscription(config: web::Data, session: Session) -> impl Responder { if let Some(SessionState { email }) = SessionState::get(&session) { - todo!() + let mut template = SubscriptionLoggedInTemplate { email }; + HttpResponse::Ok() + .content_type(ContentType::html()) + .body(template.render().expect("rendering can't fail")) } else { let mut template = SubscriptionLoggedOutTemplate { providers: vec![] }; for (name, provider) in config.oidc.iter() { diff --git a/src/client.rs b/src/client.rs new file mode 100644 index 0000000..f80bcf8 --- /dev/null +++ b/src/client.rs @@ -0,0 +1,18 @@ +use openidconnect::{reqwest::Error, HttpRequest, HttpResponse}; + +pub async fn async_http_client( + request: HttpRequest, +) -> Result> { + let method = request.method.clone(); + let url = request.url.clone(); + log::debug!("making client request: {method} {url}"); + let response = match openidconnect::reqwest::async_http_client(request).await { + Ok(v) => v, + Err(e) => return Err(e), + }; + log::debug!( + "client request finished with {}: {method} {url}", + response.status_code + ); + Ok(response) +} diff --git a/src/config.rs b/src/config.rs index f45ee18..6107f11 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,3 +1,4 @@ +use crate::client::async_http_client; use clap::builder::{TryMapValueParser, TypedValueParser, ValueParserFactory}; use clio::CachedInput; use eyre::Context; @@ -66,11 +67,9 @@ impl OIDCProvider { self.state.as_ref().expect("resolve called by main") } pub async fn resolve(&mut self) -> eyre::Result<()> { - let provider_metadata = CoreProviderMetadata::discover_async( - self.issuer_url.clone(), - openidconnect::reqwest::async_http_client, - ) - .await?; + let provider_metadata = + CoreProviderMetadata::discover_async(self.issuer_url.clone(), async_http_client) + .await?; let client = CoreClient::from_provider_metadata( provider_metadata.clone(), self.client_id.clone(), diff --git a/src/main.rs b/src/main.rs index 633aec3..cf1ea69 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,23 +1,22 @@ use crate::cli::{Listen, ListenFdSocket, ListenFdSockets}; -use actix_session::{ - config::{BrowserSession, SessionMiddlewareBuilder}, - storage::CookieSessionStore, - SessionMiddleware, -}; +use actix_session::{storage::CookieSessionStore, SessionMiddleware}; use actix_web::{cookie::Key, web, App, HttpServer}; use clap::Parser; use listenfd::ListenFd; mod app; mod cli; +mod client; mod config; #[tokio::main] async fn main() -> eyre::Result<()> { + env_logger::init(); let mut listenfd = ListenFd::from_env(); color_eyre::install()?; let mut args = cli::CLI::parse(); args.resolve(&mut listenfd).await?; + log::info!("retrieved config from auth servers"); let config = web::Data::new(args.config); let cookie_session_key = Key::generate(); let mut server = HttpServer::new(move || { @@ -49,6 +48,7 @@ async fn main() -> eyre::Result<()> { }; } } + log::info!("started server"); server.run().await?; Ok(()) }