1
0
Fork 0
mirror of https://gitlab.com/famedly/conduit.git synced 2025-06-27 16:35:59 +00:00

feat: base support

This commit is contained in:
avdb13 2024-07-15 12:24:06 +02:00
parent 139588b64c
commit 67c23d6dd4
9 changed files with 148 additions and 178 deletions

View file

@ -35,7 +35,7 @@ axum = { version = "0.7", default-features = false, features = [
"json", "json",
"matched-path", "matched-path",
], optional = true } ], optional = true }
axum-extra = { version = "0.9", features = ["typed-header", "cookie"] } axum-extra = { version = "0.9", features = ["cookie", "typed-header"] }
axum-server = { version = "0.6", features = ["tls-rustls"] } axum-server = { version = "0.6", features = ["tls-rustls"] }
tower = { version = "0.4.13", features = ["util"] } tower = { version = "0.4.13", features = ["util"] }
tower-http = { version = "0.5", features = [ tower-http = { version = "0.5", features = [
@ -49,15 +49,6 @@ tower-http = { version = "0.5", features = [
"trace", "trace",
"util", "util",
] } ] }
# tower-http = { version = "0.5", features = [
# "add-extension",
# "cors",
# "decompression-full",
# "sensitive-headers",
# "set-header",
# "trace",
# "util",
# ] }
tower-service = "0.3" tower-service = "0.3"
# Async runtime and utilities # Async runtime and utilities
@ -153,11 +144,6 @@ figment = { version = "0.10.8", features = ["env", "toml"] }
# Validating urls in config # Validating urls in config
url = { version = "2", features = ["serde"] } url = { version = "2", features = ["serde"] }
# HTML
mas-oidc-client = { git = "https://github.com/matrix-org/matrix-authentication-service", default-features = false }
mas-http = { git = "https://github.com/matrix-org/matrix-authentication-service", features = ["client"] }
maud = { version = "0.26.0", default-features = false, features = ["axum"] }
async-trait = "0.1.68" async-trait = "0.1.68"
tikv-jemallocator = { version = "0.5.0", features = [ tikv-jemallocator = { version = "0.5.0", features = [
"unprefixed_malloc_on_supported_platforms", "unprefixed_malloc_on_supported_platforms",
@ -190,11 +176,21 @@ optional = true
package = "rust-rocksdb" package = "rust-rocksdb"
version = "0.25" version = "0.25"
[dependencies.mas-http]
features = ["client"]
git = "https://github.com/matrix-org/matrix-authentication-service"
rev = "fbc360d1a94ef2ebf63d979bb403228a700f43c8"
[dependencies.mas-oidc-client]
features = []
git = "https://github.com/matrix-org/matrix-authentication-service"
rev = "fbc360d1a94ef2ebf63d979bb403228a700f43c8"
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
nix = { version = "0.28", features = ["resource"] } nix = { version = "0.28", features = ["resource"] }
[features] [features]
default = ["backend_sqlite", "conduit_bin"] default = ["backend_rocksdb", "backend_sqlite", "conduit_bin", "systemd"]
#backend_sled = ["sled"] #backend_sled = ["sled"]
backend_persy = ["parking_lot", "persy"] backend_persy = ["parking_lot", "persy"]
backend_sqlite = ["sqlite"] backend_sqlite = ["sqlite"]

View file

@ -124,25 +124,6 @@ Identity providers using OAuth such as Github are not supported yet.
| `name` | `string` | The name displayed on fallback pages. | `issuer` | | `name` | `string` | The name displayed on fallback pages. | `issuer` |
| `icon` | `Url` OR `MxcUri` | The icon displayed on fallback pages. | N/A | | `icon` | `Url` OR `MxcUri` | The icon displayed on fallback pages. | N/A |
| `scopes` | `array` | The scopes used to obtain extra claims which can be used for templates. | `["openid"]` | | `scopes` | `array` | The scopes used to obtain extra claims which can be used for templates. | `["openid"]` |
<!-- | `pkce` | `bool` | | `true` | -->
<!-- | `backchannel_logout` | `bool` | | `true` | -->
<!-- | `unique_claim` | `string` | The key of the claim, used to uniquely identify users | `"sub"` | <!-1- TODO: claim_correlation? -1-> -->
<!-- | `credentials`* | `table` | See [Client Credentials](#client-credentials) | N/A | -->
| `client_id`* | `string` | The provider-supplied, unique ID for the client. | N/A | | `client_id`* | `string` | The provider-supplied, unique ID for the client. | N/A |
| `client_secret`* | `string` | The provider-supplied, unique ID for the client. | N/A | | `client_secret`* | `string` | The provider-supplied, unique ID for the client. | N/A |
| `authentication_method`* | `"basic" | "post"` | The method used for client authentication. | N/A | | `authentication_method`* | `"basic" OR "post"` | The method used for client authentication. | N/A |
<!-- TODO -->
<!-- #### Example -->
<!-- ```toml -->
<!-- [global.sso.keycloak] -->
<!-- name = "A Mysterious KeyCloak Server" -->
<!-- icon = "mxc://matrix.org/tuKmXlmbHzYPFmdHafbZHOWj" -->
<!-- issuer = "https://oidc.conduit.rs:8443/realms/dev_team_realm" -->
<!-- scopes = ["openid", "profile"] -->
<!-- ``` -->
<!-- localpart = "userinfo.preferred_username" -->
<!-- displayname = "id_token.name" -->
<!-- avatar_url = "userinfo.picture" -->
<!-- email = "userinfo.email" -->
<!-- msisdn = "userinfo.phone_number" -->

View file

@ -322,8 +322,6 @@ pub async fn change_password_route(
.ok_or_else(|| Error::BadRequest(ErrorKind::MissingToken, "Missing access token."))?; .ok_or_else(|| Error::BadRequest(ErrorKind::MissingToken, "Missing access token."))?;
let sender_device = body.sender_device.as_ref().expect("user is authenticated"); let sender_device = body.sender_device.as_ref().expect("user is authenticated");
// if services().users.password_hash(sender_user)? == Some("");
let mut uiaainfo = UiaaInfo { let mut uiaainfo = UiaaInfo {
flows: vec![AuthFlow { flows: vec![AuthFlow {
stages: vec![AuthType::Password], stages: vec![AuthType::Password],

View file

@ -100,6 +100,12 @@ pub async fn upload_signing_keys_route(
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let sender_device = body.sender_device.as_ref().expect("user is authenticated"); let sender_device = body.sender_device.as_ref().expect("user is authenticated");
let master_key = services()
.users
.get_master_key(Some(sender_user), sender_user, &|other| {
sender_user == other
})?;
// UIAA // UIAA
let mut uiaainfo = UiaaInfo { let mut uiaainfo = UiaaInfo {
flows: vec![AuthFlow { flows: vec![AuthFlow {
@ -111,11 +117,15 @@ pub async fn upload_signing_keys_route(
auth_error: None, auth_error: None,
}; };
let master_key = services() if let (Some(master_key), None) = (&body.master_key, master_key) {
.users services().users.add_cross_signing_keys(
.get_master_key(None, sender_user, &|user_id| user_id == sender_user)?; sender_user,
master_key,
if let Some(auth) = &body.auth { &body.self_signing_key,
&body.user_signing_key,
true,
)?;
} else if let Some(auth) = &body.auth {
let (worked, uiaainfo) = let (worked, uiaainfo) =
services() services()
.uiaa .uiaa
@ -130,20 +140,10 @@ pub async fn upload_signing_keys_route(
.uiaa .uiaa
.create(sender_user, sender_device, &uiaainfo, &json)?; .create(sender_user, sender_device, &uiaainfo, &json)?;
return Err(Error::Uiaa(uiaainfo)); return Err(Error::Uiaa(uiaainfo));
} else if master_key.is_some() { } else {
return Err(Error::BadRequest(ErrorKind::NotJson, "Not json.")); return Err(Error::BadRequest(ErrorKind::NotJson, "Not json."));
} }
if let Some(master_key) = &body.master_key {
services().users.add_cross_signing_keys(
sender_user,
master_key,
&body.self_signing_key,
&body.user_signing_key,
true, // notify so that other users see the new keys
)?;
}
Ok(upload_signing_keys::v3::Response {}) Ok(upload_signing_keys::v3::Response {})
} }

View file

@ -113,20 +113,24 @@ pub async fn login_route(body: Ruma<login::v3::Request>) -> Result<login::v3::Re
login::v3::LoginInfo::Token(login::v3::Token { token }) => { login::v3::LoginInfo::Token(login::v3::Token { token }) => {
match ( match (
services().globals.jwt_decoding_key(), services().globals.jwt_decoding_key(),
services().sso.login_type().next().is_some(), services().globals.config.idps.is_empty(),
) { ) {
(_, false) => { (_, false) => {
let mut validation = Validation::new(Algorithm::HS256); let mut v = Validation::new(Algorithm::HS256);
validation.validate_nbf = false;
validation.set_required_spec_claims(&["sub", "exp", "aud", "iss"]); v.set_required_spec_claims(&["sub", "exp", "aud", "iss"]);
v.validate_aud = false;
v.validate_nbf = false;
services() services()
.globals .globals
.validate_claims::<LoginToken>(token, Some(validation)) .validate_claims::<LoginToken>(token, Some(&v))
.as_ref()
.map(LoginToken::audience) .map(LoginToken::audience)
.map(ToOwned::to_owned) .map_err(|e| {
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid token."))? tracing::warn!("Invalid token: {}", e);
Error::BadRequest(ErrorKind::InvalidParam, "Invalid token.")
})?
} }
(Some(jwt_decoding_key), _) => { (Some(jwt_decoding_key), _) => {
let token = jsonwebtoken::decode::<Claims>( let token = jsonwebtoken::decode::<Claims>(

View file

@ -7,15 +7,7 @@ use crate::{
}, },
services, utils, Error, Result, Ruma, services, utils, Error, Result, Ruma,
}; };
use axum::{ use futures_util::TryFutureExt;
response::{AppendHeaders, IntoResponse, Redirect},
RequestExt,
};
use axum_extra::{
headers::{self},
TypedHeader,
};
use http::header::{self};
use mas_oidc_client::{ use mas_oidc_client::{
requests::{ requests::{
authorization_code::{self, AuthorizationRequestData}, authorization_code::{self, AuthorizationRequestData},
@ -24,7 +16,6 @@ use mas_oidc_client::{
}, },
types::{ types::{
client_credentials::ClientCredentials, client_credentials::ClientCredentials,
errors::ClientError,
iana::jose::JsonWebSignatureAlg, iana::jose::JsonWebSignatureAlg,
requests::{AccessTokenResponse, AuthorizationResponse}, requests::{AccessTokenResponse, AuthorizationResponse},
}, },
@ -33,6 +24,7 @@ use rand::{rngs::StdRng, Rng, SeedableRng};
use ruma::{ use ruma::{
api::client::{ api::client::{
error::ErrorKind, error::ErrorKind,
media::create_content,
session::{sso_login, sso_login_with_provider}, session::{sso_login, sso_login_with_provider},
}, },
events::{room::message::RoomMessageEventContent, GlobalAccountDataEventType}, events::{room::message::RoomMessageEventContent, GlobalAccountDataEventType},
@ -46,7 +38,7 @@ pub const CALLBACK_PATH: &str = "/_matrix/client/unstable/conduit/callback";
/// # `GET /_matrix/client/v3/login/sso/redirect` /// # `GET /_matrix/client/v3/login/sso/redirect`
/// ///
/// Redirect the user to the SSO interface. /// Redirect the user to the SSO interfa.
/// TODO: this should be removed once Ruma supports trailing slashes. /// TODO: this should be removed once Ruma supports trailing slashes.
pub async fn get_sso_redirect_route( pub async fn get_sso_redirect_route(
Ruma { Ruma {
@ -148,37 +140,25 @@ pub async fn get_sso_redirect_with_provider_route(
}) })
} }
async fn handle_callback_helper(req: axum::extract::Request) -> Result<axum::response::Response> { /// # `GET /_conduit/client/sso/callback`
let query = req.uri().query().ok_or_else(|| { ///
Error::BadRequest(ErrorKind::MissingParam, "Empty authorization callback.") /// Validate the authorization response received from the identity provider.
})?; /// On success, generate a login token, add it to `redirectUrl` as a query and perform the redirect.
/// If this is the first login, register the user, possibly interactively through a fallback page.
let AuthorizationResponse { pub async fn handle_callback_route(
code, body: Ruma<sso_callback::Request>,
access_token: _, ) -> Result<sso_login_with_provider::v3::Response> {
token_type: _, let sso_callback::Request {
id_token: _, response:
expires_in: _, AuthorizationResponse {
} = serde_html_form::from_str(query).map_err(|_| { code,
serde_html_form::from_str(query) access_token: _,
.map(ClientError::into) token_type: _,
.unwrap_or_else(|_| { id_token: _,
error!("Failed to deserialize authorization callback: {}", query); expires_in: _,
},
Error::BadRequest( cookie,
ErrorKind::Unknown, } = body.body;
"Failed to deserialize authorization callback.",
)
})
})?;
let Ok(Some(cookie)): Result<Option<TypedHeader<headers::Cookie>>, _> = req.extract().await
else {
return Err(Error::BadRequest(
ErrorKind::MissingParam,
"Missing session cookie.",
));
};
let ValidationData { let ValidationData {
provider, provider,
@ -186,12 +166,7 @@ async fn handle_callback_helper(req: axum::extract::Request) -> Result<axum::res
inner: validation_data, inner: validation_data,
} = services() } = services()
.globals .globals
.validate_claims( .validate_claims(&cookie, None)
cookie.get(SSO_SESSION_COOKIE).ok_or_else(|| {
Error::BadRequest(ErrorKind::MissingParam, "Missing value for session cookie.")
})?,
None,
)
.map_err(|_| { .map_err(|_| {
Error::BadRequest(ErrorKind::InvalidParam, "Invalid value for session cookie.") Error::BadRequest(ErrorKind::InvalidParam, "Invalid value for session cookie.")
})?; })?;
@ -224,7 +199,7 @@ async fn handle_callback_helper(req: axum::extract::Request) -> Result<axum::res
let ref jwks = jose::fetch_jwks(services().sso.service(), provider.metadata.jwks_uri()) let ref jwks = jose::fetch_jwks(services().sso.service(), provider.metadata.jwks_uri())
.await .await
.map_err(|_| Error::bad_config("Failed to fetch signing keys for token endpoint."))?; .map_err(|_| Error::bad_config("Failed to fetch signing keys for token endpoint."))?;
let jwt_verification_data = Some(JwtVerificationData { let idt_verification_data = Some(JwtVerificationData {
jwks, jwks,
issuer: &provider.config.issuer, issuer: &provider.config.issuer,
client_id: &provider.config.client_id, client_id: &provider.config.client_id,
@ -247,7 +222,7 @@ async fn handle_callback_helper(req: axum::extract::Request) -> Result<axum::res
provider.metadata.token_endpoint(), provider.metadata.token_endpoint(),
code.unwrap_or_default(), code.unwrap_or_default(),
validation_data, validation_data,
jwt_verification_data, idt_verification_data,
SystemTime::now().into(), SystemTime::now().into(),
&mut StdRng::from_entropy(), &mut StdRng::from_entropy(),
) )
@ -257,21 +232,28 @@ async fn handle_callback_helper(req: axum::extract::Request) -> Result<axum::res
unreachable!("ID token should never be empty") unreachable!("ID token should never be empty")
}; };
let mut _userinfo = HashMap::default(); let mut userinfo = HashMap::default();
if let Some(endpoint) = provider.metadata.userinfo_endpoint.as_ref() { if let Some(endpoint) = provider.metadata.userinfo_endpoint.as_ref() {
_userinfo = userinfo::fetch_userinfo( userinfo = userinfo::fetch_userinfo(
services().sso.service(), services().sso.service(),
endpoint, endpoint,
&access_token, &access_token,
jwt_verification_data, None,
&id_token, &id_token,
) )
.await .await
.map_err(|_| Error::bad_config("Failed to fetch claims for userinfo endpoint."))?; .map_err(|e| {
tracing::error!("Failed to fetch claims for userinfo endpoint: {:?}", e);
Error::bad_config("Failed to fetch claims for userinfo endpoint.")
})?;
} }
let (_, id_token) = id_token.into_parts(); let (_, id_token) = id_token.into_parts();
info!("userinfo: {:?}", &userinfo);
info!("id_token: {:?}", &id_token);
let subject = match id_token.get(SUBJECT_CLAIM_KEY) { let subject = match id_token.get(SUBJECT_CLAIM_KEY) {
Some(Value::String(s)) => s.to_owned(), Some(Value::String(s)) => s.to_owned(),
Some(Value::Number(n)) => n.to_string(), Some(Value::Number(n)) => n.to_string(),
@ -299,8 +281,13 @@ async fn handle_callback_helper(req: axum::extract::Request) -> Result<axum::res
let user_id = loop { let user_id = loop {
match UserId::parse_with_server_name(&*localpart, services().globals.server_name()) match UserId::parse_with_server_name(&*localpart, services().globals.server_name())
{ .map(|user_id| {
Ok(user_id) if services().users.exists(&user_id)? => break user_id, (
user_id.clone(),
services().users.exists(&user_id).unwrap_or(true),
)
}) {
Ok((user_id, false)) => break user_id,
_ => { _ => {
let n: u8 = rand::thread_rng().gen(); let n: u8 = rand::thread_rng().gen();
@ -310,12 +297,15 @@ async fn handle_callback_helper(req: axum::extract::Request) -> Result<axum::res
}; };
services().users.set_placeholder_password(&user_id)?; services().users.set_placeholder_password(&user_id)?;
let mut displayname = id_token let displayname = id_token
.get("preferred_username") .get("preferred_username")
.or(id_token.get("nickname")) .or(id_token.get("nickname"));
let mut displayname = displayname
.as_deref() .as_deref()
.map(Value::to_string) .map(Value::as_str)
.unwrap_or(user_id.localpart().to_owned()); .flatten()
.unwrap_or(user_id.localpart())
.to_owned();
// If enabled append lightning bolt to display name (default true) // If enabled append lightning bolt to display name (default true)
if services().globals.enable_lightning_bolt() { if services().globals.enable_lightning_bolt() {
@ -326,6 +316,34 @@ async fn handle_callback_helper(req: axum::extract::Request) -> Result<axum::res
.users .users
.set_displayname(&user_id, Some(displayname.clone()))?; .set_displayname(&user_id, Some(displayname.clone()))?;
if let Some(Value::String(url)) = userinfo.get("picture").or(id_token.get("picture")) {
let req = services()
.globals
.default_client()
.get(url)
.send()
.and_then(reqwest::Response::bytes);
if let Ok(file) = req.await {
let _ = crate::api::client_server::create_content_route(Ruma {
body: create_content::v3::Request::new(file.to_vec()),
sender_user: None,
sender_device: None,
sender_servername: None,
json_body: None,
appservice_info: None,
})
.await
.and_then(|res| {
tracing::info!("successfully imported avatar for {}", &user_id);
services()
.users
.set_avatar_url(&user_id, Some(res.content_uri))
});
}
}
// Initial account data // Initial account data
services().account_data.update( services().account_data.update(
None, None,
@ -355,7 +373,7 @@ async fn handle_callback_helper(req: axum::extract::Request) -> Result<axum::res
{ {
services() services()
.admin .admin
.make_user_admin(&user_id, displayname) .make_user_admin(&user_id, displayname.to_owned())
.await?; .await?;
warn!("Granting {} admin privileges as the first user", user_id); warn!("Granting {} admin privileges as the first user", user_id);
@ -376,26 +394,10 @@ async fn handle_callback_helper(req: axum::extract::Request) -> Result<axum::res
.query_pairs_mut() .query_pairs_mut()
.append_pair("loginToken", &signed); .append_pair("loginToken", &signed);
Ok(( Ok(sso_login_with_provider::v3::Response {
AppendHeaders(vec![( location: redirect_url.to_string(),
header::SET_COOKIE, cookie: Some(utils::build_cookie(SSO_SESSION_COOKIE, "", CALLBACK_PATH, None).to_string()),
utils::build_cookie(SSO_SESSION_COOKIE, "", CALLBACK_PATH, None).to_string(), })
)]),
Redirect::temporary(redirect_url.as_str()),
)
.into_response())
}
/// # `GET /_conduit/client/sso/callback`
///
/// Validate the authorization response received from the identity provider.
/// On success, generate a login token, add it to `redirectUrl` as a query and perform the redirect.
/// If this is the first login, register the user, possibly interactively through a fallback page.
pub async fn handle_callback_route(req: axum::extract::Request) -> axum::response::Response {
match handle_callback_helper(req).await {
Ok(res) => res,
Err(e) => e.into_response(),
}
} }
mod sso_callback { mod sso_callback {
@ -404,9 +406,9 @@ mod sso_callback {
use mas_oidc_client::types::requests::AuthorizationResponse; use mas_oidc_client::types::requests::AuthorizationResponse;
use ruma::{ use ruma::{
api::{ api::{
client::Error, client::{session::sso_login_with_provider, Error},
error::{FromHttpRequestError, HeaderDeserializationError}, error::{FromHttpRequestError, HeaderDeserializationError},
IncomingRequest, Metadata, OutgoingResponse, IncomingRequest, Metadata,
}, },
metadata, metadata,
}; };
@ -423,15 +425,13 @@ mod sso_callback {
}; };
pub struct Request { pub struct Request {
response: AuthorizationResponse, pub response: AuthorizationResponse,
cookie: String, pub cookie: String,
} }
pub struct Response {}
impl IncomingRequest for Request { impl IncomingRequest for Request {
type EndpointError = Error; type EndpointError = Error;
type OutgoingResponse = Response; type OutgoingResponse = sso_login_with_provider::v3::Response;
const METADATA: Metadata = METADATA; const METADATA: Metadata = METADATA;
@ -470,12 +470,4 @@ mod sso_callback {
Ok(Self { response, cookie }) Ok(Self { response, cookie })
} }
} }
impl OutgoingResponse for Response {
fn try_into_http_response<T: Default + bytes::BufMut>(
self,
) -> Result<http::Response<T>, ruma::api::error::IntoHttpError> {
todo!()
}
}
} }

View file

@ -9,10 +9,7 @@ use axum::{
Router, Router,
}; };
use axum_server::{bind, bind_rustls, tls_rustls::RustlsConfig, Handle as ServerHandle}; use axum_server::{bind, bind_rustls, tls_rustls::RustlsConfig, Handle as ServerHandle};
use conduit::api::{ use conduit::api::{client_server, server_server};
client_server::{self, CALLBACK_PATH},
server_server,
};
use figment::{ use figment::{
providers::{Env, Format, Toml}, providers::{Env, Format, Toml},
Figment, Figment,
@ -283,10 +280,7 @@ fn routes(config: &Config) -> Router {
.ruma_route(client_server::get_sso_redirect_with_provider_route) .ruma_route(client_server::get_sso_redirect_with_provider_route)
// The specification will likely never introduce any endpoint for handling authorization callbacks. // The specification will likely never introduce any endpoint for handling authorization callbacks.
// As a workaround, we use custom path that redirects the user to the default login handler. // As a workaround, we use custom path that redirects the user to the default login handler.
.route( .ruma_route(client_server::handle_callback_route)
&format!("/{CALLBACK_PATH}"),
get(client_server::handle_callback_route),
)
.ruma_route(client_server::get_capabilities_route) .ruma_route(client_server::get_capabilities_route)
.ruma_route(client_server::get_pushrules_all_route) .ruma_route(client_server::get_pushrules_all_route)
.ruma_route(client_server::set_pushrule_route) .ruma_route(client_server::set_pushrule_route)

View file

@ -18,7 +18,7 @@ use ruma::{
DeviceId, RoomVersionId, ServerName, UserId, DeviceId, RoomVersionId, ServerName, UserId,
}; };
use std::{ use std::{
collections::{BTreeMap, HashMap}, collections::{BTreeMap, HashMap, HashSet},
error::Error as StdError, error::Error as StdError,
fs, fs,
future::{self, Future}, future::{self, Future},
@ -522,7 +522,7 @@ impl Service {
pub fn validate_claims<T: DeserializeOwned>( pub fn validate_claims<T: DeserializeOwned>(
&self, &self,
token: &str, token: &str,
validation_data: Option<jsonwebtoken::Validation>, validation_data: Option<&jsonwebtoken::Validation>,
) -> jsonwebtoken::errors::Result<T> { ) -> jsonwebtoken::errors::Result<T> {
let key = jsonwebtoken::DecodingKey::from_secret( let key = jsonwebtoken::DecodingKey::from_secret(
self.keypair().sign(PROBLEMATIC_CONST).as_bytes(), self.keypair().sign(PROBLEMATIC_CONST).as_bytes(),
@ -533,9 +533,9 @@ impl Service {
// these validations are redundant as all JWTs are stored in cookies // these validations are redundant as all JWTs are stored in cookies
v.validate_exp = false; v.validate_exp = false;
v.validate_nbf = false; v.validate_nbf = false;
v.required_spec_claims = Default::default(); v.required_spec_claims = HashSet::new();
jsonwebtoken::decode::<T>(token, &key, &validation_data.unwrap_or(v)) jsonwebtoken::decode::<T>(token, &key, validation_data.unwrap_or(&v))
.map(|data| data.claims) .map(|data| data.claims)
} }

View file

@ -11,6 +11,7 @@ use crate::{
utils, Error, Result, utils, Error, Result,
}; };
use futures_util::future::{self}; use futures_util::future::{self};
use http::HeaderValue;
use mas_oidc_client::{ use mas_oidc_client::{
http_service::HttpService, http_service::HttpService,
requests::{authorization_code::AuthorizationValidationData, discovery}, requests::{authorization_code::AuthorizationValidationData, discovery},
@ -20,6 +21,7 @@ use ruma::{api::client::session::get_login_types::v3::IdentityProvider, OwnedUse
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tokio::sync::OnceCell; use tokio::sync::OnceCell;
use tower::BoxError; use tower::BoxError;
use tower_http::{set_header::SetRequestHeaderLayer, ServiceBuilderExt};
use tracing::error; use tracing::error;
use url::Url; use url::Url;
@ -43,14 +45,17 @@ impl Service {
pub fn build(db: &'static dyn Data) -> Result<Arc<Self>> { pub fn build(db: &'static dyn Data) -> Result<Arc<Self>> {
let client = tower::ServiceBuilder::new() let client = tower::ServiceBuilder::new()
.map_err(BoxError::from) .map_err(BoxError::from)
.layer(tower_http::timeout::TimeoutLayer::new(
std::time::Duration::from_secs(10),
))
.layer(mas_http::BytesToBodyRequestLayer) .layer(mas_http::BytesToBodyRequestLayer)
.layer(mas_http::BodyToBytesResponseLayer) .layer(mas_http::BodyToBytesResponseLayer)
// .override_request_header(http::header::USER_AGENT, "conduit".to_owned()) .layer(SetRequestHeaderLayer::overriding(
// .concurrency_limit(10) http::header::USER_AGENT,
// .follow_redirects() HeaderValue::from_static("conduit/0.9-alpha"),
// .layer(tower_http::timeout::TimeoutLayer::new( ))
// std::time::Duration::from_secs(10), .concurrency_limit(10)
// )) .follow_redirects()
.service(mas_http::make_untraced_client()); .service(mas_http::make_untraced_client());
Ok(Arc::new(Self { Ok(Arc::new(Self {
@ -157,8 +162,8 @@ impl LoginToken {
.expect("time overflow"), .expect("time overflow"),
} }
} }
pub fn audience(&self) -> &UserId { pub fn audience(self) -> OwnedUserId {
&self.aud self.aud
} }
} }