From c1746697b94f1f2f454afc769647ef282b3b0ebb Mon Sep 17 00:00:00 2001 From: Maxime Dor Date: Tue, 19 Sep 2017 03:46:31 +0200 Subject: [PATCH] Split template creation and 3PID connector to integrate bindings verification --- .../invite/medium/EmailInviteConfig.java | 101 +++++++++++++++++ .../connector/EmailSmtpConfig.java} | 56 ++------- .../mxisd/invitation/InvitationManager.java | 50 ++++++--- .../EmailInviteContentGenerator.java} | 73 +++--------- .../IInviteContentGenerator.java} | 6 +- .../connector/EmailSmtpConnector.java | 106 ++++++++++++++++++ .../connector/IThreePidConnector.java | 31 +++++ src/main/resources/application.yaml | 15 ++- 8 files changed, 309 insertions(+), 129 deletions(-) create mode 100644 src/main/groovy/io/kamax/mxisd/config/invite/medium/EmailInviteConfig.java rename src/main/groovy/io/kamax/mxisd/config/{invite/sender/EmailSenderConfig.java => threepid/connector/EmailSmtpConfig.java} (56%) rename src/main/groovy/io/kamax/mxisd/invitation/{sender/EmailInviteSender.java => generator/EmailInviteContentGenerator.java} (58%) rename src/main/groovy/io/kamax/mxisd/invitation/{sender/IInviteSender.java => generator/IInviteContentGenerator.java} (86%) create mode 100644 src/main/groovy/io/kamax/mxisd/threepid/connector/EmailSmtpConnector.java create mode 100644 src/main/groovy/io/kamax/mxisd/threepid/connector/IThreePidConnector.java diff --git a/src/main/groovy/io/kamax/mxisd/config/invite/medium/EmailInviteConfig.java b/src/main/groovy/io/kamax/mxisd/config/invite/medium/EmailInviteConfig.java new file mode 100644 index 0000000..76b79c2 --- /dev/null +++ b/src/main/groovy/io/kamax/mxisd/config/invite/medium/EmailInviteConfig.java @@ -0,0 +1,101 @@ +/* + * mxisd - Matrix Identity Server Daemon + * Copyright (C) 2017 Maxime Dor + * + * https://max.kamax.io/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package io.kamax.mxisd.config.invite.medium; + +import io.kamax.mxisd.config.MatrixConfig; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.WordUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.PostConstruct; +import java.io.File; + +@Configuration +@ConfigurationProperties("invite.medium.email") +public class EmailInviteConfig { + + private Logger log = LoggerFactory.getLogger(EmailInviteConfig.class); + + private MatrixConfig mxCfg; + + private String from; + private String name; + private String template; + + @Autowired + public EmailInviteConfig(MatrixConfig mxCfg) { + this.mxCfg = mxCfg; + } + + public String getFrom() { + return from; + } + + public void setFrom(String from) { + this.from = from; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getTemplate() { + return template; + } + + public void setTemplate(String template) { + this.template = template; + } + + @PostConstruct + public void build() { + log.info("--- E-mail invites config ---"); + log.info("From: {}", getFrom()); + + if (StringUtils.isBlank(getName())) { + setName(WordUtils.capitalize(mxCfg.getDomain()) + " Identity Server"); + } + log.info("Name: {}", getName()); + + if (!StringUtils.startsWith(getTemplate(), "classpath:")) { + if (StringUtils.isBlank(getTemplate())) { + log.warn("invite.medium.email is empty! Will not send invites"); + } else { + File cp = new File(getTemplate()).getAbsoluteFile(); + log.info("Template: {}", cp.getAbsolutePath()); + if (!cp.exists() || !cp.isFile() || !cp.canRead()) { + log.warn(getTemplate() + " does not exist, is not a file or cannot be read"); + } + } + } else { + log.info("Template: Built-in: {}", getTemplate()); + } + } + +} diff --git a/src/main/groovy/io/kamax/mxisd/config/invite/sender/EmailSenderConfig.java b/src/main/groovy/io/kamax/mxisd/config/threepid/connector/EmailSmtpConfig.java similarity index 56% rename from src/main/groovy/io/kamax/mxisd/config/invite/sender/EmailSenderConfig.java rename to src/main/groovy/io/kamax/mxisd/config/threepid/connector/EmailSmtpConfig.java index 8a0fa67..24a5e49 100644 --- a/src/main/groovy/io/kamax/mxisd/config/invite/sender/EmailSenderConfig.java +++ b/src/main/groovy/io/kamax/mxisd/config/threepid/connector/EmailSmtpConfig.java @@ -18,7 +18,7 @@ * along with this program. If not, see . */ -package io.kamax.mxisd.config.invite.sender; +package io.kamax.mxisd.config.threepid.connector; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; @@ -27,22 +27,18 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; import javax.annotation.PostConstruct; -import java.io.File; @Configuration -@ConfigurationProperties(prefix = "invite.sender.email") -public class EmailSenderConfig { +@ConfigurationProperties(prefix = "threepid.email.connector.provider.smtp") +public class EmailSmtpConfig { - private Logger log = LoggerFactory.getLogger(EmailSenderConfig.class); + private Logger log = LoggerFactory.getLogger(EmailSmtpConfig.class); private String host; private int port; private int tls; private String login; private String password; - private String email; - private String name; - private String template; public String getHost() { return host; @@ -84,52 +80,14 @@ public class EmailSenderConfig { this.password = password; } - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getTemplate() { - return template; - } - - public void setTemplate(String template) { - this.template = template; - } - @PostConstruct - private void postConstruct() { - log.info("--- E-mail Invite Sender config ---"); + public void build() { + log.info("--- E-mail SMTP Connector config ---"); log.info("Host: {}", getHost()); log.info("Port: {}", getPort()); log.info("TLS Mode: {}", getTls()); log.info("Login: {}", getLogin()); - log.info("Has password: {}", !StringUtils.isBlank(getPassword())); - log.info("E-mail: {}", getEmail()); - if (!StringUtils.startsWith(getTemplate(), "classpath:")) { - if (StringUtils.isBlank(getTemplate())) { - log.warn("invite.sender.template is empty! Will not send invites"); - } else { - File cp = new File(getTemplate()).getAbsoluteFile(); - log.info("Template: {}", cp.getAbsolutePath()); - if (!cp.exists() || !cp.isFile() || !cp.canRead()) { - log.warn(getTemplate() + " does not exist, is not a file or cannot be read"); - } - } - } else { - log.info("Template: Built-in"); - } + log.info("Has password: {}", StringUtils.isNotBlank(getPassword())); } } diff --git a/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java b/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java index f9bab89..d5502d3 100644 --- a/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java +++ b/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java @@ -26,13 +26,14 @@ import io.kamax.mxisd.config.DnsOverwrite; import io.kamax.mxisd.config.DnsOverwriteEntry; import io.kamax.mxisd.exception.BadRequestException; import io.kamax.mxisd.exception.MappingAlreadyExistsException; -import io.kamax.mxisd.invitation.sender.IInviteSender; +import io.kamax.mxisd.invitation.generator.IInviteContentGenerator; import io.kamax.mxisd.lookup.SingleLookupReply; import io.kamax.mxisd.lookup.ThreePidMapping; import io.kamax.mxisd.lookup.strategy.LookupStrategy; import io.kamax.mxisd.signature.SignatureManager; import io.kamax.mxisd.storage.IStorage; import io.kamax.mxisd.storage.ormlite.ThreePidInviteIO; +import io.kamax.mxisd.threepid.connector.IThreePidConnector; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.RandomStringUtils; import org.apache.commons.lang.StringUtils; @@ -83,14 +84,35 @@ public class InvitationManager { @Autowired private DnsOverwrite dns; - private Map senders; + private Map generators; + private Map connectors; private CloseableHttpClient client; private Gson gson; private Timer refreshTimer; - private String getId(IThreePidInvite invite) { - return invite.getSender().getDomain().toLowerCase() + invite.getMedium().toLowerCase() + invite.getAddress().toLowerCase(); + @Autowired + public InvitationManager( + List generatorList, + List connectorList + ) { + generators = new HashMap<>(); + generatorList.forEach(sender -> { // FIXME to support several possible implementations + if (generators.containsKey(sender.getMedium())) { + throw new RuntimeException("More than one " + sender.getMedium() + " content generator"); + } + + generators.put(sender.getMedium(), sender); + }); + + connectors = new HashMap<>(); + connectorList.forEach(connector -> { // FIXME to support several possible implementations + if (connectors.containsKey(connector.getMedium())) { + throw new RuntimeException("More than one " + connector.getMedium() + " connector"); + } + + connectors.put(connector.getMedium(), connector); + }); } @PostConstruct @@ -140,9 +162,14 @@ public class InvitationManager { @PreDestroy private void preDestroy() { + refreshTimer.cancel(); ForkJoinPool.commonPool().awaitQuiescence(1, TimeUnit.MINUTES); } + private String getId(IThreePidInvite invite) { + return invite.getSender().getDomain().toLowerCase() + invite.getMedium().toLowerCase() + invite.getAddress().toLowerCase(); + } + private String getIdForLog(IThreePidInviteReply reply) { return reply.getInvite().getSender().getId() + ":" + reply.getInvite().getRoomId() + ":" + reply.getInvite().getMedium() + ":" + reply.getInvite().getAddress(); } @@ -193,21 +220,16 @@ public class InvitationManager { return "https://" + domain + ":8448"; } - @Autowired - public InvitationManager(List senderList) { - senders = new HashMap<>(); - senderList.forEach(sender -> senders.put(sender.getMedium(), sender)); - } - public synchronized IThreePidInviteReply storeInvite(IThreePidInvite invitation) { // TODO better sync - IInviteSender sender = senders.get(invitation.getMedium()); - if (sender == null) { + IInviteContentGenerator generator = generators.get(invitation.getMedium()); + IThreePidConnector connector = connectors.get(invitation.getMedium()); + if (generator == null || connector == null) { throw new BadRequestException("Medium type " + invitation.getMedium() + " is not supported"); } String invId = getId(invitation); log.info("Handling invite for {}:{} from {} in room {}", invitation.getMedium(), invitation.getAddress(), invitation.getSender(), invitation.getRoomId()); - if (invitations.containsKey(invId)) { // FIXME we need to lookup using the HS domain too!! + if (invitations.containsKey(invId)) { log.info("Invite is already pending for {}:{}, returning data", invitation.getMedium(), invitation.getAddress()); return invitations.get(invId); } @@ -224,7 +246,7 @@ public class InvitationManager { IThreePidInviteReply reply = new ThreePidInviteReply(invId, invitation, token, displayName); log.info("Performing invite to {}:{}", invitation.getMedium(), invitation.getAddress()); - sender.send(reply); + connector.send(reply, generator.generate(reply)); log.info("Storing invite under ID {}", invId); storage.insertInvite(reply); diff --git a/src/main/groovy/io/kamax/mxisd/invitation/sender/EmailInviteSender.java b/src/main/groovy/io/kamax/mxisd/invitation/generator/EmailInviteContentGenerator.java similarity index 58% rename from src/main/groovy/io/kamax/mxisd/invitation/sender/EmailInviteSender.java rename to src/main/groovy/io/kamax/mxisd/invitation/generator/EmailInviteContentGenerator.java index 8a67ac2..bcd1e44 100644 --- a/src/main/groovy/io/kamax/mxisd/invitation/sender/EmailInviteSender.java +++ b/src/main/groovy/io/kamax/mxisd/invitation/generator/EmailInviteContentGenerator.java @@ -18,61 +18,35 @@ * along with this program. If not, see . */ -package io.kamax.mxisd.invitation.sender; +package io.kamax.mxisd.invitation.generator; -import com.sun.mail.smtp.SMTPTransport; import io.kamax.matrix.ThreePidMedium; import io.kamax.mxisd.config.MatrixConfig; -import io.kamax.mxisd.config.invite.sender.EmailSenderConfig; -import io.kamax.mxisd.exception.ConfigurationException; +import io.kamax.mxisd.config.invite.medium.EmailInviteConfig; import io.kamax.mxisd.invitation.IThreePidInviteReply; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.WordUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Component; -import javax.annotation.PostConstruct; -import javax.mail.Message; -import javax.mail.MessagingException; -import javax.mail.Session; -import javax.mail.internet.InternetAddress; -import javax.mail.internet.MimeMessage; import java.io.FileInputStream; import java.io.IOException; -import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; -import java.util.Date; @Component -public class EmailInviteSender implements IInviteSender { +public class EmailInviteContentGenerator implements IInviteContentGenerator { - private Logger log = LoggerFactory.getLogger(EmailInviteSender.class); - - @Autowired - private EmailSenderConfig cfg; - - @Autowired + private EmailInviteConfig cfg; private MatrixConfig mxCfg; - - @Autowired private ApplicationContext app; - private Session session; - private InternetAddress sender; - - @PostConstruct - private void postConstruct() { - try { - session = Session.getInstance(System.getProperties()); - sender = new InternetAddress(cfg.getEmail(), cfg.getName()); - } catch (UnsupportedEncodingException e) { - // What are we supposed to do with this?! - throw new ConfigurationException(e); - } + @Autowired + public EmailInviteContentGenerator(EmailInviteConfig cfg, MatrixConfig mxCfg, ApplicationContext app) { + this.cfg = cfg; + this.mxCfg = mxCfg; + this.app = app; } @Override @@ -81,7 +55,7 @@ public class EmailInviteSender implements IInviteSender { } @Override - public void send(IThreePidInviteReply invite) { + public String generate(IThreePidInviteReply invite) { if (!ThreePidMedium.Email.is(invite.getInvite().getMedium())) { throw new IllegalArgumentException(invite.getInvite().getMedium() + " is not a supported 3PID type"); } @@ -99,7 +73,7 @@ public class EmailInviteSender implements IInviteSender { StandardCharsets.UTF_8); templateBody = templateBody.replace("%DOMAIN%", mxCfg.getDomain()); templateBody = templateBody.replace("%DOMAIN_PRETTY%", domainPretty); - templateBody = templateBody.replace("%FROM_EMAIL%", cfg.getEmail()); + templateBody = templateBody.replace("%FROM_EMAIL%", cfg.getFrom()); templateBody = templateBody.replace("%FROM_NAME%", cfg.getName()); templateBody = templateBody.replace("%SENDER_ID%", invite.getInvite().getSender().getId()); templateBody = templateBody.replace("%SENDER_NAME%", senderName); @@ -110,28 +84,9 @@ public class EmailInviteSender implements IInviteSender { templateBody = templateBody.replace("%ROOM_NAME%", roomName); templateBody = templateBody.replace("%ROOM_NAME_OR_ID%", roomNameOrId); - MimeMessage msg = new MimeMessage(session, IOUtils.toInputStream(templateBody, StandardCharsets.UTF_8)); - msg.setHeader("X-Mailer", "mxisd"); // TODO set version - msg.setSentDate(new Date()); - msg.setFrom(sender); - msg.setRecipients(Message.RecipientType.TO, invite.getInvite().getAddress()); - msg.saveChanges(); - - log.info("Sending invite to {} via SMTP using {}:{}", invite.getInvite().getAddress(), cfg.getHost(), cfg.getPort()); - SMTPTransport transport = (SMTPTransport) session.getTransport("smtp"); - transport.setStartTLS(cfg.getTls() > 0); - transport.setRequireStartTLS(cfg.getTls() > 1); - - log.info("Connecting to {}:{}", cfg.getHost(), cfg.getPort()); - transport.connect(cfg.getHost(), cfg.getPort(), cfg.getLogin(), cfg.getPassword()); - try { - transport.sendMessage(msg, InternetAddress.parse(invite.getInvite().getAddress())); - log.info("Invite to {} was sent", invite.getInvite().getAddress()); - } finally { - transport.close(); - } - } catch (IOException | MessagingException e) { - throw new RuntimeException("Unable to send e-mail invite to " + invite.getInvite().getAddress(), e); + return templateBody; + } catch (IOException e) { + throw new RuntimeException("Unable to read template file", e); } } diff --git a/src/main/groovy/io/kamax/mxisd/invitation/sender/IInviteSender.java b/src/main/groovy/io/kamax/mxisd/invitation/generator/IInviteContentGenerator.java similarity index 86% rename from src/main/groovy/io/kamax/mxisd/invitation/sender/IInviteSender.java rename to src/main/groovy/io/kamax/mxisd/invitation/generator/IInviteContentGenerator.java index 14af7fd..a05b381 100644 --- a/src/main/groovy/io/kamax/mxisd/invitation/sender/IInviteSender.java +++ b/src/main/groovy/io/kamax/mxisd/invitation/generator/IInviteContentGenerator.java @@ -18,14 +18,14 @@ * along with this program. If not, see . */ -package io.kamax.mxisd.invitation.sender; +package io.kamax.mxisd.invitation.generator; import io.kamax.mxisd.invitation.IThreePidInviteReply; -public interface IInviteSender { +public interface IInviteContentGenerator { String getMedium(); - void send(IThreePidInviteReply invite); + String generate(IThreePidInviteReply invite); } diff --git a/src/main/groovy/io/kamax/mxisd/threepid/connector/EmailSmtpConnector.java b/src/main/groovy/io/kamax/mxisd/threepid/connector/EmailSmtpConnector.java new file mode 100644 index 0000000..b9d68e0 --- /dev/null +++ b/src/main/groovy/io/kamax/mxisd/threepid/connector/EmailSmtpConnector.java @@ -0,0 +1,106 @@ +/* + * mxisd - Matrix Identity Server Daemon + * Copyright (C) 2017 Maxime Dor + * + * https://max.kamax.io/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package io.kamax.mxisd.threepid.connector; + +import com.sun.mail.smtp.SMTPTransport; +import io.kamax.matrix.ThreePidMedium; +import io.kamax.mxisd.config.invite.medium.EmailInviteConfig; +import io.kamax.mxisd.config.threepid.connector.EmailSmtpConfig; +import io.kamax.mxisd.exception.ConfigurationException; +import io.kamax.mxisd.invitation.IThreePidInviteReply; +import org.apache.commons.io.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.mail.Message; +import javax.mail.MessagingException; +import javax.mail.Session; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeMessage; +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; +import java.util.Date; + +@Component +public class EmailSmtpConnector implements IThreePidConnector { + + private Logger log = LoggerFactory.getLogger(EmailSmtpConnector.class); + + private EmailSmtpConfig cfg; + private EmailInviteConfig invCfg; + + private Session session; + private InternetAddress sender; + + @Autowired + public EmailSmtpConnector(EmailSmtpConfig cfg, EmailInviteConfig invCfg) { + try { + session = Session.getInstance(System.getProperties()); + sender = new InternetAddress(invCfg.getFrom(), invCfg.getName()); + } catch (UnsupportedEncodingException e) { + // What are we supposed to do with this?! + throw new ConfigurationException(e); + } + + this.cfg = cfg; + this.invCfg = invCfg; + } + + @Override + public String getMedium() { + return ThreePidMedium.Email.getId(); + } + + @Override + public void send(IThreePidInviteReply invite, String content) { + if (!ThreePidMedium.Email.is(invite.getInvite().getMedium())) { + throw new IllegalArgumentException(invite.getInvite().getMedium() + " is not a supported 3PID type"); + } + + try { + MimeMessage msg = new MimeMessage(session, IOUtils.toInputStream(content, StandardCharsets.UTF_8)); + msg.setHeader("X-Mailer", "mxisd"); // TODO set version + msg.setSentDate(new Date()); + msg.setFrom(sender); + msg.setRecipients(Message.RecipientType.TO, invite.getInvite().getAddress()); + msg.saveChanges(); + + log.info("Sending invite to {} via SMTP using {}:{}", invite.getInvite().getAddress(), cfg.getHost(), cfg.getPort()); + SMTPTransport transport = (SMTPTransport) session.getTransport("smtp"); + transport.setStartTLS(cfg.getTls() > 0); + transport.setRequireStartTLS(cfg.getTls() > 1); + + log.info("Connecting to {}:{}", cfg.getHost(), cfg.getPort()); + transport.connect(cfg.getHost(), cfg.getPort(), cfg.getLogin(), cfg.getPassword()); + try { + transport.sendMessage(msg, InternetAddress.parse(invite.getInvite().getAddress())); + log.info("Invite to {} was sent", invite.getInvite().getAddress()); + } finally { + transport.close(); + } + } catch (MessagingException e) { + throw new RuntimeException("Unable to send e-mail invite to " + invite.getInvite().getAddress(), e); + } + } + +} diff --git a/src/main/groovy/io/kamax/mxisd/threepid/connector/IThreePidConnector.java b/src/main/groovy/io/kamax/mxisd/threepid/connector/IThreePidConnector.java new file mode 100644 index 0000000..07bdc1a --- /dev/null +++ b/src/main/groovy/io/kamax/mxisd/threepid/connector/IThreePidConnector.java @@ -0,0 +1,31 @@ +/* + * mxisd - Matrix Identity Server Daemon + * Copyright (C) 2017 Maxime Dor + * + * https://max.kamax.io/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package io.kamax.mxisd.threepid.connector; + +import io.kamax.mxisd.invitation.IThreePidInviteReply; + +public interface IThreePidConnector { + + String getMedium(); + + void send(IThreePidInviteReply invite, String content); + +} diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 87d88f1..8dc80b6 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -62,12 +62,19 @@ forward: - "https://matrix.org" - "https://vector.im" +threepid: + email: + connector: + active: 'smtp' + provider: + smtp: + port: 587 + tls: 1 + invite: - sender: + medium: email: - tls: 1 - name: "mxisd Identity Server" - template: "classpath:email/invite-template.eml" + template: 'classpath:email/invite-template.eml' storage: backend: 'sqlite'