add /openid/request_token and /openid/userinfo routes
heavily changed and improved by me Co-authored-by: mikoto <avdb@keemail.me> Signed-off-by: strawberry <strawberry@puppygock.gay>
This commit is contained in:
parent
5edd391e83
commit
1a64e42cfe
11 changed files with 151 additions and 7 deletions
|
@ -389,6 +389,13 @@ allow_profile_lookup_federation_requests = true
|
||||||
# setting this to false may reduce startup time.
|
# setting this to false may reduce startup time.
|
||||||
#media_statup_check = true
|
#media_statup_check = true
|
||||||
|
|
||||||
|
# OpenID token expiration/TTL in seconds
|
||||||
|
#
|
||||||
|
# These are the OpenID tokens that are primarily used for Matrix account integrations, *not* OIDC/OpenID Connect/etc
|
||||||
|
#
|
||||||
|
# Defaults to 3600 (1 hour)
|
||||||
|
#openid_token_ttl = 3600
|
||||||
|
|
||||||
|
|
||||||
### Generic database options
|
### Generic database options
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ pub(super) mod keys;
|
||||||
pub(super) mod media;
|
pub(super) mod media;
|
||||||
pub(super) mod membership;
|
pub(super) mod membership;
|
||||||
pub(super) mod message;
|
pub(super) mod message;
|
||||||
|
pub(super) mod openid;
|
||||||
pub(super) mod presence;
|
pub(super) mod presence;
|
||||||
pub(super) mod profile;
|
pub(super) mod profile;
|
||||||
pub(super) mod push;
|
pub(super) mod push;
|
||||||
|
@ -48,6 +49,7 @@ pub(super) use media::*;
|
||||||
pub(super) use membership::*;
|
pub(super) use membership::*;
|
||||||
pub use membership::{join_room_by_id_helper, leave_all_rooms, leave_room, validate_and_add_event_id};
|
pub use membership::{join_room_by_id_helper, leave_all_rooms, leave_room, validate_and_add_event_id};
|
||||||
pub(super) use message::*;
|
pub(super) use message::*;
|
||||||
|
pub(super) use openid::*;
|
||||||
pub(super) use presence::*;
|
pub(super) use presence::*;
|
||||||
pub(super) use profile::*;
|
pub(super) use profile::*;
|
||||||
pub use profile::{update_all_rooms, update_avatar_url, update_displayname};
|
pub use profile::{update_all_rooms, update_avatar_url, update_displayname};
|
||||||
|
|
41
src/api/client/openid.rs
Normal file
41
src/api/client/openid.rs
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use conduit::utils;
|
||||||
|
use ruma::{
|
||||||
|
api::client::{account, error::ErrorKind},
|
||||||
|
authentication::TokenType,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::TOKEN_LENGTH;
|
||||||
|
use crate::{services, Error, Result, Ruma};
|
||||||
|
|
||||||
|
/// # `POST /_matrix/client/v3/user/{userId}/openid/request_token`
|
||||||
|
///
|
||||||
|
/// Request an OpenID token to verify identity with third-party services.
|
||||||
|
///
|
||||||
|
/// - The token generated is only valid for the OpenID API
|
||||||
|
pub(crate) async fn create_openid_token_route(
|
||||||
|
body: Ruma<account::request_openid_token::v3::Request>,
|
||||||
|
) -> Result<account::request_openid_token::v3::Response> {
|
||||||
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
|
if sender_user != &body.user_id {
|
||||||
|
return Err(Error::BadRequest(
|
||||||
|
ErrorKind::InvalidParam,
|
||||||
|
"Not allowed to request OpenID tokens on behalf of other users",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let access_token = utils::random_string(TOKEN_LENGTH);
|
||||||
|
|
||||||
|
let expires_in = services()
|
||||||
|
.users
|
||||||
|
.create_openid_token(&body.user_id, &access_token)?;
|
||||||
|
|
||||||
|
Ok(account::request_openid_token::v3::Response {
|
||||||
|
access_token,
|
||||||
|
token_type: TokenType::Bearer,
|
||||||
|
matrix_server_name: services().globals.config.server_name.clone(),
|
||||||
|
expires_in: Duration::from_secs(expires_in),
|
||||||
|
})
|
||||||
|
}
|
|
@ -76,12 +76,26 @@ pub(super) async fn auth(
|
||||||
}
|
}
|
||||||
|
|
||||||
match (metadata.authentication, token) {
|
match (metadata.authentication, token) {
|
||||||
(_, Token::Invalid) => Err(Error::BadRequest(
|
(_, Token::Invalid) => {
|
||||||
ErrorKind::UnknownToken {
|
// OpenID endpoint uses a query param with the same name, drop this once query
|
||||||
soft_logout: false,
|
// params for user auth are removed from the spec. This is required to make
|
||||||
},
|
// integration manager work.
|
||||||
"Unknown access token.",
|
if request.query.access_token.is_some() && request.parts.uri.path().contains("/openid/") {
|
||||||
)),
|
Ok(Auth {
|
||||||
|
origin: None,
|
||||||
|
sender_user: None,
|
||||||
|
sender_device: None,
|
||||||
|
appservice_info: None,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(Error::BadRequest(
|
||||||
|
ErrorKind::UnknownToken {
|
||||||
|
soft_logout: false,
|
||||||
|
},
|
||||||
|
"Unknown access token.",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
},
|
||||||
(AuthScheme::AccessToken, Token::Appservice(info)) => Ok(auth_appservice(request, info)?),
|
(AuthScheme::AccessToken, Token::Appservice(info)) => Ok(auth_appservice(request, info)?),
|
||||||
(AuthScheme::None | AuthScheme::AccessTokenOptional | AuthScheme::AppserviceToken, Token::Appservice(info)) => {
|
(AuthScheme::None | AuthScheme::AccessTokenOptional | AuthScheme::AppserviceToken, Token::Appservice(info)) => {
|
||||||
Ok(Auth {
|
Ok(Auth {
|
||||||
|
|
|
@ -39,6 +39,7 @@ pub fn build(router: Router, server: &Server) -> Router {
|
||||||
.ruma_route(client::get_room_aliases_route)
|
.ruma_route(client::get_room_aliases_route)
|
||||||
.ruma_route(client::get_filter_route)
|
.ruma_route(client::get_filter_route)
|
||||||
.ruma_route(client::create_filter_route)
|
.ruma_route(client::create_filter_route)
|
||||||
|
.ruma_route(client::create_openid_token_route)
|
||||||
.ruma_route(client::set_global_account_data_route)
|
.ruma_route(client::set_global_account_data_route)
|
||||||
.ruma_route(client::set_room_account_data_route)
|
.ruma_route(client::set_room_account_data_route)
|
||||||
.ruma_route(client::get_global_account_data_route)
|
.ruma_route(client::get_global_account_data_route)
|
||||||
|
@ -212,6 +213,7 @@ pub fn build(router: Router, server: &Server) -> Router {
|
||||||
.ruma_route(server::get_profile_information_route)
|
.ruma_route(server::get_profile_information_route)
|
||||||
.ruma_route(server::get_keys_route)
|
.ruma_route(server::get_keys_route)
|
||||||
.ruma_route(server::claim_keys_route)
|
.ruma_route(server::claim_keys_route)
|
||||||
|
.ruma_route(server::get_openid_userinfo_route)
|
||||||
.ruma_route(server::get_hierarchy_route)
|
.ruma_route(server::get_hierarchy_route)
|
||||||
.ruma_route(server::well_known_server)
|
.ruma_route(server::well_known_server)
|
||||||
.route("/_conduwuit/local_user_count", get(client::conduwuit_local_user_count))
|
.route("/_conduwuit/local_user_count", get(client::conduwuit_local_user_count))
|
||||||
|
|
|
@ -7,6 +7,7 @@ pub(super) mod invite;
|
||||||
pub(super) mod key;
|
pub(super) mod key;
|
||||||
pub(super) mod make_join;
|
pub(super) mod make_join;
|
||||||
pub(super) mod make_leave;
|
pub(super) mod make_leave;
|
||||||
|
pub(super) mod openid;
|
||||||
pub(super) mod publicrooms;
|
pub(super) mod publicrooms;
|
||||||
pub(super) mod query;
|
pub(super) mod query;
|
||||||
pub(super) mod send;
|
pub(super) mod send;
|
||||||
|
@ -27,6 +28,7 @@ pub(super) use invite::*;
|
||||||
pub(super) use key::*;
|
pub(super) use key::*;
|
||||||
pub(super) use make_join::*;
|
pub(super) use make_join::*;
|
||||||
pub(super) use make_leave::*;
|
pub(super) use make_leave::*;
|
||||||
|
pub(super) use openid::*;
|
||||||
pub(super) use publicrooms::*;
|
pub(super) use publicrooms::*;
|
||||||
pub(super) use query::*;
|
pub(super) use query::*;
|
||||||
pub(super) use send::*;
|
pub(super) use send::*;
|
||||||
|
|
16
src/api/server/openid.rs
Normal file
16
src/api/server/openid.rs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
use ruma::api::federation::openid::get_openid_userinfo;
|
||||||
|
|
||||||
|
use crate::{services, Result, Ruma};
|
||||||
|
|
||||||
|
/// # `GET /_matrix/federation/v1/openid/userinfo`
|
||||||
|
///
|
||||||
|
/// Get information about the user that generated the OpenID token.
|
||||||
|
pub(crate) async fn get_openid_userinfo_route(
|
||||||
|
body: Ruma<get_openid_userinfo::v1::Request>,
|
||||||
|
) -> Result<get_openid_userinfo::v1::Response> {
|
||||||
|
Ok(get_openid_userinfo::v1::Response::new(
|
||||||
|
services()
|
||||||
|
.users
|
||||||
|
.find_from_openid_token(&body.access_token)?,
|
||||||
|
))
|
||||||
|
}
|
|
@ -201,6 +201,8 @@ pub struct Config {
|
||||||
pub query_trusted_key_servers_first: bool,
|
pub query_trusted_key_servers_first: bool,
|
||||||
#[serde(default = "default_log")]
|
#[serde(default = "default_log")]
|
||||||
pub log: String,
|
pub log: String,
|
||||||
|
#[serde(default = "default_openid_token_ttl")]
|
||||||
|
pub openid_token_ttl: u64,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub turn_username: String,
|
pub turn_username: String,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
@ -671,6 +673,7 @@ impl fmt::Display for Config {
|
||||||
"Query Trusted Key Servers First",
|
"Query Trusted Key Servers First",
|
||||||
&self.query_trusted_key_servers_first.to_string(),
|
&self.query_trusted_key_servers_first.to_string(),
|
||||||
),
|
),
|
||||||
|
("OpenID Token TTL", &self.openid_token_ttl.to_string()),
|
||||||
(
|
(
|
||||||
"TURN username",
|
"TURN username",
|
||||||
if self.turn_username.is_empty() {
|
if self.turn_username.is_empty() {
|
||||||
|
@ -984,6 +987,8 @@ pub fn default_log() -> String {
|
||||||
|
|
||||||
fn default_notification_push_path() -> String { "/_matrix/push/v1/notify".to_owned() }
|
fn default_notification_push_path() -> String { "/_matrix/push/v1/notify".to_owned() }
|
||||||
|
|
||||||
|
fn default_openid_token_ttl() -> u64 { 60 * 60 }
|
||||||
|
|
||||||
fn default_turn_ttl() -> u64 { 60 * 60 * 24 }
|
fn default_turn_ttl() -> u64 { 60 * 60 * 24 }
|
||||||
|
|
||||||
fn default_presence_idle_timeout_s() -> u64 { 5 * 60 }
|
fn default_presence_idle_timeout_s() -> u64 { 5 * 60 }
|
||||||
|
|
|
@ -91,6 +91,7 @@ pub const MAPS: &[&str] = &[
|
||||||
"userid_presenceid",
|
"userid_presenceid",
|
||||||
"userid_selfsigningkeyid",
|
"userid_selfsigningkeyid",
|
||||||
"userid_usersigningkeyid",
|
"userid_usersigningkeyid",
|
||||||
|
"openidtoken_expiresatuserid",
|
||||||
"userroomid_highlightcount",
|
"userroomid_highlightcount",
|
||||||
"userroomid_invitestate",
|
"userroomid_invitestate",
|
||||||
"userroomid_joined",
|
"userroomid_joined",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::{collections::BTreeMap, mem::size_of, sync::Arc};
|
use std::{collections::BTreeMap, mem::size_of, sync::Arc};
|
||||||
|
|
||||||
use conduit::{utils, warn, Error, Result};
|
use conduit::{debug_info, utils, warn, Error, Result};
|
||||||
use database::{Database, Map};
|
use database::{Database, Map};
|
||||||
use ruma::{
|
use ruma::{
|
||||||
api::client::{device::Device, error::ErrorKind, filter::FilterDefinition},
|
api::client::{device::Device, error::ErrorKind, filter::FilterDefinition},
|
||||||
|
@ -28,6 +28,7 @@ pub struct Data {
|
||||||
userid_masterkeyid: Arc<Map>,
|
userid_masterkeyid: Arc<Map>,
|
||||||
userid_selfsigningkeyid: Arc<Map>,
|
userid_selfsigningkeyid: Arc<Map>,
|
||||||
userid_usersigningkeyid: Arc<Map>,
|
userid_usersigningkeyid: Arc<Map>,
|
||||||
|
openidtoken_expiresatuserid: Arc<Map>,
|
||||||
keychangeid_userid: Arc<Map>,
|
keychangeid_userid: Arc<Map>,
|
||||||
todeviceid_events: Arc<Map>,
|
todeviceid_events: Arc<Map>,
|
||||||
userfilterid_filter: Arc<Map>,
|
userfilterid_filter: Arc<Map>,
|
||||||
|
@ -51,6 +52,7 @@ impl Data {
|
||||||
userid_masterkeyid: db["userid_masterkeyid"].clone(),
|
userid_masterkeyid: db["userid_masterkeyid"].clone(),
|
||||||
userid_selfsigningkeyid: db["userid_selfsigningkeyid"].clone(),
|
userid_selfsigningkeyid: db["userid_selfsigningkeyid"].clone(),
|
||||||
userid_usersigningkeyid: db["userid_usersigningkeyid"].clone(),
|
userid_usersigningkeyid: db["userid_usersigningkeyid"].clone(),
|
||||||
|
openidtoken_expiresatuserid: db["openidtoken_expiresatuserid"].clone(),
|
||||||
keychangeid_userid: db["keychangeid_userid"].clone(),
|
keychangeid_userid: db["keychangeid_userid"].clone(),
|
||||||
todeviceid_events: db["todeviceid_events"].clone(),
|
todeviceid_events: db["todeviceid_events"].clone(),
|
||||||
userfilterid_filter: db["userfilterid_filter"].clone(),
|
userfilterid_filter: db["userfilterid_filter"].clone(),
|
||||||
|
@ -920,6 +922,49 @@ impl Data {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates an OpenID token, which can be used to prove that a user has
|
||||||
|
/// access to an account (primarily for integrations)
|
||||||
|
pub(super) fn create_openid_token(&self, user_id: &UserId, token: &str) -> Result<u64> {
|
||||||
|
let expires_in = services().globals.config.openid_token_ttl;
|
||||||
|
let expires_at = utils::millis_since_unix_epoch().saturating_add(expires_in * 1000);
|
||||||
|
|
||||||
|
let mut value = expires_at.to_be_bytes().to_vec();
|
||||||
|
value.extend_from_slice(user_id.as_bytes());
|
||||||
|
|
||||||
|
self.openidtoken_expiresatuserid
|
||||||
|
.insert(token.as_bytes(), value.as_slice())?;
|
||||||
|
|
||||||
|
Ok(expires_in)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find out which user an OpenID access token belongs to.
|
||||||
|
pub(super) fn find_from_openid_token(&self, token: &str) -> Result<OwnedUserId> {
|
||||||
|
let Some(value) = self.openidtoken_expiresatuserid.get(token.as_bytes())? else {
|
||||||
|
return Err(Error::BadRequest(ErrorKind::Unauthorized, "OpenID token is unrecognised"));
|
||||||
|
};
|
||||||
|
|
||||||
|
let (expires_at_bytes, user_bytes) = value.split_at(0_u64.to_be_bytes().len());
|
||||||
|
|
||||||
|
let expires_at = u64::from_be_bytes(
|
||||||
|
expires_at_bytes
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| Error::bad_database("expires_at in openid_userid is invalid u64."))?,
|
||||||
|
);
|
||||||
|
|
||||||
|
if expires_at < utils::millis_since_unix_epoch() {
|
||||||
|
debug_info!("OpenID token is expired, removing");
|
||||||
|
self.openidtoken_expiresatuserid.remove(token.as_bytes())?;
|
||||||
|
|
||||||
|
return Err(Error::BadRequest(ErrorKind::Unauthorized, "OpenID token is expired"));
|
||||||
|
}
|
||||||
|
|
||||||
|
UserId::parse(
|
||||||
|
utils::string_from_bytes(user_bytes)
|
||||||
|
.map_err(|_| Error::bad_database("User ID in openid_userid is invalid unicode."))?,
|
||||||
|
)
|
||||||
|
.map_err(|_| Error::bad_database("User ID in openid_userid is invalid."))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Will only return with Some(username) if the password was not empty and the
|
/// Will only return with Some(username) if the password was not empty and the
|
||||||
|
|
|
@ -477,6 +477,15 @@ impl Service {
|
||||||
pub fn get_filter(&self, user_id: &UserId, filter_id: &str) -> Result<Option<FilterDefinition>> {
|
pub fn get_filter(&self, user_id: &UserId, filter_id: &str) -> Result<Option<FilterDefinition>> {
|
||||||
self.db.get_filter(user_id, filter_id)
|
self.db.get_filter(user_id, filter_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates an OpenID token, which can be used to prove that a user has
|
||||||
|
/// access to an account (primarily for integrations)
|
||||||
|
pub fn create_openid_token(&self, user_id: &UserId, token: &str) -> Result<u64> {
|
||||||
|
self.db.create_openid_token(user_id, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find out which user an OpenID access token belongs to.
|
||||||
|
pub fn find_from_openid_token(&self, token: &str) -> Result<OwnedUserId> { self.db.find_from_openid_token(token) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ensure that a user only sees signatures from themselves and the target user
|
/// Ensure that a user only sees signatures from themselves and the target user
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue