Add conditional UIAA flows for SSO and password. (#314)
Signed-off-by: Jason Volk <jason@zemos.net>
This commit is contained in:
@@ -5,7 +5,13 @@ use ruma::{
|
|||||||
client::uiaa::{AuthData, AuthFlow, AuthType, Jwt, UiaaInfo},
|
client::uiaa::{AuthData, AuthFlow, AuthType, Jwt, UiaaInfo},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use tuwunel_core::{Err, Error, Result, err, is_equal_to, utils};
|
use tuwunel_core::{
|
||||||
|
Err, Error, Result, err, utils,
|
||||||
|
utils::{
|
||||||
|
OptionExt,
|
||||||
|
future::{OptionFutureExt, TryExtExt},
|
||||||
|
},
|
||||||
|
};
|
||||||
use tuwunel_service::{Services, uiaa::SESSION_ID_LENGTH};
|
use tuwunel_service::{Services, uiaa::SESSION_ID_LENGTH};
|
||||||
|
|
||||||
use crate::{Ruma, client::jwt};
|
use crate::{Ruma, client::jwt};
|
||||||
@@ -15,14 +21,46 @@ where
|
|||||||
T: IncomingRequest + Send + Sync,
|
T: IncomingRequest + Send + Sync,
|
||||||
{
|
{
|
||||||
let sender_device = body.sender_device()?;
|
let sender_device = body.sender_device()?;
|
||||||
|
let sender_user = body.sender_user.as_deref();
|
||||||
|
|
||||||
let flows = [
|
let password_flow = [AuthType::Password];
|
||||||
AuthFlow::new([AuthType::Password].into()),
|
let has_password = sender_user
|
||||||
AuthFlow::new([AuthType::Jwt].into()),
|
.map_async(|sender_user| {
|
||||||
];
|
services
|
||||||
|
.users
|
||||||
|
.has_password(sender_user)
|
||||||
|
.unwrap_or(false)
|
||||||
|
})
|
||||||
|
.unwrap_or(false)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
//TODO: UIAA for SSO.
|
||||||
|
let sso_flow = [AuthType::Sso];
|
||||||
|
let has_sso = false;
|
||||||
|
let _has_sso = sender_user
|
||||||
|
.map_async(|sender_user| {
|
||||||
|
services
|
||||||
|
.oauth
|
||||||
|
.sessions
|
||||||
|
.exists_for_user(sender_user)
|
||||||
|
})
|
||||||
|
.unwrap_or(false)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
//NOTE: Not implemented as a fallback/web for now.
|
||||||
|
let has_jwt = false;
|
||||||
|
let jwt_flow = [AuthType::Jwt];
|
||||||
|
|
||||||
let mut uiaainfo = UiaaInfo {
|
let mut uiaainfo = UiaaInfo {
|
||||||
flows: flows.into(),
|
flows: has_password
|
||||||
|
.then_some(password_flow)
|
||||||
|
.into_iter()
|
||||||
|
.chain(has_sso.then_some(sso_flow))
|
||||||
|
.chain(has_jwt.then_some(jwt_flow))
|
||||||
|
.map(Vec::from)
|
||||||
|
.map(AuthFlow::new)
|
||||||
|
.collect(),
|
||||||
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -70,16 +108,6 @@ where
|
|||||||
.as_deref()
|
.as_deref()
|
||||||
.ok_or_else(|| err!(Request(MissingToken("Missing access token."))))?;
|
.ok_or_else(|| err!(Request(MissingToken("Missing access token."))))?;
|
||||||
|
|
||||||
// Skip UIAA for SSO/OIDC users.
|
|
||||||
if services
|
|
||||||
.users
|
|
||||||
.origin(sender_user)
|
|
||||||
.await
|
|
||||||
.is_ok_and(is_equal_to!("sso"))
|
|
||||||
{
|
|
||||||
return Ok(sender_user.to_owned());
|
|
||||||
}
|
|
||||||
|
|
||||||
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
|
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
|
||||||
services
|
services
|
||||||
.uiaa
|
.uiaa
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ use tuwunel_core::{
|
|||||||
};
|
};
|
||||||
use tuwunel_database::{Deserialized, Json, Map};
|
use tuwunel_database::{Deserialized, Json, Map};
|
||||||
|
|
||||||
|
use crate::users::PASSWORD_SENTINEL;
|
||||||
|
|
||||||
pub struct Service {
|
pub struct Service {
|
||||||
userdevicesessionid_uiaarequest: RwLock<RequestMap>,
|
userdevicesessionid_uiaarequest: RwLock<RequestMap>,
|
||||||
db: Data,
|
db: Data,
|
||||||
@@ -107,9 +109,11 @@ pub async fn try_auth(
|
|||||||
// Check if password is correct
|
// Check if password is correct
|
||||||
let user_id = user_id_from_username;
|
let user_id = user_id_from_username;
|
||||||
let mut password_verified = false;
|
let mut password_verified = false;
|
||||||
|
let mut password_sentinel = false;
|
||||||
|
|
||||||
// First try local password hash verification
|
// First try local password hash verification
|
||||||
if let Ok(hash) = self.services.users.password_hash(&user_id).await {
|
if let Ok(hash) = self.services.users.password_hash(&user_id).await {
|
||||||
|
password_sentinel = hash == PASSWORD_SENTINEL;
|
||||||
password_verified = hash::verify_password(password, &hash).is_ok();
|
password_verified = hash::verify_password(password, &hash).is_ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,6 +134,19 @@ pub async fn try_auth(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For SSO users that have never set a password, allow.
|
||||||
|
if !password_verified
|
||||||
|
&& password_sentinel
|
||||||
|
&& self
|
||||||
|
.services
|
||||||
|
.oauth
|
||||||
|
.sessions
|
||||||
|
.exists_for_user(&user_id)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
return Ok((true, uiaainfo));
|
||||||
|
}
|
||||||
|
|
||||||
if !password_verified {
|
if !password_verified {
|
||||||
uiaainfo.auth_error = Some(StandardErrorBody {
|
uiaainfo.auth_error = Some(StandardErrorBody {
|
||||||
kind: ErrorKind::forbidden(),
|
kind: ErrorKind::forbidden(),
|
||||||
|
|||||||
Reference in New Issue
Block a user