diff --git a/Cargo.lock b/Cargo.lock index 6673bb4..9ed1fda 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,7 +21,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cf01b9b56e767bb57b94ebf91a58b338002963785cdd7013e21c0d4679471e4" dependencies = [ - "generic-array", + "generic-array 0.12.3", ] [[package]] @@ -57,7 +57,7 @@ checksum = "cfd7e7ae3f9a1fb5c03b389fc6bb9a51400d0c13053f0dca698c832bfd893a0d" dependencies = [ "block-cipher-trait", "byteorder", - "opaque-debug", + "opaque-debug 0.2.3", ] [[package]] @@ -67,7 +67,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f70a6b5f971e473091ab7cfb5ffac6cde81666c4556751d8d5620ead8abf100" dependencies = [ "block-cipher-trait", - "opaque-debug", + "opaque-debug 0.2.3", ] [[package]] @@ -174,7 +174,16 @@ dependencies = [ "block-padding", "byte-tools", "byteorder", - "generic-array", + "generic-array 0.12.3", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array 0.14.4", ] [[package]] @@ -183,7 +192,7 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c924d49bd09e7c06003acda26cd9742e796e34282ec6c1189404dee0c1f4774" dependencies = [ - "generic-array", + "generic-array 0.12.3", ] [[package]] @@ -310,10 +319,10 @@ dependencies = [ "aes-gcm", "base64 0.12.3", "hkdf", - "hmac", + "hmac 0.7.1", "percent-encoding 2.1.0", "rand 0.7.3", - "sha2", + "sha2 0.8.2", "time 0.1.44", ] @@ -344,16 +353,32 @@ dependencies = [ "url 2.1.1", ] +[[package]] +name = "cpuid-bool" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634" + [[package]] name = "crypto-mac" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" dependencies = [ - "generic-array", + "generic-array 0.12.3", "subtle 1.0.0", ] +[[package]] +name = "crypto-mac" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58bcd97a54c7ca5ce2f6eb16f6bede5b0ab5f0055fedc17d2f0b4466e21671ca" +dependencies = [ + "generic-array 0.14.4", + "subtle 2.3.0", +] + [[package]] name = "devise" version = "0.2.0" @@ -418,7 +443,16 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" dependencies = [ - "generic-array", + "generic-array 0.12.3", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array 0.14.4", ] [[package]] @@ -591,6 +625,16 @@ dependencies = [ "typenum", ] +[[package]] +name = "generic-array" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +dependencies = [ + "typenum", + "version_check 0.9.2", +] + [[package]] name = "getrandom" version = "0.1.15" @@ -644,8 +688,8 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fa08a006102488bd9cd5b8013aabe84955cf5ae22e304c2caf655b633aefae3" dependencies = [ - "digest", - "hmac", + "digest 0.8.1", + "hmac 0.7.1", ] [[package]] @@ -654,8 +698,18 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dcb5e64cda4c23119ab41ba960d1e170a774c8e4b9d9e6a9bc18aabf5e59695" dependencies = [ - "crypto-mac", - "digest", + "crypto-mac 0.7.0", + "digest 0.8.1", +] + +[[package]] +name = "hmac" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deae6d9dbb35ec2c502d62b8f7b1c000a0822c3b0794ba36b3149c0a1c840dff" +dependencies = [ + "crypto-mac 0.9.1", + "digest 0.9.0", ] [[package]] @@ -786,6 +840,21 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jwt" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddab2a3df24e9694bde32a01b4f7f3a3693715db5b0f259228b171b2a6526752" +dependencies = [ + "base64 0.12.3", + "crypto-mac 0.9.1", + "digest 0.9.0", + "hmac 0.9.0", + "serde", + "serde_json", + "sha2 0.9.1", +] + [[package]] name = "kernel32-sys" version = "0.2.2" @@ -1019,6 +1088,12 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + [[package]] name = "owo-colors" version = "1.1.3" @@ -1643,10 +1718,23 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69" dependencies = [ - "block-buffer", - "digest", + "block-buffer 0.7.3", + "digest 0.8.1", "fake-simd", - "opaque-debug", + "opaque-debug 0.2.3", +] + +[[package]] +name = "sha2" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2933378ddfeda7ea26f48c555bdad8bb446bf8a3d17832dc83e380d444cfb8c1" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if 0.1.10", + "cpuid-bool", + "digest 0.9.0", + "opaque-debug 0.3.0", ] [[package]] @@ -1996,7 +2084,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df0c900f2f9b4116803415878ff48b63da9edb268668e08cf9292d7503114a01" dependencies = [ - "generic-array", + "generic-array 0.12.3", "subtle 2.3.0", ] @@ -2162,12 +2250,15 @@ dependencies = [ "chrono", "color-eyre", "diesel", - "log 0.4.11", + "hmac 0.9.0", + "jwt", + "lazy_static", "rocket", "rocket_contrib", "rocket_oauth2", "serde", "serde_json", + "sha2 0.9.1", "tracing", "tracing-log", "tracing-subscriber", diff --git a/Cargo.toml b/Cargo.toml index 81d5c0c..16c29b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,10 @@ edition = "2018" chrono = { version = "0.4", features = ["serde"] } color-eyre = "0.5" diesel = { version = "1", features = ["postgres", "r2d2", "uuidv07", "chrono"] } -log = "0" +lazy_static = "1.4" +jwt = "0.11" +hmac = "0.9" +sha2 = "0.9" rocket = "0.4" rocket_oauth2 = "0.4" serde = { version = "^1", features = ["derive"] } diff --git a/shell.nix b/shell.nix index d38355c..04d5003 100644 --- a/shell.nix +++ b/shell.nix @@ -8,4 +8,5 @@ in pkgs.mkShell rec { DATABASE_URL = "postgresql://postgres:hunter2@localhost:5432/wasmcloud"; ROCKET_DATABASES = '' { main_data = { url = "${DATABASE_URL}" } }''; + JWT_SECRET = "hunter2"; } diff --git a/src/jwt.rs b/src/jwt.rs new file mode 100644 index 0000000..534c735 --- /dev/null +++ b/src/jwt.rs @@ -0,0 +1,61 @@ +use crate::{MainDatabase, models, schema}; + +use color_eyre::eyre::{eyre, Result}; +use diesel::prelude::*; +use jwt::{SignWithKey, VerifyWithKey}; +use lazy_static::lazy_static; +use std::env; +use hmac::{Hmac, NewMac}; +use sha2::Sha256; +use std::collections::BTreeMap; + +lazy_static! { + pub static ref SECRET: String = env::var("JWT_SECRET") + .expect("JWT_SECRET to be populated") + .to_string(); +} + + +#[tracing::instrument] +pub fn make(user_id: uuid::Uuid, token_id: uuid::Uuid) -> Result { + let key: Hmac = Hmac::new_varkey(&*SECRET.as_bytes()).unwrap(); + let mut claims = BTreeMap::new(); + claims.insert("sub", user_id.to_string()); + claims.insert("jti", token_id.to_string()); + + let token_str = claims.sign_with_key(&key)?; + tracing::debug!("token: {}", token_str); + Ok(token_str) +} + +#[tracing::instrument(skip(token, conn))] +pub fn verify(token: String, conn: MainDatabase) -> Result { + use schema::{tokens::dsl::tokens, users::dsl::users}; + let key: Hmac = Hmac::new_varkey(&*SECRET.as_bytes()).unwrap(); + + let claims: BTreeMap = token.verify_with_key(&key)?; + let uid = uuid::Uuid::parse_str( + &claims + .get("sub") + .ok_or(eyre!("can't get subscriber from JWT"))?, + )?; + let jti = claims + .get("jti") + .ok_or(eyre!("can't get token ID from JWT"))?; + + let tok = tokens + .find(uuid::Uuid::parse_str(&jti)?) + .get_result::(&*conn)?; + + if tok.deleted_at.is_none() { + return Err(eyre!("token was deleted")); + } + + if tok.user_id != uid { + return Err(eyre!("token and user mismatch")); + } + + let user = users.find(uid).get_result::(&*conn)?; + + Ok(user) +} diff --git a/src/main.rs b/src/main.rs index 0bc09e2..3fed461 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,18 +14,19 @@ use rocket::{ http::{Cookie, Cookies, SameSite}, response::Redirect, }; -use rocket_contrib::{helmet::SpaceHelmet}; +use rocket_contrib::helmet::SpaceHelmet; use rocket_oauth2::{OAuth2, TokenResponse}; pub mod api; pub mod gitea; +pub mod jwt; pub mod models; pub mod schema; #[database("main_data")] pub struct MainDatabase(PgConnection); -struct Gitea; +pub struct Gitea; #[tracing::instrument(skip(oauth2, cookies))] #[get("/login/gitea")] @@ -39,14 +40,14 @@ fn gitea_callback( conn: MainDatabase, token: TokenResponse, mut cookies: Cookies<'_>, -) -> Redirect { +) -> String { let tok = token.access_token().to_string(); let refresh = token.refresh_token().unwrap().to_string(); let gitea_user = gitea::user(tok.clone()).expect("gitea api call to work"); use schema::{ - gitea_tokens, + gitea_tokens, tokens, users::{ dsl::{email, users}, table as users_table, @@ -57,56 +58,68 @@ fn gitea_callback( .limit(1) .load::(&*conn) { - Ok(u) => if u.len() == 0 { - let u = models::NewUser { - salutation: gitea_user.full_name, - email: gitea_user.email, - is_admin: gitea_user.is_admin, - is_locked: false, - tier: 0, - }; + Ok(u) => { + if u.len() == 0 { + let u = models::NewUser { + salutation: gitea_user.full_name, + email: gitea_user.email, + is_admin: gitea_user.is_admin, + is_locked: false, + tier: 0, + }; - let u: models::User = diesel::insert_into(users_table) - .values(&u) - .get_result(&*conn) - .expect("able to insert user"); + let u: models::User = diesel::insert_into(users_table) + .values(&u) + .get_result(&*conn) + .expect("able to insert user"); - let tok = models::NewGiteaToken { - user_id: u.id.clone(), - access_token: tok, - refresh_token: refresh, - }; + let tok = models::NewGiteaToken { + user_id: u.id.clone(), + access_token: tok, + refresh_token: refresh, + }; - let _: models::GiteaToken = diesel::insert_into(gitea_tokens::table) - .values(&tok) - .get_result(&*conn) - .expect("able to insert token"); + let _: models::GiteaToken = diesel::insert_into(gitea_tokens::table) + .values(&tok) + .get_result(&*conn) + .expect("able to insert token"); - u - } else { - tracing::info!("{} {:?} logged in", u[0].id, u[0].salutation); - u[0].clone() - }, + u + } else { + tracing::info!("{} {:?} logged in", u[0].id, u[0].salutation); + u[0].clone() + } + } Err(why) => { tracing::error!("error reading from database: {}", why); todo!("error response") } }; + let tok: models::Token = diesel::insert_into(tokens::table) + .values(&models::NewToken { + user_id: user.id.clone(), + }) + .get_result(&*conn) + .expect("create token information"); + tracing::info!("created new token for {} with id {}", user.id, tok.id); + + let tok = jwt::make(user.id, tok.id).expect("to sign JWT"); // Set a private cookie with the access token cookies.add_private( - Cookie::build("token", token.access_token().to_string()) + Cookie::build("token", tok.clone()) .same_site(SameSite::Lax) .finish(), ); - Redirect::to("/") + tok } fn main() -> Result<()> { color_eyre::install()?; tracing_subscriber::fmt::init(); + tracing::trace!("JWT secret: {:?}", *jwt::SECRET); rocket::ignite() .attach(OAuth2::::fairing("gitea")) .attach(MainDatabase::fairing())