mirror of
https://forgejo.ellis.link/continuwuation/continuwuity.git
synced 2026-05-26 20:49:55 +00:00
feat: Add support for 3pid management
This commit is contained in:
@@ -2069,10 +2069,12 @@
|
||||
#
|
||||
#sender =
|
||||
|
||||
# Whether to require that users provide an email address when they register.
|
||||
# Whether to require that users provide an email address when they
|
||||
# register.
|
||||
#
|
||||
#require_email_for_registration = false
|
||||
|
||||
# Whether to require that users who register with a registration token provide an email address.
|
||||
# Whether to require that users who register with a registration token
|
||||
# provide an email address.
|
||||
#
|
||||
#require_email_for_token_registration = false
|
||||
|
||||
@@ -1121,7 +1121,7 @@ pub(super) async fn get_user_by_email(&self, email: String) -> Result {
|
||||
self.bail_restricted()?;
|
||||
|
||||
let Ok(email) = Address::try_from(email) else {
|
||||
return Err!("Invalid email address");
|
||||
return Err!("Invalid email address.");
|
||||
};
|
||||
|
||||
match self.services.threepid.get_localpart_for_email(&email).await {
|
||||
@@ -1147,7 +1147,7 @@ pub(super) async fn change_email(&self, user_id: String, email: Option<String>)
|
||||
|
||||
let user_id = parse_local_user_id(self.services, &user_id)?;
|
||||
let Ok(new_email) = email.map(Address::try_from).transpose() else {
|
||||
return Err!("Invalid email address");
|
||||
return Err!("Invalid email address.");
|
||||
};
|
||||
|
||||
if self.services.mailer.mailer().is_none() {
|
||||
|
||||
@@ -13,9 +13,8 @@ use ruma::{
|
||||
api::client::{
|
||||
account::{
|
||||
ThirdPartyIdRemovalStatus, change_password, check_registration_token_validity,
|
||||
deactivate, get_3pids, get_username_availability,
|
||||
request_3pid_management_token_via_email, request_3pid_management_token_via_msisdn,
|
||||
request_password_change_token_via_email, whoami,
|
||||
deactivate, get_username_availability, request_password_change_token_via_email,
|
||||
whoami,
|
||||
},
|
||||
uiaa::{AuthFlow, AuthType},
|
||||
},
|
||||
@@ -33,6 +32,7 @@ use super::{DEVICE_ID_LENGTH, TOKEN_LENGTH, join_room_by_id_helper};
|
||||
use crate::Ruma;
|
||||
|
||||
pub(crate) mod register;
|
||||
pub(crate) mod threepid;
|
||||
|
||||
/// # `GET /_matrix/client/v3/register/available`
|
||||
///
|
||||
@@ -226,12 +226,12 @@ pub(crate) async fn change_password_route(
|
||||
/// # `POST /_matrix/client/v3/account/password/email/requestToken`
|
||||
///
|
||||
/// Requests a validation email for the purpose of resetting a user's password.
|
||||
pub(crate) async fn password_request_token_route(
|
||||
pub(crate) async fn request_password_change_token_via_email_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<request_password_change_token_via_email::v3::Request>,
|
||||
) -> Result<request_password_change_token_via_email::v3::Response> {
|
||||
let Ok(email) = Address::try_from(body.email.clone()) else {
|
||||
return Err!(Request(InvalidParam("Invalid email address")));
|
||||
return Err!(Request(InvalidParam("Invalid email address.")));
|
||||
};
|
||||
|
||||
let Some(localpart) = services.threepid.get_localpart_for_email(&email).await else {
|
||||
@@ -341,45 +341,6 @@ pub(crate) async fn deactivate_route(
|
||||
})
|
||||
}
|
||||
|
||||
/// # `GET _matrix/client/v3/account/3pid`
|
||||
///
|
||||
/// Get a list of third party identifiers associated with this account.
|
||||
///
|
||||
/// - Currently always returns empty list
|
||||
pub(crate) async fn third_party_route(
|
||||
body: Ruma<get_3pids::v3::Request>,
|
||||
) -> Result<get_3pids::v3::Response> {
|
||||
let _sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
Ok(get_3pids::v3::Response::new(Vec::new()))
|
||||
}
|
||||
|
||||
/// # `POST /_matrix/client/v3/account/3pid/email/requestToken`
|
||||
///
|
||||
/// "This API should be used to request validation tokens when adding an email
|
||||
/// address to an account"
|
||||
///
|
||||
/// - 403 signals that The homeserver does not allow the third party identifier
|
||||
/// as a contact option.
|
||||
pub(crate) async fn request_3pid_management_token_via_email_route(
|
||||
_body: Ruma<request_3pid_management_token_via_email::v3::Request>,
|
||||
) -> Result<request_3pid_management_token_via_email::v3::Response> {
|
||||
Err!(Request(ThreepidDenied("Third party identifiers are not implemented")))
|
||||
}
|
||||
|
||||
/// # `POST /_matrix/client/v3/account/3pid/msisdn/requestToken`
|
||||
///
|
||||
/// "This API should be used to request validation tokens when adding an phone
|
||||
/// number to an account"
|
||||
///
|
||||
/// - 403 signals that The homeserver does not allow the third party identifier
|
||||
/// as a contact option.
|
||||
pub(crate) async fn request_3pid_management_token_via_msisdn_route(
|
||||
_body: Ruma<request_3pid_management_token_via_msisdn::v3::Request>,
|
||||
) -> Result<request_3pid_management_token_via_msisdn::v3::Response> {
|
||||
Err!(Request(ThreepidDenied("Third party identifiers are not implemented")))
|
||||
}
|
||||
|
||||
/// # `GET /_matrix/client/v1/register/m.login.registration_token/validity`
|
||||
///
|
||||
/// Checks if the provided registration token is valid at the time of checking.
|
||||
|
||||
@@ -569,13 +569,13 @@ async fn determine_registration_user_id(
|
||||
|
||||
/// # `POST /_matrix/client/v3/register/email/requestToken`
|
||||
///
|
||||
/// Requests a validation email for the purpose of registering a new account
|
||||
pub(crate) async fn register_request_token_route(
|
||||
/// Requests a validation email for the purpose of registering a new account.
|
||||
pub(crate) async fn request_registration_token_via_email_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<request_registration_token_via_email::v3::Request>,
|
||||
) -> Result<request_registration_token_via_email::v3::Response> {
|
||||
let Ok(email) = Address::try_from(body.email.clone()) else {
|
||||
return Err!(Request(InvalidParam("Invalid email address")));
|
||||
return Err!(Request(InvalidParam("Invalid email address.")));
|
||||
};
|
||||
|
||||
if services
|
||||
@@ -584,7 +584,7 @@ pub(crate) async fn register_request_token_route(
|
||||
.await
|
||||
.is_some()
|
||||
{
|
||||
return Err!(Request(ThreepidInUse("This email address is already in use")));
|
||||
return Err!(Request(ThreepidInUse("This email address is already in use.")));
|
||||
}
|
||||
|
||||
let session = services
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
use std::time::SystemTime;
|
||||
|
||||
use axum::extract::State;
|
||||
use conduwuit::{Err, Result, err};
|
||||
use lettre::{Address, message::Mailbox};
|
||||
use ruma::{
|
||||
MilliSecondsSinceUnixEpoch,
|
||||
api::client::account::{
|
||||
ThirdPartyIdRemovalStatus, add_3pid, delete_3pid, get_3pids,
|
||||
request_3pid_management_token_via_email, request_3pid_management_token_via_msisdn,
|
||||
},
|
||||
thirdparty::{Medium, ThirdPartyIdentifierInit},
|
||||
};
|
||||
use service::{mailer::messages, uiaa::Identity};
|
||||
|
||||
use crate::Ruma;
|
||||
|
||||
/// # `GET _matrix/client/v3/account/3pid`
|
||||
///
|
||||
/// Get a list of third party identifiers associated with this account.
|
||||
pub(crate) async fn third_party_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<get_3pids::v3::Request>,
|
||||
) -> Result<get_3pids::v3::Response> {
|
||||
let sender_user = body.sender_user();
|
||||
let mut threepids = vec![];
|
||||
|
||||
if let Some(email) = services
|
||||
.threepid
|
||||
.get_email_for_localpart(sender_user.localpart())
|
||||
.await
|
||||
{
|
||||
threepids.push(
|
||||
ThirdPartyIdentifierInit {
|
||||
address: email.to_string(),
|
||||
medium: Medium::Email,
|
||||
// We don't currently track these, and they aren't used for much
|
||||
validated_at: MilliSecondsSinceUnixEpoch::now(),
|
||||
added_at: MilliSecondsSinceUnixEpoch::from_system_time(SystemTime::UNIX_EPOCH)
|
||||
.unwrap(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(get_3pids::v3::Response::new(threepids))
|
||||
}
|
||||
|
||||
/// # `POST /_matrix/client/v3/account/3pid/email/requestToken`
|
||||
///
|
||||
/// Requests a validation email for the purpose of changing an account's email.
|
||||
pub(crate) async fn request_3pid_management_token_via_email_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<request_3pid_management_token_via_email::v3::Request>,
|
||||
) -> Result<request_3pid_management_token_via_email::v3::Response> {
|
||||
let Ok(email) = Address::try_from(body.email.clone()) else {
|
||||
return Err!(Request(InvalidParam("Invalid email address.")));
|
||||
};
|
||||
|
||||
if services
|
||||
.threepid
|
||||
.get_localpart_for_email(&email)
|
||||
.await
|
||||
.is_some()
|
||||
{
|
||||
return Err!(Request(ThreepidInUse("This email address is already in use.")));
|
||||
}
|
||||
|
||||
let session = services
|
||||
.threepid
|
||||
.send_validation_email(
|
||||
Mailbox::new(None, email),
|
||||
|verification_link| messages::ChangeEmail {
|
||||
server_name: services.config.server_name.as_str(),
|
||||
user_id: body.sender_user.as_deref(),
|
||||
verification_link,
|
||||
},
|
||||
&body.client_secret,
|
||||
body.send_attempt.try_into().unwrap(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(request_3pid_management_token_via_email::v3::Response::new(session))
|
||||
}
|
||||
|
||||
/// # `POST /_matrix/client/v3/account/3pid/msisdn/requestToken`
|
||||
///
|
||||
/// "This API should be used to request validation tokens when adding an email
|
||||
/// address to an account"
|
||||
///
|
||||
/// - 403 signals that The homeserver does not allow the third party identifier
|
||||
/// as a contact option.
|
||||
pub(crate) async fn request_3pid_management_token_via_msisdn_route(
|
||||
_body: Ruma<request_3pid_management_token_via_msisdn::v3::Request>,
|
||||
) -> Result<request_3pid_management_token_via_msisdn::v3::Response> {
|
||||
Err!(Request(ThreepidMediumNotSupported(
|
||||
"MSISDN third-party identifiers are not supported."
|
||||
)))
|
||||
}
|
||||
|
||||
/// # `POST /_matrix/client/v3/account/3pid/add`
|
||||
pub(crate) async fn add_3pid_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<add_3pid::v3::Request>,
|
||||
) -> Result<add_3pid::v3::Response> {
|
||||
let sender_user = body.sender_user();
|
||||
|
||||
// Require password auth to add an email
|
||||
services
|
||||
.uiaa
|
||||
.authenticate_password(&body.auth, Some(Identity::from_user_id(sender_user)))
|
||||
.await?;
|
||||
|
||||
let email = services
|
||||
.threepid
|
||||
.consume_valid_session(&body.sid, &body.client_secret)
|
||||
.await
|
||||
.map_err(|message| err!(Request(ThreepidAuthFailed("{message}"))))?;
|
||||
|
||||
services
|
||||
.threepid
|
||||
.associate_localpart_email(sender_user.localpart(), &email)
|
||||
.await?;
|
||||
|
||||
Ok(add_3pid::v3::Response::new())
|
||||
}
|
||||
|
||||
/// # `POST /_matrix/client/v3/account/3pid/delete`
|
||||
pub(crate) async fn delete_3pid_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<delete_3pid::v3::Request>,
|
||||
) -> Result<delete_3pid::v3::Response> {
|
||||
let sender_user = body.sender_user();
|
||||
|
||||
if body.medium != Medium::Email {
|
||||
return Ok(delete_3pid::v3::Response {
|
||||
id_server_unbind_result: ThirdPartyIdRemovalStatus::NoSupport,
|
||||
});
|
||||
}
|
||||
|
||||
if services
|
||||
.threepid
|
||||
.disassociate_localpart_email(sender_user.localpart())
|
||||
.await
|
||||
.is_none()
|
||||
{
|
||||
return Err!(Request(ThreepidNotFound("Your account has no associated email.")));
|
||||
}
|
||||
|
||||
Ok(delete_3pid::v3::Response {
|
||||
id_server_unbind_result: ThirdPartyIdRemovalStatus::Success,
|
||||
})
|
||||
}
|
||||
@@ -30,8 +30,10 @@ pub(crate) async fn get_capabilities_route(
|
||||
default: services.server.config.default_room_version.clone(),
|
||||
};
|
||||
|
||||
// we do not implement 3PID stuff
|
||||
capabilities.thirdparty_id_changes = ThirdPartyIdChangesCapability { enabled: false };
|
||||
// Only allow 3pid changes if SMTP is configured
|
||||
capabilities.thirdparty_id_changes = ThirdPartyIdChangesCapability {
|
||||
enabled: services.mailer.mailer().is_some(),
|
||||
};
|
||||
|
||||
capabilities.get_login_token = GetLoginTokenCapability {
|
||||
enabled: services.server.config.login_via_existing_session,
|
||||
@@ -51,7 +53,7 @@ pub(crate) async fn get_capabilities_route(
|
||||
.await
|
||||
{
|
||||
// Advertise suspension API
|
||||
capabilities.set("uk.timedout.msc4323", json!({"suspend":true, "lock": false}))?;
|
||||
capabilities.set("uk.timedout.msc4323", json!({"suspend": true, "lock": false}))?;
|
||||
}
|
||||
|
||||
Ok(get_capabilities::v3::Response { capabilities })
|
||||
|
||||
+7
-5
@@ -29,7 +29,7 @@ pub fn build(router: Router<State>, server: &Server) -> Router<State> {
|
||||
.ruma_route(&client::get_supported_versions_route)
|
||||
.ruma_route(&client::get_register_available_route)
|
||||
.ruma_route(&client::register::register_route)
|
||||
.ruma_route(&client::register::register_request_token_route)
|
||||
.ruma_route(&client::register::request_registration_token_via_email_route)
|
||||
.ruma_route(&client::get_login_types_route)
|
||||
.ruma_route(&client::login_route)
|
||||
.ruma_route(&client::login_token_route)
|
||||
@@ -37,11 +37,13 @@ pub fn build(router: Router<State>, server: &Server) -> Router<State> {
|
||||
.ruma_route(&client::logout_route)
|
||||
.ruma_route(&client::logout_all_route)
|
||||
.ruma_route(&client::change_password_route)
|
||||
.ruma_route(&client::password_request_token_route)
|
||||
.ruma_route(&client::request_password_change_token_via_email_route)
|
||||
.ruma_route(&client::deactivate_route)
|
||||
.ruma_route(&client::third_party_route)
|
||||
.ruma_route(&client::request_3pid_management_token_via_email_route)
|
||||
.ruma_route(&client::request_3pid_management_token_via_msisdn_route)
|
||||
.ruma_route(&client::threepid::third_party_route)
|
||||
.ruma_route(&client::threepid::request_3pid_management_token_via_email_route)
|
||||
.ruma_route(&client::threepid::request_3pid_management_token_via_msisdn_route)
|
||||
.ruma_route(&client::threepid::add_3pid_route)
|
||||
.ruma_route(&client::threepid::delete_3pid_route)
|
||||
.ruma_route(&client::check_registration_token_validity)
|
||||
.ruma_route(&client::get_capabilities_route)
|
||||
.ruma_route(&client::get_pushrules_all_route)
|
||||
|
||||
@@ -415,13 +415,6 @@ impl<'a, 'de: 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> {
|
||||
tracing::instrument(level = "trace", skip_all, fields(?self.buf))
|
||||
)]
|
||||
fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
|
||||
debug_assert_eq!(
|
||||
conduwuit::debug::type_name::<V>(),
|
||||
"serde_json::value::de::<impl serde_core::de::Deserialize for \
|
||||
serde_json::value::Value>::deserialize::ValueVisitor",
|
||||
"deserialize_any: type not expected"
|
||||
);
|
||||
|
||||
match self.record_peek_byte() {
|
||||
| Some(b'{') => self.deserialize_map(visitor),
|
||||
| Some(b'[') => serde_json::Deserializer::from_slice(self.record_next())
|
||||
|
||||
@@ -8,7 +8,8 @@ pub trait MessageTemplate: Template {
|
||||
#[derive(Template)]
|
||||
#[template(path = "mail/change_email.txt")]
|
||||
pub struct ChangeEmail<'a> {
|
||||
pub user_id: &'a UserId,
|
||||
pub server_name: &'a str,
|
||||
pub user_id: Option<&'a UserId>,
|
||||
pub verification_link: String,
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,12 @@
|
||||
|
||||
{% block content -%}
|
||||
Hello!
|
||||
|
||||
{% if let Some(user_id) = user_id -%}
|
||||
Somebody, probably you, tried to associate this email address with the Matrix account {{ user_id }}.
|
||||
If that's your account, and this is your email address, click this link to proceed:
|
||||
{%- else -%}
|
||||
Somebody, probably you, tried to associate this email address with a Matrix account on {{ server_name }}.
|
||||
{%- endif %}
|
||||
If that was you, and this is your email address, click this link to proceed:
|
||||
{{ verification_link }}
|
||||
Otherwise, you can ignore this email. The above link will expire in one hour.
|
||||
{%- endblock %}
|
||||
|
||||
@@ -227,15 +227,15 @@ impl Service {
|
||||
///
|
||||
/// [`Self::get_localpart_for_email`] may be used if only the email is
|
||||
/// known.
|
||||
pub async fn disassociate_localpart_email(&self, localpart: &str) {
|
||||
let email = self
|
||||
.get_email_for_localpart(localpart)
|
||||
.await
|
||||
.expect("localpart has no email associated");
|
||||
pub async fn disassociate_localpart_email(&self, localpart: &str) -> Option<Address> {
|
||||
let email = self.get_email_for_localpart(localpart).await?;
|
||||
|
||||
self.db.localpart_email.remove(localpart);
|
||||
self.db
|
||||
.email_localpart
|
||||
.remove(<Address as AsRef<str>>::as_ref(&email));
|
||||
|
||||
Some(email)
|
||||
}
|
||||
|
||||
/// Get the email associated with a localpart, if one exists.
|
||||
|
||||
Reference in New Issue
Block a user