mirror of
https://forgejo.ellis.link/continuwuation/continuwuity.git
synced 2026-05-26 20:49:55 +00:00
feat: Add admin commands for managing users' email addresses
This commit is contained in:
Generated
+2
@@ -982,6 +982,7 @@ dependencies = [
|
||||
"conduwuit_service",
|
||||
"const-str",
|
||||
"futures",
|
||||
"lettre",
|
||||
"log",
|
||||
"ruma",
|
||||
"serde-saphyr",
|
||||
@@ -1011,6 +1012,7 @@ dependencies = [
|
||||
"hyper",
|
||||
"ipaddress",
|
||||
"itertools 0.14.0",
|
||||
"lettre",
|
||||
"log",
|
||||
"rand 0.10.0",
|
||||
"reqwest",
|
||||
|
||||
@@ -2050,6 +2050,8 @@
|
||||
#
|
||||
# For most modern mail servers, format the URI like this:
|
||||
# `smtps://username:password@hostname:port`
|
||||
# Note that you will need to URL-encode the username and password. If your username _is_
|
||||
# your email address, you will need to replace the `@` with `%40`.
|
||||
#
|
||||
# For a guide on the accepted URI syntax, consult Lettre's documentation:
|
||||
# https://docs.rs/lettre/latest/lettre/transport/smtp/struct.AsyncSmtpTransport.html#method.from_url
|
||||
|
||||
@@ -80,6 +80,7 @@ conduwuit-macros.workspace = true
|
||||
conduwuit-service.workspace = true
|
||||
const-str.workspace = true
|
||||
futures.workspace = true
|
||||
lettre.workspace = true
|
||||
log.workspace = true
|
||||
ruma.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
||||
@@ -19,6 +19,7 @@ use conduwuit::{
|
||||
warn,
|
||||
};
|
||||
use futures::{FutureExt, StreamExt, TryStreamExt};
|
||||
use lettre::message::Mailbox;
|
||||
use ruma::{
|
||||
CanonicalJsonObject, CanonicalJsonValue, EventId, OwnedEventId, OwnedRoomId,
|
||||
OwnedRoomOrAliasId, OwnedServerName, RoomId, RoomVersionId,
|
||||
@@ -876,3 +877,31 @@ pub(super) async fn trim_memory(&self) -> Result {
|
||||
|
||||
writeln!(self, "done").await
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn send_test_email(&self) -> Result {
|
||||
self.bail_restricted()?;
|
||||
|
||||
let mailer = self.services.mailer.expect_mailer()?;
|
||||
let Some(sender) = self.sender else {
|
||||
return Err!("No sender user provided in context");
|
||||
};
|
||||
|
||||
let Some(email) = self
|
||||
.services
|
||||
.threepid
|
||||
.get_email_for_localpart(sender.localpart())
|
||||
.await
|
||||
else {
|
||||
return Err!("{} has no associated email address", sender);
|
||||
};
|
||||
|
||||
mailer
|
||||
.send(Mailbox::new(None, email.clone()), service::mailer::messages::Test)
|
||||
.await?;
|
||||
|
||||
self.write_str(&format!("Test email successfully sent to {email}"))
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -225,6 +225,9 @@ pub enum DebugCommand {
|
||||
level: Option<i32>,
|
||||
},
|
||||
|
||||
/// Send a test email to the invoking admin's email address
|
||||
SendTestEmail,
|
||||
|
||||
/// Developer test stubs
|
||||
#[command(subcommand)]
|
||||
#[allow(non_snake_case)]
|
||||
|
||||
@@ -11,6 +11,7 @@ use conduwuit::{
|
||||
warn,
|
||||
};
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use lettre::Address;
|
||||
use ruma::{
|
||||
OwnedEventId, OwnedRoomId, OwnedRoomOrAliasId, OwnedServerName, OwnedUserId, UserId,
|
||||
events::{
|
||||
@@ -1094,3 +1095,106 @@ pub(super) async fn enable_login(&self, user_id: String) -> Result {
|
||||
|
||||
self.write_str(&format!("{user_id} can now log in.")).await
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn get_email(&self, user_id: String) -> Result {
|
||||
self.bail_restricted()?;
|
||||
let user_id = parse_local_user_id(self.services, &user_id)?;
|
||||
|
||||
match self
|
||||
.services
|
||||
.threepid
|
||||
.get_email_for_localpart(user_id.localpart())
|
||||
.await
|
||||
{
|
||||
| Some(email) =>
|
||||
self.write_str(&format!("{user_id} has the associated email address {email}."))
|
||||
.await,
|
||||
| None =>
|
||||
self.write_str(&format!("{user_id} has no associated email address."))
|
||||
.await,
|
||||
}
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
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");
|
||||
};
|
||||
|
||||
match self.services.threepid.get_localpart_for_email(&email).await {
|
||||
| Some(localpart) => {
|
||||
let user_id = OwnedUserId::parse(format!(
|
||||
"@{localpart}:{}",
|
||||
self.services.globals.server_name()
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
self.write_str(&format!("{email} belongs to {user_id}."))
|
||||
.await
|
||||
},
|
||||
| None =>
|
||||
self.write_str(&format!("No user has {email} as their email address."))
|
||||
.await,
|
||||
}
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn change_email(&self, user_id: String, email: Option<String>) -> Result {
|
||||
self.bail_restricted()?;
|
||||
|
||||
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");
|
||||
};
|
||||
|
||||
if self.services.mailer.mailer().is_none() {
|
||||
warn!("SMTP has not been configured on this server, emails cannot be sent.");
|
||||
}
|
||||
|
||||
let current_email = self
|
||||
.services
|
||||
.threepid
|
||||
.get_email_for_localpart(user_id.localpart())
|
||||
.await;
|
||||
|
||||
match (current_email, new_email) {
|
||||
| (None, None) =>
|
||||
self.write_str(&format!(
|
||||
"{user_id} already had no associated email. No changes have been made."
|
||||
))
|
||||
.await,
|
||||
| (current_email, Some(new_email)) => {
|
||||
self.services
|
||||
.threepid
|
||||
.associate_localpart_email(user_id.localpart(), &new_email)
|
||||
.await?;
|
||||
|
||||
if let Some(current_email) = current_email {
|
||||
self.write_str(&format!(
|
||||
"The associated email of {user_id} has been changed from {current_email} to \
|
||||
{new_email}."
|
||||
))
|
||||
.await
|
||||
} else {
|
||||
self.write_str(&format!(
|
||||
"{user_id} has been associated with the email {new_email}."
|
||||
))
|
||||
.await
|
||||
}
|
||||
},
|
||||
| (Some(current_email), None) => {
|
||||
self.services
|
||||
.threepid
|
||||
.disassociate_localpart_email(user_id.localpart())
|
||||
.await;
|
||||
|
||||
self.write_str(&format!(
|
||||
"The associated email of {user_id} has been removed (it was {current_email})."
|
||||
))
|
||||
.await
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,24 @@ pub enum UserCommand {
|
||||
username: String,
|
||||
},
|
||||
|
||||
/// Get a user's associated email address.
|
||||
GetEmail {
|
||||
user_id: String,
|
||||
},
|
||||
|
||||
/// Get the user with the given email address.
|
||||
GetUserByEmail {
|
||||
email: String,
|
||||
},
|
||||
|
||||
/// Update or remove a user's email address.
|
||||
///
|
||||
/// If `email` is not supplied, the user's existing address wil be removed.
|
||||
ChangeEmail {
|
||||
user_id: String,
|
||||
email: Option<String>,
|
||||
},
|
||||
|
||||
/// Deactivate a user
|
||||
///
|
||||
/// User will be removed from all rooms by default.
|
||||
|
||||
@@ -168,19 +168,25 @@ pub(crate) async fn register_route(
|
||||
|
||||
let password = if is_guest { None } else { body.password.as_deref() };
|
||||
|
||||
// Create user
|
||||
services.users.create(&user_id, password, None).await?;
|
||||
|
||||
// If the user registered with an email, associate it with their account
|
||||
// If the user registered with an email, associate it with their account.
|
||||
// Do this _before_ creating the user to make sure that, if their email is
|
||||
// already in use, we don't make them an account.
|
||||
//
|
||||
// Note that this should only rarely cause a bailout because email uniqueness is
|
||||
// also checked by /requestToken.
|
||||
#[allow(clippy::collapsible_if)]
|
||||
if let Some(identity) = identity {
|
||||
if let Some(email) = identity.email {
|
||||
services
|
||||
.threepid
|
||||
.associate_localpart_email(user_id.localpart(), email);
|
||||
.associate_localpart_email(user_id.localpart(), &email)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
// Create user
|
||||
services.users.create(&user_id, password, None).await?;
|
||||
|
||||
// Set an initial display name
|
||||
let mut displayname = user_id.localpart().to_owned();
|
||||
|
||||
|
||||
@@ -2461,6 +2461,9 @@ pub struct SmtpConfig {
|
||||
///
|
||||
/// For most modern mail servers, format the URI like this:
|
||||
/// `smtps://username:password@hostname:port`
|
||||
/// Note that you will need to URL-encode the username and password. If your
|
||||
/// username _is_ your email address, you will need to replace the `@` with
|
||||
/// `%40`.
|
||||
///
|
||||
/// For a guide on the accepted URI syntax, consult Lettre's documentation:
|
||||
/// https://docs.rs/lettre/latest/lettre/transport/smtp/struct.AsyncSmtpTransport.html#method.from_url
|
||||
|
||||
@@ -49,10 +49,18 @@ pub(super) static MAPS: &[Descriptor] = &[
|
||||
name: "bannedroomids",
|
||||
..descriptor::RANDOM_SMALL
|
||||
},
|
||||
Descriptor {
|
||||
name: "clientsecret_validationsessionid",
|
||||
..descriptor::RANDOM_SMALL
|
||||
},
|
||||
Descriptor {
|
||||
name: "disabledroomids",
|
||||
..descriptor::RANDOM_SMALL
|
||||
},
|
||||
Descriptor {
|
||||
name: "email_localpart",
|
||||
..descriptor::RANDOM_SMALL
|
||||
},
|
||||
Descriptor {
|
||||
name: "eventid_outlierpdu",
|
||||
cache_disp: CacheDisp::SharedWith("pduid_pdu"),
|
||||
@@ -100,6 +108,10 @@ pub(super) static MAPS: &[Descriptor] = &[
|
||||
name: "lazyloadedids",
|
||||
..descriptor::RANDOM_SMALL
|
||||
},
|
||||
Descriptor {
|
||||
name: "localpart_email",
|
||||
..descriptor::RANDOM_SMALL
|
||||
},
|
||||
Descriptor {
|
||||
name: "mediaid_file",
|
||||
..descriptor::RANDOM_SMALL
|
||||
@@ -458,4 +470,12 @@ pub(super) static MAPS: &[Descriptor] = &[
|
||||
name: "userroomid_invitesender",
|
||||
..descriptor::RANDOM_SMALL
|
||||
},
|
||||
Descriptor {
|
||||
name: "validationsessionid_session",
|
||||
..descriptor::RANDOM_SMALL
|
||||
},
|
||||
Descriptor {
|
||||
name: "validationsessionid_token",
|
||||
..descriptor::RANDOM_SMALL
|
||||
},
|
||||
];
|
||||
|
||||
@@ -8,8 +8,8 @@ pub trait MessageTemplate: Template {
|
||||
#[derive(Template)]
|
||||
#[template(path = "mail/change_email.txt.j2")]
|
||||
pub struct ChangeEmail<'a> {
|
||||
user_id: &'a UserId,
|
||||
verification_link: &'a str,
|
||||
pub user_id: &'a UserId,
|
||||
pub verification_link: String,
|
||||
}
|
||||
|
||||
impl MessageTemplate for ChangeEmail<'_> {
|
||||
@@ -19,8 +19,8 @@ impl MessageTemplate for ChangeEmail<'_> {
|
||||
#[derive(Template)]
|
||||
#[template(path = "mail/new_account.txt.j2")]
|
||||
pub struct NewAccount<'a> {
|
||||
server_name: &'a str,
|
||||
verification_link: &'a str,
|
||||
pub server_name: &'a str,
|
||||
pub verification_link: String,
|
||||
}
|
||||
|
||||
impl MessageTemplate for NewAccount<'_> {
|
||||
@@ -30,9 +30,9 @@ impl MessageTemplate for NewAccount<'_> {
|
||||
#[derive(Template)]
|
||||
#[template(path = "mail/password_reset.txt.j2")]
|
||||
pub struct PasswordReset<'a> {
|
||||
display_name: &'a str,
|
||||
user_id: &'a UserId,
|
||||
verification_link: &'a str,
|
||||
pub display_name: Option<&'a str>,
|
||||
pub user_id: &'a UserId,
|
||||
pub verification_link: String,
|
||||
}
|
||||
|
||||
impl MessageTemplate for PasswordReset<'_> {
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::sync::Arc;
|
||||
use conduwuit::{Err, Result, err, info};
|
||||
use lettre::{
|
||||
AsyncSmtpTransport, AsyncTransport, Tokio1Executor,
|
||||
message::{Mailbox, MessageBuilder},
|
||||
message::{Mailbox, MessageBuilder, header::ContentType},
|
||||
};
|
||||
|
||||
use crate::{Args, mailer::messages::MessageTemplate};
|
||||
@@ -65,6 +65,11 @@ impl Service {
|
||||
.as_ref()
|
||||
.map(|(sender, transport)| Mailer { sender, transport })
|
||||
}
|
||||
|
||||
pub fn expect_mailer(&self) -> Result<Mailer<'_>> {
|
||||
self.mailer()
|
||||
.ok_or_else(|| err!("SMTP is not configured on this server"))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Mailer<'a> {
|
||||
@@ -89,6 +94,7 @@ impl Mailer<'_> {
|
||||
.to(recipient)
|
||||
.subject(subject)
|
||||
.date_now()
|
||||
.header(ContentType::TEXT_PLAIN)
|
||||
.body(body)
|
||||
.expect("should have been able to construct message");
|
||||
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
{% extends "_base.txt.j2" %}
|
||||
|
||||
{% block content -%}
|
||||
{%- if let Some(display_name) = display_name -%}
|
||||
Hello {{ display_name }} ({{ user_id }}),
|
||||
{%- else -%}
|
||||
Hello {{ user_id }},
|
||||
{%- endif %}
|
||||
|
||||
Somebody, probably you, tried to reset your Matrix account's password.
|
||||
If you requested for your password to be reset, click this link to proceed:
|
||||
|
||||
@@ -6,6 +6,7 @@ use std::{
|
||||
use conduwuit::utils;
|
||||
use database::{Database, Deserialized, Map};
|
||||
use lettre::Address;
|
||||
use ruma::{ClientSecret, OwnedClientSecret, OwnedSessionId};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub(super) struct Data {
|
||||
@@ -20,11 +21,11 @@ pub(super) struct Data {
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub(crate) struct ValidationSession {
|
||||
/// The session's ID
|
||||
pub session_id: String,
|
||||
pub session_id: OwnedSessionId,
|
||||
/// The email address which is being validated
|
||||
pub email: Address,
|
||||
/// The client's supplied client secret
|
||||
pub client_secret: String,
|
||||
pub client_secret: OwnedClientSecret,
|
||||
/// Whether the email address has been validated successfully yet
|
||||
pub(super) has_been_validated: bool,
|
||||
}
|
||||
@@ -62,9 +63,9 @@ impl PartialEq<str> for ValidationToken {
|
||||
impl Data {
|
||||
pub(super) fn new(db: &Arc<Database>) -> Self {
|
||||
Self {
|
||||
clientsecret_sessionid: db["clientsecret_sessionid"].clone(),
|
||||
sessionid_session: db["sessionid_session"].clone(),
|
||||
sessionid_token: db["sessionid_token"].clone(),
|
||||
clientsecret_sessionid: db["clientsecret_validationsessionid"].clone(),
|
||||
sessionid_session: db["validationsessionid_session"].clone(),
|
||||
sessionid_token: db["validationsessionid_token"].clone(),
|
||||
localpart_email: db["localpart_email"].clone(),
|
||||
email_localpart: db["email_localpart"].clone(),
|
||||
}
|
||||
@@ -74,8 +75,8 @@ impl Data {
|
||||
pub(super) fn create_session(
|
||||
&self,
|
||||
email: Address,
|
||||
session_id: String,
|
||||
client_secret: String,
|
||||
session_id: OwnedSessionId,
|
||||
client_secret: OwnedClientSecret,
|
||||
token: ValidationToken,
|
||||
) {
|
||||
let session = ValidationSession {
|
||||
@@ -103,7 +104,7 @@ impl Data {
|
||||
/// Get a validation session by client secret.
|
||||
pub(super) async fn get_session_by_secret(
|
||||
&self,
|
||||
client_secret: &str,
|
||||
client_secret: &ClientSecret,
|
||||
) -> Option<ValidationSession> {
|
||||
let session_id: String = self
|
||||
.clientsecret_sessionid
|
||||
|
||||
+60
-26
@@ -10,15 +10,16 @@ use crate::{
|
||||
};
|
||||
|
||||
mod data;
|
||||
use conduwuit::{Err, Result, utils};
|
||||
use conduwuit::{Err, Result, result::FlatOk, utils};
|
||||
use data::{Data, ValidationToken};
|
||||
use database::Deserialized;
|
||||
use lettre::{Address, message::Mailbox};
|
||||
use ruma::{ClientSecret, OwnedClientSecret, OwnedSessionId};
|
||||
|
||||
pub struct Service {
|
||||
db: Data,
|
||||
services: Services,
|
||||
send_attempts: Mutex<HashMap<(String, Address), usize>>,
|
||||
send_attempts: Mutex<HashMap<(OwnedClientSecret, Address), usize>>,
|
||||
}
|
||||
|
||||
struct Services {
|
||||
@@ -45,20 +46,23 @@ impl Service {
|
||||
const RANDOM_SID_LENGTH: usize = 16;
|
||||
const VALIDATION_URL_PATH: &str = "/_continuwuity/3pid/email/validate";
|
||||
|
||||
#[must_use]
|
||||
pub fn generate_session_id() -> OwnedSessionId {
|
||||
OwnedSessionId::parse(utils::random_string(Self::RANDOM_SID_LENGTH)).unwrap()
|
||||
}
|
||||
|
||||
/// Send a validation message to an email address.
|
||||
///
|
||||
/// Returns the validation session ID on success.
|
||||
#[allow(clippy::impl_trait_in_params)]
|
||||
pub async fn send_validation_email<Template: MessageTemplate>(
|
||||
&self,
|
||||
recipient: Address,
|
||||
recipient: Mailbox,
|
||||
prepare_body: impl FnOnce(String) -> Template,
|
||||
client_secret: &str,
|
||||
client_secret: &ClientSecret,
|
||||
send_attempt: usize,
|
||||
) -> Result<String> {
|
||||
let Some(mailer) = self.services.mailer.mailer() else {
|
||||
return Err!("SMTP is not configured");
|
||||
};
|
||||
) -> Result<OwnedSessionId> {
|
||||
let mailer = self.services.mailer.expect_mailer()?;
|
||||
|
||||
let (session_id, ValidationToken { token, .. }) =
|
||||
match self.db.get_session_by_secret(client_secret).await {
|
||||
@@ -102,11 +106,11 @@ impl Service {
|
||||
},
|
||||
// If no session exists, create a new one.
|
||||
| None => {
|
||||
let session_id = utils::random_string(Self::RANDOM_SID_LENGTH);
|
||||
let session_id = Self::generate_session_id();
|
||||
let token = ValidationToken::new_random();
|
||||
|
||||
self.db.create_session(
|
||||
recipient.clone(),
|
||||
recipient.email.clone(),
|
||||
session_id.clone(),
|
||||
client_secret.to_owned(),
|
||||
token.clone(),
|
||||
@@ -125,11 +129,9 @@ impl Service {
|
||||
|
||||
validation_url
|
||||
.query_pairs_mut()
|
||||
.append_pair("session_id", &session_id)
|
||||
.append_pair("client_secret", client_secret)
|
||||
.append_pair("session_id", session_id.as_ref())
|
||||
.append_pair("token", &token);
|
||||
|
||||
let recipient = Mailbox::new(None, recipient);
|
||||
let message = prepare_body(validation_url.to_string());
|
||||
|
||||
mailer.send(recipient, message).await?;
|
||||
@@ -137,10 +139,10 @@ impl Service {
|
||||
Ok(session_id)
|
||||
}
|
||||
|
||||
/// Attempt to mark a validation session as valid using a validation token.
|
||||
pub async fn try_validate_session(
|
||||
&self,
|
||||
session_id: &str,
|
||||
client_secret: &str,
|
||||
supplied_token: &str,
|
||||
) -> Result<(), Cow<'static, str>> {
|
||||
let Some(session) = self.db.get_session(session_id).await else {
|
||||
@@ -151,15 +153,12 @@ impl Service {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if session.client_secret != client_secret {
|
||||
return Err("Invalid client secret for session".into());
|
||||
}
|
||||
|
||||
let token = self
|
||||
.db
|
||||
.get_session_validation_token(&session)
|
||||
.await
|
||||
.expect("valid session should have a token");
|
||||
|
||||
if token != *supplied_token || !token.is_valid() {
|
||||
return Err("Validation token is invalid or expired, please request a new one".into());
|
||||
}
|
||||
@@ -169,10 +168,12 @@ impl Service {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Consume a validated validation session, removing it from the database
|
||||
/// and returning the newly validated email address.
|
||||
pub async fn consume_valid_session(
|
||||
&self,
|
||||
session_id: &str,
|
||||
client_secret: &str,
|
||||
client_secret: &ClientSecret,
|
||||
) -> Result<Address, Cow<'static, str>> {
|
||||
let Some(session) = self.db.get_session(session_id).await else {
|
||||
return Err("Validation session does not exist".into());
|
||||
@@ -183,14 +184,38 @@ impl Service {
|
||||
self.db.remove_session(session).await;
|
||||
Ok(email)
|
||||
} else {
|
||||
Err("Validation failed. Did you use the link that was sent to you?".into())
|
||||
Err("This email address has not been validated. Did you use the link that was sent \
|
||||
to you?"
|
||||
.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Associate a localpart with an email address.
|
||||
pub fn associate_localpart_email(&self, localpart: &str, email: Address) {
|
||||
self.db.localpart_email.raw_put(localpart, &email);
|
||||
self.db.email_localpart.put_raw(email, localpart);
|
||||
pub async fn associate_localpart_email(
|
||||
&self,
|
||||
localpart: &str,
|
||||
email: &Address,
|
||||
) -> Result<()> {
|
||||
match self.get_localpart_for_email(email).await {
|
||||
| Some(existing_localpart) if existing_localpart != localpart => {
|
||||
// Another account is already using the supplied email
|
||||
|
||||
Err!(Request(ThreepidInUse("This email address is already in use.")))
|
||||
},
|
||||
| Some(_) => {
|
||||
// The supplied localpart is already associated with the supplied email,
|
||||
// no changes are necessary
|
||||
Ok(())
|
||||
},
|
||||
| None => {
|
||||
// The supplied email is not already in use
|
||||
|
||||
let email: &str = email.as_ref();
|
||||
self.db.localpart_email.insert(localpart, email);
|
||||
self.db.email_localpart.insert(email, localpart);
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Given a localpart, remove its corresponding email address.
|
||||
@@ -203,7 +228,9 @@ impl Service {
|
||||
.await
|
||||
.expect("localpart has no email associated");
|
||||
self.db.localpart_email.remove(localpart);
|
||||
self.db.email_localpart.del(&email);
|
||||
self.db
|
||||
.email_localpart
|
||||
.remove(<Address as AsRef<str>>::as_ref(&email));
|
||||
}
|
||||
|
||||
/// Get the email associated with a localpart, if one exists.
|
||||
@@ -212,12 +239,19 @@ impl Service {
|
||||
.localpart_email
|
||||
.get(localpart)
|
||||
.await
|
||||
.deserialized()
|
||||
.deserialized::<String>()
|
||||
.ok()
|
||||
.map(TryInto::try_into)
|
||||
.flat_ok()
|
||||
}
|
||||
|
||||
/// Get the localpart associated with an email, if one exists.
|
||||
pub async fn get_localpart_for_email(&self, email: &Address) -> Option<String> {
|
||||
self.db.email_localpart.qry(email).await.deserialized().ok()
|
||||
self.db
|
||||
.email_localpart
|
||||
.get(<Address as AsRef<str>>::as_ref(email))
|
||||
.await
|
||||
.deserialized()
|
||||
.ok()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,7 +301,7 @@ impl Service {
|
||||
match self
|
||||
.services
|
||||
.threepid
|
||||
.consume_valid_session(sid.as_str(), client_secret.as_str())
|
||||
.consume_valid_session(sid.as_str(), client_secret)
|
||||
.await
|
||||
{
|
||||
| Ok(email) => {
|
||||
|
||||
Reference in New Issue
Block a user