From d49911c5e01ca1e1a6d14533bcf6ae47a146fe49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20K=C3=B6sters?= Date: Sun, 7 Feb 2021 17:38:45 +0100 Subject: [PATCH] Add 'm.login.token' authentication --- Cargo.lock | 86 ++++++++++++++++++++++++++++++++++-- Cargo.toml | 2 + src/appservice_server.rs | 2 +- src/client_server/session.rs | 73 +++++++++++++++++++++--------- src/database.rs | 1 + src/database/globals.rs | 13 +++++- src/database/sending.rs | 2 +- 7 files changed, 150 insertions(+), 29 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b5be6aa..78ff405 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -182,6 +182,19 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "time 0.1.44", + "winapi 0.3.9", +] + [[package]] name = "color_quant" version = "1.1.0" @@ -197,6 +210,7 @@ dependencies = [ "http", "image", "js_int", + "jsonwebtoken", "log", "rand", "regex", @@ -243,7 +257,7 @@ version = "0.15.0-dev" source = "git+https://github.com/SergioBenitez/cookie-rs.git?rev=1c3ca83#1c3ca838543b60a4448d279dc4b903cc7a2bc22a" dependencies = [ "percent-encoding", - "time", + "time 0.2.23", "version_check", ] @@ -578,7 +592,7 @@ checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6" dependencies = [ "cfg-if 0.1.10", "libc", - "wasi", + "wasi 0.9.0+wasi-snapshot-preview1", ] [[package]] @@ -850,6 +864,20 @@ dependencies = [ "serde", ] +[[package]] +name = "jsonwebtoken" +version = "7.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afabcc15e437a6484fc4f12d0fd63068fe457bf93f1c148d3d9649c60b103f32" +dependencies = [ + "base64 0.12.3", + "pem", + "ring", + "serde", + "serde_json", + "simple_asn1", +] + [[package]] name = "kernel32-sys" version = "0.2.2" @@ -1044,6 +1072,17 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "num-bigint" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-integer" version = "0.1.44" @@ -1204,6 +1243,17 @@ dependencies = [ "syn", ] +[[package]] +name = "pem" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c220d01f863d13d96ca82359d1e81e64a7c6bf0637bcde7b2349630addf0c6" +dependencies = [ + "base64 0.13.0", + "once_cell", + "regex", +] + [[package]] name = "percent-encoding" version = "2.1.0" @@ -1538,7 +1588,7 @@ dependencies = [ "rocket_http", "serde", "state", - "time", + "time 0.2.23", "tokio", "ubyte", "version_check", @@ -1575,7 +1625,7 @@ dependencies = [ "ref-cast", "smallvec", "state", - "time", + "time 0.2.23", "tokio", "tokio-rustls", "uncased", @@ -1969,6 +2019,17 @@ dependencies = [ "libc", ] +[[package]] +name = "simple_asn1" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692ca13de57ce0613a363c8c2f1de925adebc81b04c923ac60c5488bb44abe4b" +dependencies = [ + "chrono", + "num-bigint", + "num-traits", +] + [[package]] name = "slab" version = "0.4.2" @@ -2168,6 +2229,17 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi 0.3.9", +] + [[package]] name = "time" version = "0.2.23" @@ -2498,6 +2570,12 @@ version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + [[package]] name = "wasm-bindgen" version = "0.2.69" diff --git a/Cargo.toml b/Cargo.toml index 56a04e5..f7fbdc5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,6 +63,8 @@ ring = "0.16.19" trust-dns-resolver = "0.19.6" # Used to find matching events for appservices regex = "1.4.2" +# jwt jsonwebtokens +jsonwebtoken = "7.2.0" [features] default = ["conduit_bin"] diff --git a/src/appservice_server.rs b/src/appservice_server.rs index ec504b5..986909b 100644 --- a/src/appservice_server.rs +++ b/src/appservice_server.rs @@ -1,6 +1,6 @@ use crate::{utils, Error, Result}; use http::header::{HeaderValue, CONTENT_TYPE}; -use log::{info, warn}; +use log::warn; use ruma::api::OutgoingRequest; use std::{ convert::{TryFrom, TryInto}, diff --git a/src/client_server/session.rs b/src/client_server/session.rs index 48fbea2..1b2583c 100644 --- a/src/client_server/session.rs +++ b/src/client_server/session.rs @@ -8,6 +8,13 @@ use ruma::{ }, UserId, }; +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +struct Claims { + sub: String, + exp: usize, +} #[cfg(feature = "conduit_bin")] use rocket::{get, post}; @@ -40,40 +47,62 @@ pub async fn login_route( body: Ruma>, ) -> ConduitResult { // Validate login method - let user_id = - // TODO: Other login methods - if let (login::IncomingUserInfo::MatrixId(username), login::IncomingLoginInfo::Password { password }) = - (&body.user, &body.login_info) - { - let user_id = UserId::parse_with_server_name(username.to_string(), db.globals.server_name()) - .map_err(|_| Error::BadRequest( - ErrorKind::InvalidUsername, - "Username is invalid." - ))?; - let hash = db.users.password_hash(&user_id)? - .ok_or(Error::BadRequest( - ErrorKind::Forbidden, - "Wrong username or password." - ))?; + // TODO: Other login methods + let user_id = match &body.login_info { + login::IncomingLoginInfo::Password { password } => { + let username = if let login::IncomingUserInfo::MatrixId(matrix_id) = &body.user { + matrix_id + } else { + return Err(Error::BadRequest(ErrorKind::Forbidden, "Bad login type.")); + }; + let user_id = + UserId::parse_with_server_name(username.to_owned(), db.globals.server_name()) + .map_err(|_| { + Error::BadRequest(ErrorKind::InvalidUsername, "Username is invalid.") + })?; + let hash = db.users.password_hash(&user_id)?.ok_or(Error::BadRequest( + ErrorKind::Forbidden, + "Wrong username or password.", + ))?; if hash.is_empty() { return Err(Error::BadRequest( ErrorKind::UserDeactivated, - "The user has been deactivated" + "The user has been deactivated", )); } - let hash_matches = - argon2::verify_encoded(&hash, password.as_bytes()).unwrap_or(false); + let hash_matches = argon2::verify_encoded(&hash, password.as_bytes()).unwrap_or(false); if !hash_matches { - return Err(Error::BadRequest(ErrorKind::Forbidden, "Wrong username or password.")); + return Err(Error::BadRequest( + ErrorKind::Forbidden, + "Wrong username or password.", + )); } user_id - } else { - return Err(Error::BadRequest(ErrorKind::Forbidden, "Bad login type.")); - }; + } + login::IncomingLoginInfo::Token { token } => { + if let Some(jwt_decoding_key) = db.globals.jwt_decoding_key() { + let token = jsonwebtoken::decode::( + &token, + &jwt_decoding_key, + &jsonwebtoken::Validation::default(), + ) + .map_err(|_| Error::BadRequest(ErrorKind::InvalidUsername, "Token is invalid."))?; + let username = token.claims.sub; + UserId::parse_with_server_name(username, db.globals.server_name()).map_err( + |_| Error::BadRequest(ErrorKind::InvalidUsername, "Username is invalid."), + )? + } else { + return Err(Error::BadRequest( + ErrorKind::Unknown, + "Token login is not supported (server has no jwt decoding key).", + )); + } + } + }; // Generate new device id if the user didn't specify one let device_id = body diff --git a/src/database.rs b/src/database.rs index 9fce293..8fcffd9 100644 --- a/src/database.rs +++ b/src/database.rs @@ -38,6 +38,7 @@ pub struct Config { allow_encryption: bool, #[serde(default = "false_fn")] allow_federation: bool, + jwt_secret: Option, } fn false_fn() -> bool { diff --git a/src/database/globals.rs b/src/database/globals.rs index 3e24d82..ccd6284 100644 --- a/src/database/globals.rs +++ b/src/database/globals.rs @@ -11,12 +11,13 @@ pub const COUNTER: &str = "c"; #[derive(Clone)] pub struct Globals { + pub actual_destination_cache: Arc, (String, Option)>>>, // actual_destination, host pub(super) globals: sled::Tree, config: Config, keypair: Arc, reqwest_client: reqwest::Client, - pub actual_destination_cache: Arc, (String, Option)>>>, // actual_destination, host dns_resolver: TokioAsyncResolver, + jwt_decoding_key: Option>, } impl Globals { @@ -62,6 +63,11 @@ impl Globals { .build() .unwrap(); + let jwt_decoding_key = config + .jwt_secret + .as_ref() + .map(|secret| jsonwebtoken::DecodingKey::from_secret(secret.as_bytes()).into_static()); + Ok(Self { globals, config, @@ -73,6 +79,7 @@ impl Globals { Error::bad_config("Failed to set up trust dns resolver with system config.") })?, actual_destination_cache: Arc::new(RwLock::new(HashMap::new())), + jwt_decoding_key, }) } @@ -126,4 +133,8 @@ impl Globals { pub fn dns_resolver(&self) -> &TokioAsyncResolver { &self.dns_resolver } + + pub fn jwt_decoding_key(&self) -> Option<&jsonwebtoken::DecodingKey<'_>> { + self.jwt_decoding_key.as_ref() + } } diff --git a/src/database/sending.rs b/src/database/sending.rs index 0a66f73..fd32793 100644 --- a/src/database/sending.rs +++ b/src/database/sending.rs @@ -8,7 +8,7 @@ use std::{ use crate::{appservice_server, server_server, utils, Error, PduEvent, Result}; use federation::transactions::send_transaction_message; -use log::{error, info}; +use log::info; use rocket::futures::stream::{FuturesUnordered, StreamExt}; use ruma::{ api::{appservice, federation, OutgoingRequest},