From 2dfaf85eeababf735f6f67a48a9c0316caeead29 Mon Sep 17 00:00:00 2001 From: traxys Date: Wed, 16 Aug 2023 21:16:30 +0200 Subject: [PATCH] Save the login information in a cookie --- Cargo.lock | 264 ++++++++++++++++++++++++++++++++++++++++++++++++---- Cargo.toml | 4 + src/main.rs | 92 +++++++++++++++++- 3 files changed, 336 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d3c8015..662bc02 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -59,6 +59,12 @@ dependencies = [ "libc", ] +[[package]] +name = "anyhow" +version = "1.0.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c6f84b74db2535ebae81eede2f39b947dcbf01d093ae5f791e5dd414a1bf289" + [[package]] name = "async-trait" version = "0.1.73" @@ -134,6 +140,28 @@ dependencies = [ "tower-service", ] +[[package]] +name = "axum-extra" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a93e433be9382c737320af3924f7d5fc6f89c155cf2bf88949d8f5126fab283f" +dependencies = [ + "axum", + "axum-core", + "bytes", + "cookie", + "futures-util", + "http", + "http-body", + "mime", + "pin-project-lite", + "serde", + "tokio", + "tower", + "tower-layer", + "tower-service", +] + [[package]] name = "backtrace" version = "0.3.68" @@ -173,6 +201,12 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "binstring" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e0d60973d9320722cb1206f412740e162a33b8547ea8d6be75d7cff237c7a85" + [[package]] name = "bitflags" version = "1.3.2" @@ -275,6 +309,18 @@ dependencies = [ "phf_codegen", ] +[[package]] +name = "coarsetime" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a90d114103adbc625300f346d4d09dfb4ab1c4a8df6868435dd903392ecf4354" +dependencies = [ + "libc", + "once_cell", + "wasi", + "wasm-bindgen", +] + [[package]] name = "color-eyre" version = "0.6.2" @@ -308,6 +354,17 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" +[[package]] +name = "cookie" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + [[package]] name = "core-foundation-sys" version = "0.8.4" @@ -379,6 +436,12 @@ dependencies = [ "typenum", ] +[[package]] +name = "ct-codecs" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3b7eb4404b8195a9abb6356f4ac07d8ba267045c8d6d220ac4dc992e6cc75df" + [[package]] name = "darling" version = "0.20.3" @@ -414,6 +477,17 @@ dependencies = [ "syn 2.0.28", ] +[[package]] +name = "der" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" +dependencies = [ + "const-oid", + "pem-rfc7468 0.6.0", + "zeroize", +] + [[package]] name = "der" version = "0.7.8" @@ -421,7 +495,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" dependencies = [ "const-oid", - "pem-rfc7468", + "pem-rfc7468 0.7.0", "zeroize", ] @@ -470,12 +544,22 @@ version = "0.16.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4b1e0c257a9e9f25f90ff76d7a68360ed497ee519c8e428d1825ef0000799d4" dependencies = [ - "der", + "der 0.7.8", "digest", "elliptic-curve", "rfc6979", - "signature", - "spki", + "signature 2.1.0", + "spki 0.7.2", +] + +[[package]] +name = "ed25519-compact" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a3d382e8464107391c8706b4c14b087808ecb909f6c15c34114bc42e53a9e4c" +dependencies = [ + "ct-codecs", + "getrandom", ] [[package]] @@ -500,8 +584,8 @@ dependencies = [ "generic-array", "group", "hkdf", - "pem-rfc7468", - "pkcs8", + "pem-rfc7468 0.7.0", + "pkcs8 0.10.2", "rand_core", "sec1", "subtle", @@ -856,6 +940,30 @@ dependencies = [ "digest", ] +[[package]] +name = "hmac-sha1-compact" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9d405ec732fa3fcde87264e54a32a84956a377b3e3107de96e59b798c84a7" + +[[package]] +name = "hmac-sha256" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3688e69b38018fec1557254f64c8dc2cc8ec502890182f395dbb0aa997aa5735" +dependencies = [ + "digest", +] + +[[package]] +name = "hmac-sha512" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4ce1f4656bae589a3fab938f9f09bf58645b7ed01a2c5f8a3c238e01a4ef78a" +dependencies = [ + "digest", +] + [[package]] name = "home" version = "0.5.5" @@ -1060,6 +1168,46 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jwt-simple" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "733741e7bcd1532b56c9ba6c698c069f274f3782ad956f0d2c7f31650cedaa1b" +dependencies = [ + "anyhow", + "binstring", + "coarsetime", + "ct-codecs", + "ed25519-compact", + "hmac-sha1-compact", + "hmac-sha256", + "hmac-sha512", + "k256", + "p256", + "p384", + "rand", + "rsa 0.7.2", + "serde", + "serde_json", + "spki 0.6.0", + "thiserror", + "zeroize", +] + +[[package]] +name = "k256" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2", + "signature 2.1.0", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -1119,8 +1267,12 @@ name = "mail_accounts" version = "0.1.0" dependencies = [ "axum", + "axum-extra", + "base64 0.21.2", "color-eyre", + "cookie", "envious", + "jwt-simple", "once_cell", "openidconnect", "parking_lot", @@ -1326,7 +1478,7 @@ dependencies = [ "p256", "p384", "rand", - "rsa", + "rsa 0.9.2", "serde", "serde-value", "serde_derive", @@ -1423,6 +1575,15 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +[[package]] +name = "pem-rfc7468" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d159833a9105500e0398934e205e0773f0b27529557134ecfc51c27646adac" +dependencies = [ + "base64ct", +] + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -1553,15 +1714,37 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs1" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff33bdbdfc54cc98a2eca766ebdec3e1b8fb7387523d5c9c9a2891da856f719" +dependencies = [ + "der 0.6.1", + "pkcs8 0.9.0", + "spki 0.6.0", + "zeroize", +] + [[package]] name = "pkcs1" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" dependencies = [ - "der", - "pkcs8", - "spki", + "der 0.7.8", + "pkcs8 0.10.2", + "spki 0.7.2", +] + +[[package]] +name = "pkcs8" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" +dependencies = [ + "der 0.6.1", + "spki 0.6.0", ] [[package]] @@ -1570,8 +1753,8 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ - "der", - "spki", + "der 0.7.8", + "spki 0.7.2", ] [[package]] @@ -1760,6 +1943,27 @@ dependencies = [ "winapi", ] +[[package]] +name = "rsa" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "094052d5470cbcef561cb848a7209968c9f12dfa6d668f4bca048ac5de51099c" +dependencies = [ + "byteorder", + "digest", + "num-bigint-dig", + "num-integer", + "num-iter", + "num-traits", + "pkcs1 0.4.1", + "pkcs8 0.9.0", + "rand_core", + "signature 1.6.4", + "smallvec", + "subtle", + "zeroize", +] + [[package]] name = "rsa" version = "0.9.2" @@ -1773,11 +1977,11 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "pkcs1", - "pkcs8", + "pkcs1 0.7.5", + "pkcs8 0.10.2", "rand_core", - "signature", - "spki", + "signature 2.1.0", + "spki 0.7.2", "subtle", "zeroize", ] @@ -1876,9 +2080,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ "base16ct", - "der", + "der 0.7.8", "generic-array", - "pkcs8", + "pkcs8 0.10.2", "subtle", "zeroize", ] @@ -2024,6 +2228,16 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "1.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" +dependencies = [ + "digest", + "rand_core", +] + [[package]] name = "signature" version = "2.1.0" @@ -2099,6 +2313,16 @@ dependencies = [ "lock_api", ] +[[package]] +name = "spki" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" +dependencies = [ + "base64ct", + "der 0.6.1", +] + [[package]] name = "spki" version = "0.7.2" @@ -2106,7 +2330,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" dependencies = [ "base64ct", - "der", + "der 0.7.8", ] [[package]] @@ -2243,7 +2467,7 @@ dependencies = [ "once_cell", "percent-encoding", "rand", - "rsa", + "rsa 0.9.2", "serde", "sha1", "sha2", diff --git a/Cargo.toml b/Cargo.toml index 69db301..7000315 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,8 +6,12 @@ edition = "2021" [dependencies] axum = { version = "0.6.20", features = ["query"] } +axum-extra = { version = "0.7.7", features = ["cookie"] } +base64 = "0.21.2" color-eyre = "0.6.2" +cookie = "0.17.0" envious = "0.2.2" +jwt-simple = "0.11.6" once_cell = "1.18.0" openidconnect = "3.3.0" parking_lot = "0.12.1" diff --git a/src/main.rs b/src/main.rs index f86077c..baac1c6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,7 +11,11 @@ use axum::{ routing::get, Router, }; +use axum_extra::extract::{cookie::Cookie, CookieJar}; +use base64::{engine::general_purpose, engine::Engine}; use color_eyre::eyre; +use cookie::{time::OffsetDateTime, SameSite}; +use jwt_simple::prelude::*; use once_cell::sync::Lazy; use openidconnect::{ core::{CoreAuthenticationFlow, CoreClient, CoreProviderMetadata}, @@ -33,6 +37,50 @@ fn default_address() -> String { "127.0.0.1".into() } +#[derive(Clone)] +pub(crate) struct Base64(pub(crate) HS256Key); + +impl std::fmt::Debug for Base64 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + r#"b64"{}""#, + &general_purpose::STANDARD.encode(self.0.to_bytes()) + ) + } +} + +impl<'de> Deserialize<'de> for Base64 { + fn deserialize(de: D) -> Result + where + D: Deserializer<'de>, + { + use serde::de::Visitor; + + struct DecodingVisitor; + impl<'de> Visitor<'de> for DecodingVisitor { + type Value = Base64; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("must be a base 64 string") + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + general_purpose::STANDARD + .decode(v) + .map_err(E::custom) + .map(|b| HS256Key::from_bytes(&b)) + .map(Base64) + } + } + + de.deserialize_str(DecodingVisitor) + } +} + fn deserialize_comma<'de, D>(de: D) -> Result, D::Error> where D: Deserializer<'de>, @@ -63,6 +111,7 @@ where #[derive(Deserialize, Debug)] #[serde(rename_all = "UPPERCASE")] struct Settings { + jwt_secret: Base64, #[serde(default = "default_port")] port: u16, #[serde(default = "default_address")] @@ -262,8 +311,10 @@ impl OpenidConnector { } struct AppState { + jwt_secret: HS256Key, db: PgPool, oidc: OpenidConnector, + domain: String, } #[derive(thiserror::Error, Debug)] @@ -272,6 +323,8 @@ enum Error { Db(#[from] sqlx::Error), #[error("An error occured when rendering a template")] Tera(#[from] tera::Error), + #[error("A JWT error occured")] + Jwt(#[from] jwt_simple::Error), #[error("An internal error occured")] InternalError, } @@ -306,6 +359,10 @@ impl IntoResponse for Error { tracing::error!("Tera error: {e:?}"); InternalError.into_response() } + Error::Jwt(e) => { + tracing::error!("JWT error: {e:?}"); + InternalError.into_response() + } } } } @@ -326,7 +383,8 @@ async fn redirected( state: State>, Path(id): Path, Query(redirect): Query, -) -> Result<(), Error> { + jar: CookieJar, +) -> Result { match state .oidc .redirected(id, redirect.state, redirect.code) @@ -336,7 +394,7 @@ async fn redirected( let account = sqlx::query!("SELECT id FROM accounts WHERE sub = $1", sub) .fetch_optional(&state.db) .await?; - let _id = match account { + let id = match account { Some(r) => r.id, None => { let id = Uuid::new_v4(); @@ -346,7 +404,28 @@ async fn redirected( id } }; - Ok(()) + + let expire = std::time::Duration::from_secs(3600 * 24 * 31 * 6); + + let mut claims = Claims::create(expire.into()); + claims.subject = Some(id.to_string()); + + let token = state.jwt_secret.authenticate(claims)?; + + let mut cookie = Cookie::named("mail_admin_token"); + cookie.set_value(token); + cookie.set_http_only(true); + + let mut now = OffsetDateTime::now_utc(); + now += expire; + cookie.set_expires(now); + cookie.set_same_site(SameSite::Strict); + cookie.set_secure(true); + cookie.set_path("/"); + + let jar = jar.add(cookie); + + Ok(jar) } Err(e) => { tracing::error!("Could not finish OAuth2 flow: {e:?}"); @@ -403,7 +482,12 @@ async fn main() -> color_eyre::Result<()> { .route("/login", get(login)) .route("/login/redirect/:id", get(redirected)) .fallback(page_not_found) - .with_state(Arc::new(AppState { db, oidc })); + .with_state(Arc::new(AppState { + db, + oidc, + jwt_secret: config.jwt_secret.0, + domain: config.domain, + })); Ok(axum::Server::bind(&addr) .serve(router.into_make_service())