diff --git a/src/main/java/io/kamax/mxisd/HttpMxisd.java b/src/main/java/io/kamax/mxisd/HttpMxisd.java
index a17cd87..362db5d 100644
--- a/src/main/java/io/kamax/mxisd/HttpMxisd.java
+++ b/src/main/java/io/kamax/mxisd/HttpMxisd.java
@@ -33,6 +33,7 @@ import io.kamax.mxisd.http.undertow.handler.directory.v1.UserDirectorySearchHand
import io.kamax.mxisd.http.undertow.handler.identity.v1.*;
import io.kamax.mxisd.http.undertow.handler.profile.v1.InternalProfileHandler;
import io.kamax.mxisd.http.undertow.handler.profile.v1.ProfileHandler;
+import io.kamax.mxisd.http.undertow.handler.register.v1.Register3pidRequestTokenHandler;
import io.kamax.mxisd.http.undertow.handler.status.StatusHandler;
import io.undertow.Handlers;
import io.undertow.Undertow;
@@ -97,6 +98,9 @@ public class HttpMxisd {
.get(ProfileHandler.Path, SaneHandler.around(new ProfileHandler(m.getProfile())))
.get(InternalProfileHandler.Path, SaneHandler.around(new InternalProfileHandler(m.getProfile())))
+ // Registration endpoints
+ .post(Register3pidRequestTokenHandler.Path, SaneHandler.around(new Register3pidRequestTokenHandler(m.getReg(), m.getClientDns(), m.getHttpClient())))
+
// Application Service endpoints
.get("/_matrix/app/v1/users/**", asNotFoundHandler)
.get("/users/**", asNotFoundHandler) // Legacy endpoint
diff --git a/src/main/java/io/kamax/mxisd/Mxisd.java b/src/main/java/io/kamax/mxisd/Mxisd.java
index 31f37f9..ea9c3eb 100644
--- a/src/main/java/io/kamax/mxisd/Mxisd.java
+++ b/src/main/java/io/kamax/mxisd/Mxisd.java
@@ -44,6 +44,7 @@ import io.kamax.mxisd.notification.NotificationHandlers;
import io.kamax.mxisd.notification.NotificationManager;
import io.kamax.mxisd.profile.ProfileManager;
import io.kamax.mxisd.profile.ProfileProviders;
+import io.kamax.mxisd.registration.RegistrationManager;
import io.kamax.mxisd.session.SessionManager;
import io.kamax.mxisd.storage.IStorage;
import io.kamax.mxisd.storage.crypto.Ed25519KeyManager;
@@ -66,6 +67,7 @@ public class Mxisd {
private Ed25519KeyManager keyMgr;
private SignatureManager signMgr;
+ private ClientDnsOverwrite clientDns;
// Features
private AuthManager authMgr;
@@ -76,6 +78,7 @@ public class Mxisd {
private AppSvcManager asHander;
private SessionManager sessMgr;
private NotificationManager notifMgr;
+ private RegistrationManager regMgr;
public Mxisd(MxisdConfig cfg) {
this.cfg = cfg.build();
@@ -94,7 +97,7 @@ public class Mxisd {
store = new OrmLiteSqlStorage(cfg);
keyMgr = CryptoFactory.getKeyManager(cfg.getKey());
signMgr = CryptoFactory.getSignatureManager(keyMgr);
- ClientDnsOverwrite clientDns = new ClientDnsOverwrite(cfg.getDns().getOverwrite());
+ clientDns = new ClientDnsOverwrite(cfg.getDns().getOverwrite());
FederationDnsOverwrite fedDns = new FederationDnsOverwrite(cfg.getDns().getOverwrite());
Synapse synapse = new Synapse(cfg.getSynapseSql());
BridgeFetcher bridgeFetcher = new BridgeFetcher(cfg.getLookup().getRecursive().getBridge(), srvFetcher);
@@ -109,6 +112,7 @@ public class Mxisd {
invMgr = new InvitationManager(cfg, store, idStrategy, keyMgr, signMgr, fedDns, notifMgr);
authMgr = new AuthManager(cfg, AuthProviders.get(), idStrategy, invMgr, clientDns, httpClient);
dirMgr = new DirectoryManager(cfg.getDirectory(), clientDns, httpClient, DirectoryProviders.get());
+ regMgr = new RegistrationManager(httpClient, clientDns, idStrategy, invMgr);
asHander = new AppSvcManager(cfg, store, pMgr, notifMgr, synapse);
}
@@ -120,6 +124,10 @@ public class Mxisd {
return httpClient;
}
+ public ClientDnsOverwrite getClientDns() {
+ return clientDns;
+ }
+
public IRemoteIdentityServerFetcher getServerFetcher() {
return srvFetcher;
}
@@ -156,6 +164,10 @@ public class Mxisd {
return signMgr;
}
+ public RegistrationManager getReg() {
+ return regMgr;
+ }
+
public AppSvcManager getAs() {
return asHander;
}
diff --git a/src/main/java/io/kamax/mxisd/http/undertow/handler/register/v1/Register3pidRequestTokenHandler.java b/src/main/java/io/kamax/mxisd/http/undertow/handler/register/v1/Register3pidRequestTokenHandler.java
new file mode 100644
index 0000000..af2dbcb
--- /dev/null
+++ b/src/main/java/io/kamax/mxisd/http/undertow/handler/register/v1/Register3pidRequestTokenHandler.java
@@ -0,0 +1,101 @@
+/*
+ * mxisd - Matrix Identity Server Daemon
+ * Copyright (C) 2019 Kamax Sarl
+ *
+ * https://www.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.http.undertow.handler.register.v1;
+
+import com.google.gson.JsonObject;
+import io.kamax.matrix.ThreePid;
+import io.kamax.matrix.ThreePidMedium;
+import io.kamax.matrix.json.GsonUtil;
+import io.kamax.mxisd.dns.ClientDnsOverwrite;
+import io.kamax.mxisd.exception.InternalServerError;
+import io.kamax.mxisd.exception.NotAllowedException;
+import io.kamax.mxisd.http.io.identity.SessionEmailTokenRequestJson;
+import io.kamax.mxisd.http.io.identity.SessionPhoneTokenRequestJson;
+import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
+import io.kamax.mxisd.registration.RegistrationManager;
+import io.kamax.mxisd.util.RestClientUtils;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.util.HttpString;
+import org.apache.http.Header;
+import org.apache.http.HeaderElement;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.net.URI;
+
+public class Register3pidRequestTokenHandler extends BasicHttpHandler {
+
+ public static final String Key = "medium";
+ public static final String Path = "/_matrix/client/r0/register/{" + Key + "}/requestToken";
+
+ private static final Logger log = LoggerFactory.getLogger(Register3pidRequestTokenHandler.class);
+
+ private final RegistrationManager mgr;
+ private final ClientDnsOverwrite dns;
+ private final CloseableHttpClient client;
+
+ public Register3pidRequestTokenHandler(RegistrationManager mgr, ClientDnsOverwrite dns, CloseableHttpClient client) {
+ this.mgr = mgr;
+ this.dns = dns; // FIXME this shouldn't be in here but in the manager
+ this.client = client; // FIXME this shouldn't be in here but in the manager
+ }
+
+ @Override
+ public void handleRequest(HttpServerExchange exchange) {
+ JsonObject body = parseJsonObject(exchange);
+
+ String medium = getPathVariable(exchange, Key);
+ String address = GsonUtil.findString(body, "address").orElse("");
+ if (ThreePidMedium.Email.is(medium)) {
+ address = GsonUtil.get().fromJson(body, SessionEmailTokenRequestJson.class).getValue();
+ } else if (ThreePidMedium.PhoneNumber.is(medium)) {
+ address = GsonUtil.get().fromJson(body, SessionPhoneTokenRequestJson.class).getValue();
+ } else {
+ log.warn("Unsupported 3PID medium. We attempted to extract the address but the call might fail");
+ }
+
+ ThreePid tpid = new ThreePid(medium, address);
+ if (!mgr.allow(tpid)) {
+ throw new NotAllowedException("Your " + medium + " address cannot be used for registration");
+ }
+
+ String target = dns.transform(URI.create(exchange.getRequestURL())).toString();
+ log.info("Requesting remote: {}", target);
+ HttpPost req = RestClientUtils.post(target, GsonUtil.get(), body);
+ try (CloseableHttpResponse res = client.execute(req)) {
+ exchange.setStatusCode(res.getStatusLine().getStatusCode());
+ for (Header h : res.getAllHeaders()) {
+ for (HeaderElement el : h.getElements()) {
+ exchange.getResponseHeaders().add(HttpString.tryFromString(h.getName()), el.getValue());
+ }
+ }
+ res.getEntity().writeTo(exchange.getOutputStream());
+ exchange.endExchange();
+ } catch (IOException e) {
+ throw new InternalServerError(e);
+ }
+ }
+
+}
diff --git a/src/main/java/io/kamax/mxisd/invitation/InvitationManager.java b/src/main/java/io/kamax/mxisd/invitation/InvitationManager.java
index 7dca267..36938f3 100644
--- a/src/main/java/io/kamax/mxisd/invitation/InvitationManager.java
+++ b/src/main/java/io/kamax/mxisd/invitation/InvitationManager.java
@@ -23,6 +23,7 @@ package io.kamax.mxisd.invitation;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import io.kamax.matrix.MatrixID;
+import io.kamax.matrix.ThreePid;
import io.kamax.matrix.json.GsonUtil;
import io.kamax.mxisd.config.InvitationConfig;
import io.kamax.mxisd.config.MxisdConfig;
@@ -266,6 +267,22 @@ public class InvitationManager {
return reply;
}
+ public boolean hasInvite(ThreePid tpid) {
+ for (IThreePidInviteReply reply : invitations.values()) {
+ if (!StringUtils.equals(tpid.getMedium(), reply.getInvite().getMedium())) {
+ continue;
+ }
+
+ if (!StringUtils.equals(tpid.getAddress(), reply.getInvite().getAddress())) {
+ continue;
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
public void lookupMappingsForInvites() {
if (!invitations.isEmpty()) {
log.info("Checking for existing mapping for pending invites");
diff --git a/src/main/java/io/kamax/mxisd/registration/RegistrationManager.java b/src/main/java/io/kamax/mxisd/registration/RegistrationManager.java
new file mode 100644
index 0000000..63ecbc9
--- /dev/null
+++ b/src/main/java/io/kamax/mxisd/registration/RegistrationManager.java
@@ -0,0 +1,102 @@
+/*
+ * mxisd - Matrix Identity Server Daemon
+ * Copyright (C) 2019 Kamax Sarl
+ *
+ * https://www.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.registration;
+
+import com.google.gson.JsonObject;
+import io.kamax.matrix.ThreePid;
+import io.kamax.matrix.json.GsonUtil;
+import io.kamax.mxisd.dns.ClientDnsOverwrite;
+import io.kamax.mxisd.exception.NotImplementedException;
+import io.kamax.mxisd.exception.RemoteHomeServerException;
+import io.kamax.mxisd.invitation.InvitationManager;
+import io.kamax.mxisd.lookup.strategy.LookupStrategy;
+import io.kamax.mxisd.util.RestClientUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.utils.URIBuilder;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.util.EntityUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class RegistrationManager {
+
+ private static final Logger log = LoggerFactory.getLogger(RegistrationManager.class);
+
+ private final CloseableHttpClient client;
+ private final ClientDnsOverwrite dns;
+ private final LookupStrategy lookup;
+ private final InvitationManager invMgr;
+
+ private Map sessions = new ConcurrentHashMap<>();
+
+ public RegistrationManager(CloseableHttpClient client, ClientDnsOverwrite dns, LookupStrategy lookup, InvitationManager invMgr) {
+ this.client = client;
+ this.dns = dns;
+ this.lookup = lookup;
+ this.invMgr = invMgr;
+ }
+
+ private String resolveProxyUrl(URI target) {
+ URIBuilder builder = dns.transform(target);
+ String urlToLogin = builder.toString();
+ log.info("Proxy resolution: {} to {}", target.toString(), urlToLogin);
+ return urlToLogin;
+ }
+
+ public RegistrationReply execute(URI target, JsonObject request) {
+ HttpPost registerProxyRq = RestClientUtils.post(resolveProxyUrl(target), GsonUtil.get(), request);
+ try (CloseableHttpResponse response = client.execute(registerProxyRq)) {
+ int status = response.getStatusLine().getStatusCode();
+ if (status == 200) {
+ // The user managed to register. We check if it had a session
+ String sessionId = GsonUtil.findObj(request, "auth").flatMap(auth -> GsonUtil.findString(auth, "session")).orElse("");
+ if (StringUtils.isEmpty(sessionId)) {
+ // No session ID was provided. This is an edge case we do not support for now as investigation is needed
+ // to ensure how and when this happens.
+
+ HttpPost newSessReq = RestClientUtils.post(resolveProxyUrl(target), GsonUtil.get(), new JsonObject());
+ try (CloseableHttpResponse newSessRes = client.execute(newSessReq)) {
+ RegistrationReply reply = new RegistrationReply();
+ reply.setStatus(newSessRes.getStatusLine().getStatusCode());
+ reply.setBody(GsonUtil.parseObj(EntityUtils.toString(newSessRes.getEntity())));
+ return reply;
+ }
+ }
+ }
+
+ throw new NotImplementedException("Registration");
+ } catch (IOException e) {
+ throw new RemoteHomeServerException(e.getMessage());
+ }
+ }
+
+ public boolean allow(ThreePid tpid) {
+ return invMgr.hasInvite(tpid);
+ }
+
+}
diff --git a/src/main/java/io/kamax/mxisd/registration/RegistrationReply.java b/src/main/java/io/kamax/mxisd/registration/RegistrationReply.java
new file mode 100644
index 0000000..b2e2dde
--- /dev/null
+++ b/src/main/java/io/kamax/mxisd/registration/RegistrationReply.java
@@ -0,0 +1,46 @@
+/*
+ * mxisd - Matrix Identity Server Daemon
+ * Copyright (C) 2019 Kamax Sarl
+ *
+ * https://www.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.registration;
+
+import com.google.gson.JsonObject;
+
+public class RegistrationReply {
+
+ private int status;
+ private JsonObject body;
+
+ public int getStatus() {
+ return status;
+ }
+
+ public void setStatus(int status) {
+ this.status = status;
+ }
+
+ public JsonObject getBody() {
+ return body;
+ }
+
+ public void setBody(JsonObject body) {
+ this.body = body;
+ }
+
+}