Add 'm.login.token' authentication

This commit is contained in:
Timo Kösters 2021-02-07 17:38:45 +01:00
parent 3588dcd6d0
commit d49911c5e0
No known key found for this signature in database
GPG key ID: 24DA7517711A2BA4
7 changed files with 150 additions and 29 deletions

View file

@ -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},

View file

@ -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<login::Request<'_>>,
) -> ConduitResult<login::Response> {
// 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::<Claims>(
&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

View file

@ -38,6 +38,7 @@ pub struct Config {
allow_encryption: bool,
#[serde(default = "false_fn")]
allow_federation: bool,
jwt_secret: Option<String>,
}
fn false_fn() -> bool {

View file

@ -11,12 +11,13 @@ pub const COUNTER: &str = "c";
#[derive(Clone)]
pub struct Globals {
pub actual_destination_cache: Arc<RwLock<HashMap<Box<ServerName>, (String, Option<String>)>>>, // actual_destination, host
pub(super) globals: sled::Tree,
config: Config,
keypair: Arc<ruma::signatures::Ed25519KeyPair>,
reqwest_client: reqwest::Client,
pub actual_destination_cache: Arc<RwLock<HashMap<Box<ServerName>, (String, Option<String>)>>>, // actual_destination, host
dns_resolver: TokioAsyncResolver,
jwt_decoding_key: Option<jsonwebtoken::DecodingKey<'static>>,
}
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()
}
}

View file

@ -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},