diff --git a/build.gradle b/build.gradle index 59e6b9d..053837f 100644 --- a/build.gradle +++ b/build.gradle @@ -79,6 +79,9 @@ dependencies { // Spring Boot - standalone app compile 'org.springframework.boot:spring-boot-starter-web:1.5.3.RELEASE' + // Thymeleaf for HTML templates + compile "org.springframework.boot:spring-boot-starter-thymeleaf:1.5.3.RELEASE" + // Matrix Java SDK compile 'io.kamax:matrix-java-sdk:0.0.2' diff --git a/src/main/groovy/io/kamax/mxisd/config/ThymeleafConfig.java b/src/main/groovy/io/kamax/mxisd/config/ThymeleafConfig.java new file mode 100644 index 0000000..ecb967b --- /dev/null +++ b/src/main/groovy/io/kamax/mxisd/config/ThymeleafConfig.java @@ -0,0 +1,42 @@ +/* + * 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; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.thymeleaf.resourceresolver.FileResourceResolver; +import org.thymeleaf.templateresolver.TemplateResolver; + +@Configuration +public class ThymeleafConfig { + + @Bean + public TemplateResolver getFileSystemResolver() { + TemplateResolver resolver = new TemplateResolver(); + resolver.setPrefix(""); + resolver.setSuffix(""); + resolver.setCacheable(false); + resolver.setOrder(1); + resolver.setResourceResolver(new FileResourceResolver()); + return resolver; + } + +} diff --git a/src/main/groovy/io/kamax/mxisd/config/ViewConfig.java b/src/main/groovy/io/kamax/mxisd/config/ViewConfig.java new file mode 100644 index 0000000..2a6fa7c --- /dev/null +++ b/src/main/groovy/io/kamax/mxisd/config/ViewConfig.java @@ -0,0 +1,144 @@ +/* + * 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; + +import com.google.gson.Gson; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.PostConstruct; + +@Configuration +@ConfigurationProperties("view") +public class ViewConfig { + + private Logger log = LoggerFactory.getLogger(ViewConfig.class); + + public static class Session { + + public static class Paths { + + private String failure; + private String success; + + public String getFailure() { + return failure; + } + + public void setFailure(String failure) { + this.failure = failure; + } + + public String getSuccess() { + return success; + } + + public void setSuccess(String success) { + this.success = success; + } + + } + + public static class Local { + + private Paths onTokenSubmit = new Paths(); + + public Paths getOnTokenSubmit() { + return onTokenSubmit; + } + + public void setOnTokenSubmit(Paths onTokenSubmit) { + this.onTokenSubmit = onTokenSubmit; + } + + } + + public static class Remote { + + private Paths onRequest = new Paths(); + private Paths onCheck = new Paths(); + + public Paths getOnRequest() { + return onRequest; + } + + public void setOnRequest(Paths onRequest) { + this.onRequest = onRequest; + } + + public Paths getOnCheck() { + return onCheck; + } + + public void setOnCheck(Paths onCheck) { + this.onCheck = onCheck; + } + + } + + private Local local = new Local(); + private Local localRemote = new Local(); + private Remote remote = new Remote(); + + public Local getLocal() { + return local; + } + + public void setLocal(Local local) { + this.local = local; + } + + public Local getLocalRemote() { + return localRemote; + } + + public void setLocalRemote(Local localRemote) { + this.localRemote = localRemote; + } + + public Remote getRemote() { + return remote; + } + + public void setRemote(Remote remote) { + this.remote = remote; + } + } + + private Session session = new Session(); + + public Session getSession() { + return session; + } + + public void setSession(Session session) { + this.session = session; + } + + @PostConstruct + public void build() { + log.info("--- View config ---"); + log.info("Session: {}", new Gson().toJson(session)); + } + +} diff --git a/src/main/groovy/io/kamax/mxisd/controller/v1/SessionController.groovy b/src/main/groovy/io/kamax/mxisd/controller/v1/SessionController.groovy index f785e5a..718d671 100644 --- a/src/main/groovy/io/kamax/mxisd/controller/v1/SessionController.groovy +++ b/src/main/groovy/io/kamax/mxisd/controller/v1/SessionController.groovy @@ -20,146 +20,62 @@ package io.kamax.mxisd.controller.v1 -import com.google.gson.Gson -import com.google.gson.JsonObject -import io.kamax.matrix.ThreePidMedium -import io.kamax.mxisd.ThreePid -import io.kamax.mxisd.controller.v1.io.SessionEmailTokenRequestJson -import io.kamax.mxisd.controller.v1.io.SessionPhoneTokenRequestJson -import io.kamax.mxisd.exception.BadRequestException -import io.kamax.mxisd.exception.SessionNotValidatedException -import io.kamax.mxisd.invitation.InvitationManager -import io.kamax.mxisd.lookup.ThreePidValidation +import io.kamax.mxisd.config.ServerConfig +import io.kamax.mxisd.config.ViewConfig +import io.kamax.mxisd.controller.v1.remote.RemoteIdentityAPIv1 import io.kamax.mxisd.session.SessionMananger -import org.apache.commons.io.IOUtils -import org.apache.http.HttpStatus +import io.kamax.mxisd.session.ValidationResult import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired -import org.springframework.http.MediaType -import org.springframework.web.bind.annotation.* +import org.springframework.stereotype.Controller +import org.springframework.ui.Model +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam import javax.servlet.http.HttpServletRequest import javax.servlet.http.HttpServletResponse -import java.nio.charset.StandardCharsets -@RestController -@CrossOrigin -@RequestMapping(path = IdentityAPIv1.BASE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) +@Controller +@RequestMapping(path = IdentityAPIv1.BASE) class SessionController { + private Logger log = LoggerFactory.getLogger(SessionController.class) + + @Autowired + private ServerConfig srvCfg; + @Autowired private SessionMananger mgr @Autowired - private InvitationManager invMgr; - - private Gson gson = new Gson() - - private Logger log = LoggerFactory.getLogger(SessionController.class) - - private T fromJson(HttpServletRequest req, Class obj) { - gson.fromJson(new InputStreamReader(req.getInputStream(), StandardCharsets.UTF_8), obj) - } - - @RequestMapping(value = "/validate/{medium}/requestToken") - String init(HttpServletRequest request, HttpServletResponse response, @PathVariable String medium) { - log.info("Request {}: {}", request.getMethod(), request.getRequestURL(), request.getQueryString()) - if (ThreePidMedium.Email.is(medium)) { - SessionEmailTokenRequestJson req = fromJson(request, SessionEmailTokenRequestJson.class) - return gson.toJson(new Sid(mgr.create( - request.getRemoteHost(), - new ThreePid(req.getMedium(), req.getValue()), - req.getSecret(), - req.getAttempt(), - req.getNextLink()))); - } - - if (ThreePidMedium.PhoneNumber) { - SessionPhoneTokenRequestJson req = fromJson(request, SessionPhoneTokenRequestJson.class) - return gson.toJson(new Sid(mgr.create( - request.getRemoteHost(), - new ThreePid(req.getMedium(), req.getValue()), - req.getSecret(), - req.getAttempt(), - req.getNextLink()))); - } - - JsonObject obj = new JsonObject(); - obj.addProperty("errcode", "M_INVALID_3PID_TYPE") - obj.addProperty("error", medium + " is not supported as a 3PID type") - response.setStatus(HttpStatus.SC_BAD_REQUEST) - return gson.toJson(obj) - } + private ViewConfig viewCfg; @RequestMapping(value = "/validate/{medium}/submitToken") - String validate(HttpServletRequest request, - @RequestParam String sid, - @RequestParam("client_secret") String secret, @RequestParam String token) { + String validate( + HttpServletRequest request, + HttpServletResponse response, + @RequestParam String sid, + @RequestParam("client_secret") String secret, + @RequestParam String token, + Model model + ) { log.info("Requested: {}?{}", request.getRequestURL(), request.getQueryString()) - mgr.validate(sid, secret, token) - - return "{}" - } - - @RequestMapping(value = "/3pid/getValidated3pid") - String check(HttpServletRequest request, HttpServletResponse response, - @RequestParam String sid, @RequestParam("client_secret") String secret) { - log.info("Requested: {}", request.getRequestURL(), request.getQueryString()) - - try { - ThreePidValidation pid = mgr.getValidated(sid, secret) - - JsonObject obj = new JsonObject() - obj.addProperty("medium", pid.getMedium()) - obj.addProperty("address", pid.getAddress()) - obj.addProperty("validated_at", pid.getValidation().toEpochMilli()) - - return gson.toJson(obj); - } catch (SessionNotValidatedException e) { - log.info("Session {} was requested but has not yet been validated", sid); - throw e; - } - } - - @RequestMapping(value = "/3pid/bind") - String bind(HttpServletRequest request, HttpServletResponse response, - @RequestParam String sid, @RequestParam("client_secret") String secret, @RequestParam String mxid) { - String data = IOUtils.toString(request.getReader()) - log.info("Requested: {}", request.getRequestURL(), request.getQueryString()) - try { - mgr.bind(sid, secret, mxid) - return "{}" - } catch (BadRequestException e) { - log.info("requested session was not validated") - - JsonObject obj = new JsonObject() - obj.addProperty("errcode", "M_SESSION_NOT_VALIDATED") - obj.addProperty("error", e.getMessage()) - response.setStatus(HttpStatus.SC_BAD_REQUEST) - return gson.toJson(obj) - } finally { - // If a user registers, there is no standard login event. Instead, this is the only way to trigger - // resolution at an appropriate time. Meh at synapse/Riot! - invMgr.lookupMappingsForInvites() - } - } - - private class Sid { - - private String sid; - - public Sid(String sid) { - setSid(sid); - } - - String getSid() { - return sid - } - - void setSid(String sid) { - this.sid = sid + ValidationResult r = mgr.validate(sid, secret, token) + log.info("Session {} was validated", sid) + if (r.getNextUrl().isPresent()) { + String url = srvCfg.getPublicUrl() + r.getNextUrl().get() + log.info("Session {} validation: next URL is present, redirecting to {}", sid, url) + response.sendRedirect(url) + } else { + if (r.isCanRemote()) { + String url = srvCfg.getPublicUrl() + RemoteIdentityAPIv1.getRequestToken(r.getSession().getId(), r.getSession().getSecret()); + model.addAttribute("remoteSessionLink", url) + return viewCfg.getSession().getLocalRemote().getOnTokenSubmit().getSuccess() + } else { + return viewCfg.getSession().getLocal().getOnTokenSubmit().getSuccess() + } } } diff --git a/src/main/groovy/io/kamax/mxisd/controller/v1/SessionRestController.java b/src/main/groovy/io/kamax/mxisd/controller/v1/SessionRestController.java new file mode 100644 index 0000000..aa7061c --- /dev/null +++ b/src/main/groovy/io/kamax/mxisd/controller/v1/SessionRestController.java @@ -0,0 +1,159 @@ +/* + * 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.controller.v1; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import io.kamax.matrix.ThreePidMedium; +import io.kamax.mxisd.ThreePid; +import io.kamax.mxisd.config.ServerConfig; +import io.kamax.mxisd.config.ViewConfig; +import io.kamax.mxisd.controller.v1.io.SessionEmailTokenRequestJson; +import io.kamax.mxisd.controller.v1.io.SessionPhoneTokenRequestJson; +import io.kamax.mxisd.exception.BadRequestException; +import io.kamax.mxisd.exception.SessionNotValidatedException; +import io.kamax.mxisd.invitation.InvitationManager; +import io.kamax.mxisd.lookup.ThreePidValidation; +import io.kamax.mxisd.session.SessionMananger; +import io.kamax.mxisd.util.GsonParser; +import org.apache.http.HttpStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@RestController +@CrossOrigin +@RequestMapping(path = IdentityAPIv1.BASE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) +public class SessionRestController { + + private Logger log = LoggerFactory.getLogger(SessionRestController.class); + + private class Sid { // FIXME replace with RequestTokenResponse + + private String sid; + + public Sid(String sid) { + setSid(sid); + } + + String getSid() { + return sid; + } + + void setSid(String sid) { + this.sid = sid; + } + } + + @Autowired + private ServerConfig srvCfg; + + @Autowired + private SessionMananger mgr; + + @Autowired + private InvitationManager invMgr; + + @Autowired + private ViewConfig viewCfg; + + private Gson gson = new Gson(); + private GsonParser parser = new GsonParser(gson); + + @RequestMapping(value = "/validate/{medium}/requestToken") + String init(HttpServletRequest request, HttpServletResponse response, @PathVariable String medium) throws IOException { + log.info("Request {}: {}", request.getMethod(), request.getRequestURL(), request.getQueryString()); + if (ThreePidMedium.Email.is(medium)) { + SessionEmailTokenRequestJson req = parser.parse(request, SessionEmailTokenRequestJson.class); + return gson.toJson(new Sid(mgr.create( + request.getRemoteHost(), + new ThreePid(req.getMedium(), req.getValue()), + req.getSecret(), + req.getAttempt(), + req.getNextLink()))); + } + + if (ThreePidMedium.PhoneNumber.is(medium)) { + SessionPhoneTokenRequestJson req = parser.parse(request, SessionPhoneTokenRequestJson.class); + return gson.toJson(new Sid(mgr.create( + request.getRemoteHost(), + new ThreePid(req.getMedium(), req.getValue()), + req.getSecret(), + req.getAttempt(), + req.getNextLink()))); + } + + JsonObject obj = new JsonObject(); + obj.addProperty("errcode", "M_INVALID_3PID_TYPE"); + obj.addProperty("error", medium + " is not supported as a 3PID type"); + response.setStatus(HttpStatus.SC_BAD_REQUEST); + return gson.toJson(obj); + } + + @RequestMapping(value = "/3pid/getValidated3pid") + String check(HttpServletRequest request, HttpServletResponse response, + @RequestParam String sid, @RequestParam("client_secret") String secret) { + log.info("Requested: {}", request.getRequestURL(), request.getQueryString()); + + try { + ThreePidValidation pid = mgr.getValidated(sid, secret); + + JsonObject obj = new JsonObject(); + obj.addProperty("medium", pid.getMedium()); + obj.addProperty("address", pid.getAddress()); + obj.addProperty("validated_at", pid.getValidation().toEpochMilli()); + + return gson.toJson(obj); + } catch (SessionNotValidatedException e) { + log.info("Session {} was requested but has not yet been validated", sid); + throw e; + } + } + + @RequestMapping(value = "/3pid/bind") + String bind(HttpServletRequest request, HttpServletResponse response, + @RequestParam String sid, @RequestParam("client_secret") String secret, @RequestParam String mxid) { + log.info("Requested: {}", request.getRequestURL(), request.getQueryString()); + try { + mgr.bind(sid, secret, mxid); + return "{}"; + } catch (BadRequestException e) { + log.info("requested session was not validated"); + + JsonObject obj = new JsonObject(); + obj.addProperty("errcode", "M_SESSION_NOT_VALIDATED"); + obj.addProperty("error", e.getMessage()); + response.setStatus(HttpStatus.SC_BAD_REQUEST); + return gson.toJson(obj); + } finally { + // If a user registers, there is no standard login event. Instead, this is the only way to trigger + // resolution at an appropriate time. Meh at synapse/Riot! + invMgr.lookupMappingsForInvites(); + } + } + +} diff --git a/src/main/groovy/io/kamax/mxisd/controller/v1/io/RequestTokenResponse.java b/src/main/groovy/io/kamax/mxisd/controller/v1/io/RequestTokenResponse.java new file mode 100644 index 0000000..1946cd0 --- /dev/null +++ b/src/main/groovy/io/kamax/mxisd/controller/v1/io/RequestTokenResponse.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.controller.v1.io; + +public class RequestTokenResponse { + + private String sid; + + public String getSid() { + return sid; + } + +} diff --git a/src/main/groovy/io/kamax/mxisd/controller/v1/remote/RemoteIdentityAPIv1.java b/src/main/groovy/io/kamax/mxisd/controller/v1/remote/RemoteIdentityAPIv1.java index d451736..11d5192 100644 --- a/src/main/groovy/io/kamax/mxisd/controller/v1/remote/RemoteIdentityAPIv1.java +++ b/src/main/groovy/io/kamax/mxisd/controller/v1/remote/RemoteIdentityAPIv1.java @@ -22,6 +22,16 @@ package io.kamax.mxisd.controller.v1.remote; public class RemoteIdentityAPIv1 { - public static final String BASE = "/_matrix/identity-remote/api/v1"; + public static final String BASE = "/_matrix/identity/remote/api/v1"; + public static final String SESSION_REQUEST_TOKEN = BASE + "/validate/requestToken"; + public static final String SESSION_CHECK = BASE + "/validate/check"; + + public static String getRequestToken(String id, String secret) { + return SESSION_REQUEST_TOKEN + "?sid=" + id + "&client_secret=" + secret; + } + + public static String getSessionCheck(String id, String secret) { + return SESSION_CHECK + "?sid=" + id + "&client_secret=" + secret; + } } diff --git a/src/main/groovy/io/kamax/mxisd/controller/v1/remote/RemoteSessionController.java b/src/main/groovy/io/kamax/mxisd/controller/v1/remote/RemoteSessionController.java index ed06f49..25c7517 100644 --- a/src/main/groovy/io/kamax/mxisd/controller/v1/remote/RemoteSessionController.java +++ b/src/main/groovy/io/kamax/mxisd/controller/v1/remote/RemoteSessionController.java @@ -1,37 +1,59 @@ package io.kamax.mxisd.controller.v1.remote; +import io.kamax.mxisd.config.ViewConfig; +import io.kamax.mxisd.exception.SessionNotValidatedException; import io.kamax.mxisd.session.SessionMananger; +import io.kamax.mxisd.threepid.session.IThreePidSession; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; -@RestController -@CrossOrigin -@RequestMapping(path = RemoteIdentityAPIv1.BASE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) +import static io.kamax.mxisd.controller.v1.remote.RemoteIdentityAPIv1.SESSION_CHECK; +import static io.kamax.mxisd.controller.v1.remote.RemoteIdentityAPIv1.SESSION_REQUEST_TOKEN; + +@Controller public class RemoteSessionController { private Logger log = LoggerFactory.getLogger(RemoteSessionController.class); + @Autowired + private ViewConfig viewCfg; + @Autowired private SessionMananger mgr; - @RequestMapping(path = "/validate/requestToken") + @RequestMapping(path = SESSION_REQUEST_TOKEN) public String requestToken( HttpServletRequest request, @RequestParam String sid, @RequestParam("client_secret") String secret, - @RequestParam String token) { - log.info("Request {}: {}", request.getMethod(), request.getRequestURL(), request.getQueryString()); - mgr.createRemote(sid, secret, token); + Model model + ) { + log.info("Request {}: {}", request.getMethod(), request.getRequestURL()); + IThreePidSession session = mgr.createRemote(sid, secret); + model.addAttribute("checkLink", RemoteIdentityAPIv1.getSessionCheck(session.getId(), session.getSecret())); + return viewCfg.getSession().getRemote().getOnRequest().getSuccess(); + } - return "{}"; + @RequestMapping(path = SESSION_CHECK) + public String check( + HttpServletRequest request, + @RequestParam String sid, + @RequestParam("client_secret") String secret) { + log.info("Request {}: {}", request.getMethod(), request.getRequestURL()); + + try { + mgr.validateRemote(sid, secret); + return viewCfg.getSession().getRemote().getOnCheck().getSuccess(); + } catch (SessionNotValidatedException e) { + return viewCfg.getSession().getRemote().getOnCheck().getFailure(); + } } } diff --git a/src/main/groovy/io/kamax/mxisd/exception/SessionUnknownException.java b/src/main/groovy/io/kamax/mxisd/exception/SessionUnknownException.java new file mode 100644 index 0000000..14cd5fa --- /dev/null +++ b/src/main/groovy/io/kamax/mxisd/exception/SessionUnknownException.java @@ -0,0 +1,33 @@ +/* + * 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.exception; + +public class SessionUnknownException extends MatrixException { + + public SessionUnknownException() { + this("No valid session was found matching that sid and client secret"); + } + + public SessionUnknownException(String error) { + super(200, "M_NO_VALID_SESSION", error); + } + +} diff --git a/src/main/groovy/io/kamax/mxisd/session/SessionMananger.java b/src/main/groovy/io/kamax/mxisd/session/SessionMananger.java index 484abbf..9d0cd52 100644 --- a/src/main/groovy/io/kamax/mxisd/session/SessionMananger.java +++ b/src/main/groovy/io/kamax/mxisd/session/SessionMananger.java @@ -21,10 +21,14 @@ package io.kamax.mxisd.session; import com.google.gson.JsonObject; +import io.kamax.matrix.MatrixID; import io.kamax.matrix.ThreePidMedium; +import io.kamax.matrix._MatrixID; import io.kamax.mxisd.ThreePid; import io.kamax.mxisd.config.MatrixConfig; import io.kamax.mxisd.config.SessionConfig; +import io.kamax.mxisd.controller.v1.io.RequestTokenResponse; +import io.kamax.mxisd.controller.v1.remote.RemoteIdentityAPIv1; import io.kamax.mxisd.exception.*; import io.kamax.mxisd.lookup.ThreePidValidation; import io.kamax.mxisd.lookup.strategy.LookupStrategy; @@ -32,20 +36,28 @@ import io.kamax.mxisd.matrix.IdentityServerUtils; import io.kamax.mxisd.notification.NotificationManager; import io.kamax.mxisd.storage.IStorage; import io.kamax.mxisd.storage.dao.IThreePidSessionDao; +import io.kamax.mxisd.threepid.session.IThreePidSession; import io.kamax.mxisd.threepid.session.ThreePidSession; +import io.kamax.mxisd.util.GsonParser; import io.kamax.mxisd.util.RestClientUtils; +import org.apache.commons.io.IOUtils; import org.apache.commons.lang.RandomStringUtils; import org.apache.commons.lang.StringUtils; +import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; +import org.apache.http.message.BasicNameValuePair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.List; import java.util.Optional; @@ -91,7 +103,7 @@ public class SessionMananger { private ThreePidSession getSession(String sid, String secret) { Optional dao = storage.getThreePidSession(sid); if (!dao.isPresent() || !StringUtils.equals(dao.get().getSecret(), secret)) { - throw new InvalidCredentialsException(); + throw new SessionUnknownException(); } return new ThreePidSession(dao.get()); @@ -165,13 +177,28 @@ public class SessionMananger { } } - public Optional validate(String sid, String secret, String token) { + public ValidationResult validate(String sid, String secret, String token) { ThreePidSession session = getSession(sid, secret); log.info("Attempting validation for session {} from {}", session.getId(), session.getServer()); + + boolean isLocal = isLocal(session.getThreePid()); + PolicySource policy = cfg.getPolicy().getValidation().forIf(isLocal); + if (!policy.isEnabled()) { + throw new NotAllowedException("Validating " + (isLocal ? "local" : "remote") + " 3PID is not allowed"); + } + session.validate(token); storage.updateThreePidSession(session.getDao()); log.info("Session {} has been validated", session.getId()); - return session.getNextLink(); + + // FIXME definitely doable in a nicer way + ValidationResult r = new ValidationResult(session, policy.toRemote()); + if (!policy.toLocal()) { + r.setNextUrl(RemoteIdentityAPIv1.getRequestToken(sid, secret)); + } else { + session.getNextLink().ifPresent(r::setNextUrl); + } + return r; } public ThreePidValidation getValidated(String sid, String secret) { @@ -179,34 +206,74 @@ public class SessionMananger { return new ThreePidValidation(session.getThreePid(), session.getValidationTime()); } - public void bind(String sid, String secret, String mxid) { + public void bind(String sid, String secret, String mxidRaw) { + _MatrixID mxid = new MatrixID(mxidRaw); ThreePidSession session = getSessionIfValidated(sid, secret); - log.info("Accepting bind of {} on session {} from server {}", mxid, session.getId(), session.getServer()); - // TODO perform this if request was proxied + + if (!session.isRemote()) { + log.info("Session {} for {}: MXID {} was bound locally", sid, session.getThreePid(), mxid); + return; + } + + log.info("Session {} for {}: MXID {} bind is remote", sid, session.getThreePid(), mxid); + if (!session.isRemoteValidated()) { + log.error("Session {} for {}: Not validated remotely", sid, session.getThreePid()); + throw new SessionNotValidatedException(); + } + + log.info("Session {} for {}: Performing remote bind", sid, session.getThreePid()); + + UrlEncodedFormEntity entity = new UrlEncodedFormEntity( + Arrays.asList( + new BasicNameValuePair("sid", session.getRemoteId()), + new BasicNameValuePair("client_secret", session.getRemoteSecret()), + new BasicNameValuePair("mxid", mxid.getId()) + ), StandardCharsets.UTF_8); + HttpPost bindReq = new HttpPost(session.getRemoteServer() + "/_matrix/identity/api/v1/3pid/bind"); + bindReq.setEntity(entity); + + try (CloseableHttpResponse response = client.execute(bindReq)) { + int status = response.getStatusLine().getStatusCode(); + if (status < 200 || status >= 300) { + String body = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + log.error("Session {} for {}: Remote IS {} failed when trying to bind {} for remote session {}\n{}", + sid, session.getThreePid(), session.getRemoteServer(), mxid, session.getRemoteId(), body); + throw new RemoteIdentityServerException(body); + } + + log.error("Session {} for {}: MXID {} was bound remotely", sid, session.getThreePid(), mxid); + } catch (IOException e) { + log.error("Session {} for {}: I/O Error when trying to bind mxid {}", sid, session.getThreePid(), mxid); + throw new RemoteIdentityServerException(e.getMessage()); + } } - public void createRemote(String sid, String secret, String token) { + public IThreePidSession createRemote(String sid, String secret) { ThreePidSession session = getSessionIfValidated(sid, secret); + log.info("Creating remote 3PID session for {} with local session [{}] to {}", session.getThreePid(), sid); boolean isLocal = isLocal(session.getThreePid()); PolicySource policy = cfg.getPolicy().getValidation().forIf(isLocal); if (!policy.isEnabled() || !policy.toRemote()) { throw new NotAllowedException("Validating " + (isLocal ? "local" : "remote") + " 3PID is not allowed"); } + log.info("Remote 3PID is allowed by policy"); List servers = mxCfg.getIdentity().getServers(policy.getToRemote().getServer()); if (servers.isEmpty()) { throw new InternalServerError(); } - String url = IdentityServerUtils.findIsUrlForDomain(servers.get(0)).orElseThrow(InternalServerError::new); + log.info("Will use IS endpoint {}", url); + + String remoteSecret = session.isRemote() ? session.getRemoteSecret() : RandomStringUtils.randomAlphanumeric(16); JsonObject body = new JsonObject(); - body.addProperty("client_secret", RandomStringUtils.randomAlphanumeric(16)); + body.addProperty("client_secret", remoteSecret); body.addProperty(session.getThreePid().getMedium(), session.getThreePid().getAddress()); - body.addProperty("send_attempt", 1); + body.addProperty("send_attempt", session.increaseAndGetRemoteAttempt()); - log.info("Creating remote 3PID session for {} with local session [{}] to {}", session.getThreePid(), sid); + log.info("Requesting remote session with attempt {}", session.getRemoteAttempt()); HttpPost tokenReq = RestClientUtils.post(url + "/_matrix/identity/api/v1/validate/" + session.getThreePid().getMedium() + "/requestToken", body); try (CloseableHttpResponse response = client.execute(tokenReq)) { int status = response.getStatusLine().getStatusCode(); @@ -214,13 +281,66 @@ public class SessionMananger { throw new RemoteIdentityServerException("Remote identity server returned with status " + status); } - // TODO finish + RequestTokenResponse data = new GsonParser().parse(response, RequestTokenResponse.class); + log.info("Remote Session ID: {}", data.getSid()); + + session.setRemoteData(url, data.getSid(), remoteSecret, 1); + storage.updateThreePidSession(session.getDao()); + log.info("Updated Session {} with remote data", sid); + + return session; } catch (IOException e) { log.warn("Failed to create remote session with {} for {}: {}", url, session.getThreePid(), e.getMessage()); throw new RemoteIdentityServerException(e.getMessage()); } + } + public void validateRemote(String sid, String secret) { + ThreePidSession session = getSessionIfValidated(sid, secret); + if (!session.isRemote()) { + throw new NotAllowedException("Cannot remotely validate a local session"); + } + log.info("Session {} for {}: Validating remote 3PID session {} on {}", sid, session.getThreePid(), session.getRemoteId(), session.getRemoteServer()); + if (session.isRemoteValidated()) { + log.info("Session {} for {}: Already remotely validated", sid, session.getThreePid()); + return; + } + + HttpGet validateReq = new HttpGet(session.getRemoteServer() + "/_matrix/identity/api/v1/3pid/getValidated3pid?sid=" + session.getRemoteId() + "&client_secret=" + session.getRemoteSecret()); + try (CloseableHttpResponse response = client.execute(validateReq)) { + int status = response.getStatusLine().getStatusCode(); + if (status < 200 || status >= 300) { + throw new RemoteIdentityServerException("Remote identity server returned with status " + status); + } + + JsonObject o = new GsonParser().parse(response.getEntity().getContent()); + if (o.has("errcode")) { + String errcode = o.get("errcode").getAsString(); + if (StringUtils.equals("M_SESSION_NOT_VALIDATED", errcode)) { + throw new SessionNotValidatedException(); + } else if (StringUtils.equals("M_NO_VALID_SESSION", errcode)) { + throw new SessionUnknownException(); + } else { + throw new RemoteIdentityServerException("Unknown error while validating Remote 3PID session: " + errcode + " - " + o.get("error").getAsString()); + } + } + + if (o.has("validated_at")) { + ThreePid remoteThreePid = new ThreePid(o.get("medium").getAsString(), o.get("address").getAsString()); + if (session.getThreePid().equals(remoteThreePid)) { // sanity check + throw new InternalServerError("Local 3PID " + session.getThreePid() + " and remote 3PID " + remoteThreePid + " do not match for session " + session.getId()); + } + + log.info("Session {} for {}: Remotely validated successfully", sid, session.getThreePid()); + session.validateRemote(); + storage.updateThreePidSession(session.getDao()); + log.info("Session {} was updated in storage", sid); + } + } catch (IOException e) { + log.warn("Session {} for {}: Failed to validated remotely on {}: {}", sid, session.getThreePid(), session.getRemoteServer(), e.getMessage()); + throw new RemoteIdentityServerException(e.getMessage()); + } } } diff --git a/src/main/groovy/io/kamax/mxisd/session/ValidationResult.java b/src/main/groovy/io/kamax/mxisd/session/ValidationResult.java new file mode 100644 index 0000000..72c987f --- /dev/null +++ b/src/main/groovy/io/kamax/mxisd/session/ValidationResult.java @@ -0,0 +1,54 @@ +/* + * 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.session; + +import io.kamax.mxisd.threepid.session.IThreePidSession; + +import java.util.Optional; + +public class ValidationResult { + + private IThreePidSession session; + private boolean canRemote; + private String nextUrl; + + public ValidationResult(IThreePidSession session, boolean canRemote) { + this.session = session; + this.canRemote = canRemote; + } + + public IThreePidSession getSession() { + return session; + } + + public boolean isCanRemote() { + return canRemote; + } + + public Optional getNextUrl() { + return Optional.ofNullable(nextUrl); + } + + public void setNextUrl(String nextUrl) { + this.nextUrl = nextUrl; + } + +} diff --git a/src/main/groovy/io/kamax/mxisd/storage/dao/IThreePidSessionDao.java b/src/main/groovy/io/kamax/mxisd/storage/dao/IThreePidSessionDao.java index 34da70d..a907e59 100644 --- a/src/main/groovy/io/kamax/mxisd/storage/dao/IThreePidSessionDao.java +++ b/src/main/groovy/io/kamax/mxisd/storage/dao/IThreePidSessionDao.java @@ -44,4 +44,16 @@ public interface IThreePidSessionDao { long getValidationTime(); + boolean isRemote(); + + String getRemoteServer(); + + String getRemoteId(); + + String getRemoteSecret(); + + int getRemoteAttempt(); + + boolean isRemoteValidated(); + } diff --git a/src/main/groovy/io/kamax/mxisd/storage/ormlite/dao/ThreePidSessionDao.java b/src/main/groovy/io/kamax/mxisd/storage/ormlite/dao/ThreePidSessionDao.java index c2b18c0..fc74bf6 100644 --- a/src/main/groovy/io/kamax/mxisd/storage/ormlite/dao/ThreePidSessionDao.java +++ b/src/main/groovy/io/kamax/mxisd/storage/ormlite/dao/ThreePidSessionDao.java @@ -61,6 +61,24 @@ public class ThreePidSessionDao implements IThreePidSessionDao { @DatabaseField private long validationTime; + @DatabaseField(canBeNull = false) + private boolean isRemote; + + @DatabaseField + private String remoteServer; + + @DatabaseField + private String remoteId; + + @DatabaseField + private String remoteSecret; + + @DatabaseField + private Integer remoteAttempt; + + @DatabaseField(canBeNull = false) + private boolean isRemoteValidated; + public ThreePidSessionDao() { // stub for ORMLite } @@ -77,7 +95,12 @@ public class ThreePidSessionDao implements IThreePidSessionDao { setToken(session.getToken()); setValidated(session.getValidated()); setValidationTime(session.getValidationTime()); - + setRemote(session.isRemote()); + setRemoteServer(session.getRemoteServer()); + setRemoteId(session.getRemoteId()); + setRemoteSecret(session.getRemoteSecret()); + setRemoteAttempt(session.getRemoteAttempt()); + setRemoteValidated(session.isRemoteValidated()); } public ThreePidSessionDao(ThreePid tpid, String secret) { @@ -180,6 +203,60 @@ public class ThreePidSessionDao implements IThreePidSessionDao { return validationTime; } + @Override + public boolean isRemote() { + return isRemote; + } + + public void setRemote(boolean remote) { + isRemote = remote; + } + + @Override + public String getRemoteServer() { + return remoteServer; + } + + public void setRemoteServer(String remoteServer) { + this.remoteServer = remoteServer; + } + + @Override + public String getRemoteId() { + return remoteId; + } + + public void setRemoteId(String remoteId) { + this.remoteId = remoteId; + } + + @Override + public String getRemoteSecret() { + return remoteSecret; + } + + public void setRemoteSecret(String remoteSecret) { + this.remoteSecret = remoteSecret; + } + + @Override + public int getRemoteAttempt() { + return remoteAttempt; + } + + @Override + public boolean isRemoteValidated() { + return isRemoteValidated; + } + + public void setRemoteValidated(boolean remoteValidated) { + isRemoteValidated = remoteValidated; + } + + public void setRemoteAttempt(int remoteAttempt) { + this.remoteAttempt = remoteAttempt; + } + public void setValidationTime(long validationTime) { this.validationTime = validationTime; } diff --git a/src/main/groovy/io/kamax/mxisd/threepid/notification/email/EmailNotificationGenerator.java b/src/main/groovy/io/kamax/mxisd/threepid/notification/email/EmailNotificationGenerator.java index 78100e3..85739db 100644 --- a/src/main/groovy/io/kamax/mxisd/threepid/notification/email/EmailNotificationGenerator.java +++ b/src/main/groovy/io/kamax/mxisd/threepid/notification/email/EmailNotificationGenerator.java @@ -26,7 +26,6 @@ import io.kamax.mxisd.config.ServerConfig; import io.kamax.mxisd.config.threepid.medium.EmailConfig; import io.kamax.mxisd.config.threepid.medium.EmailTemplateConfig; import io.kamax.mxisd.controller.v1.IdentityAPIv1; -import io.kamax.mxisd.controller.v1.remote.RemoteIdentityAPIv1; import io.kamax.mxisd.exception.InternalServerError; import io.kamax.mxisd.invitation.IThreePidInviteReply; import io.kamax.mxisd.threepid.session.IThreePidSession; @@ -140,16 +139,14 @@ public class EmailNotificationGenerator implements IEmailNotificationGenerator { log.info("Generating notification content for remote-only 3PID session"); String templateBody = getTemplateAndPopulate(templateCfg.getSession().getValidation().getRemote(), session.getThreePid()); - // FIXME should have a global link builder, specific to mxisd - String nextStepLink = srvCfg.getPublicUrl() + RemoteIdentityAPIv1.BASE + - "/validate/requestToken?sid=" + session.getId() + - "&client_secret=" + session.getSecret() + + // FIXME should have a global link builder, most likely in the SDK? + String validationLink = srvCfg.getPublicUrl() + IdentityAPIv1.BASE + + "/validate/" + session.getThreePid().getMedium() + + "/submitToken?sid=" + session.getId() + "&client_secret=" + session.getSecret() + "&token=" + session.getToken(); - templateBody = templateBody.replace("%SESSION_ID%", session.getId()); - templateBody = templateBody.replace("%SESSION_SECRET%", session.getSecret()); - templateBody = templateBody.replace("%SESSION_TOKEN%", session.getToken()); - templateBody = templateBody.replace("%NEXT_STEP_LINK%", nextStepLink); + templateBody = templateBody.replace("%VALIDATION_LINK%", validationLink); + templateBody = templateBody.replace("%VALIDATION_TOKEN%", session.getToken()); return templateBody; } diff --git a/src/main/groovy/io/kamax/mxisd/threepid/session/IThreePidSession.java b/src/main/groovy/io/kamax/mxisd/threepid/session/IThreePidSession.java index 446d148..8611855 100644 --- a/src/main/groovy/io/kamax/mxisd/threepid/session/IThreePidSession.java +++ b/src/main/groovy/io/kamax/mxisd/threepid/session/IThreePidSession.java @@ -51,4 +51,16 @@ public interface IThreePidSession { Instant getValidationTime(); + boolean isRemote(); + + String getRemoteServer(); + + String getRemoteId(); + + String getRemoteSecret(); + + int getRemoteAttempt(); + + void setRemoteData(String server, String id, String secret, int attempt); + } diff --git a/src/main/groovy/io/kamax/mxisd/threepid/session/ThreePidSession.java b/src/main/groovy/io/kamax/mxisd/threepid/session/ThreePidSession.java index dee9354..035a60a 100644 --- a/src/main/groovy/io/kamax/mxisd/threepid/session/ThreePidSession.java +++ b/src/main/groovy/io/kamax/mxisd/threepid/session/ThreePidSession.java @@ -42,6 +42,12 @@ public class ThreePidSession implements IThreePidSession { private int attempt; private Instant validationTimestamp; private boolean isValidated; + private boolean isRemote; + private String remoteServer; + private String remoteId; + private String remoteSecret; + private int remoteAttempt; + private boolean isRemoteValidated; public ThreePidSession(IThreePidSessionDao dao) { this( @@ -58,6 +64,13 @@ public class ThreePidSession implements IThreePidSession { if (isValidated) { validationTimestamp = Instant.ofEpochMilli(dao.getValidationTime()); } + + isRemote = dao.isRemote(); + remoteServer = dao.getRemoteServer(); + remoteId = dao.getRemoteId(); + remoteSecret = dao.getRemoteSecret(); + remoteAttempt = dao.getRemoteAttempt(); + isRemoteValidated = dao.isRemoteValidated(); } public ThreePidSession(String id, String server, ThreePid tPid, String secret, int attempt, String nextLink, String token) { @@ -129,6 +142,44 @@ public class ThreePidSession implements IThreePidSession { return validationTimestamp; } + @Override + public boolean isRemote() { + return isRemote; + } + + @Override + public String getRemoteServer() { + return remoteServer; + } + + @Override + public String getRemoteId() { + return remoteId; + } + + @Override + public String getRemoteSecret() { + return remoteSecret; + } + + @Override + public int getRemoteAttempt() { + return remoteAttempt; + } + + public int increaseAndGetRemoteAttempt() { + return ++remoteAttempt; + } + + @Override + public void setRemoteData(String server, String id, String secret, int attempt) { + this.remoteServer = server; + this.remoteId = id; + this.remoteSecret = secret; + this.attempt = attempt; + this.isRemote = true; + } + @Override public boolean isValidated() { return isValidated; @@ -151,6 +202,14 @@ public class ThreePidSession implements IThreePidSession { isValidated = true; } + public boolean isRemoteValidated() { + return isRemoteValidated; + } + + public void validateRemote() { + this.isRemoteValidated = true; + } + public IThreePidSessionDao getDao() { return new IThreePidSessionDao() { @@ -209,6 +268,36 @@ public class ThreePidSession implements IThreePidSession { return isValidated ? validationTimestamp.toEpochMilli() : 0; } + @Override + public boolean isRemote() { + return isRemote; + } + + @Override + public String getRemoteServer() { + return remoteServer; + } + + @Override + public String getRemoteId() { + return remoteId; + } + + @Override + public String getRemoteSecret() { + return remoteSecret; + } + + @Override + public int getRemoteAttempt() { + return remoteAttempt; + } + + @Override + public boolean isRemoteValidated() { + return isRemoteValidated; + } + }; } diff --git a/src/main/groovy/io/kamax/mxisd/util/GsonParser.java b/src/main/groovy/io/kamax/mxisd/util/GsonParser.java index ec311c8..a83ebaf 100644 --- a/src/main/groovy/io/kamax/mxisd/util/GsonParser.java +++ b/src/main/groovy/io/kamax/mxisd/util/GsonParser.java @@ -26,6 +26,7 @@ import io.kamax.mxisd.exception.JsonMemberNotFoundException; import org.apache.commons.io.IOUtils; import org.apache.http.HttpResponse; +import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; @@ -53,6 +54,10 @@ public class GsonParser { return el.getAsJsonObject(); } + public T parse(HttpServletRequest req, Class type) throws IOException { + return gson.fromJson(parse(req.getInputStream()), type); + } + public T parse(HttpResponse res, Class type) throws IOException { return gson.fromJson(parse(res.getEntity().getContent()), type); } diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 839b378..35c99f9 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -106,6 +106,24 @@ session.policy.validation: enabled: true server: 'root' +view: + session: + local: + onTokenSubmit: + success: 'session/local/tokenSubmitSuccess' + failure: 'session/local/tokenSubmitFailure' + localRemote: + onTokenSubmit: + success: 'session/localRemote/tokenSubmitSuccess' + failure: 'session/local/tokenSubmitFailure' + remote: + onRequest: + success: 'session/remote/requestSuccess' + failure: 'session/remote/requestFailure' + onCheck: + success: 'session/remote/checkSuccess' + failure: 'session/remote/checkFailure' + storage: backend: 'sqlite' diff --git a/src/main/resources/email/validate-remote-template.eml b/src/main/resources/email/validate-remote-template.eml index 76c64fc..b4888c6 100644 --- a/src/main/resources/email/validate-remote-template.eml +++ b/src/main/resources/email/validate-remote-template.eml @@ -17,7 +17,7 @@ If you continue, your e-mail address and Matrix ID association will be made publ If you would still like to continue, you will need to: 1. Go to your private Public registration process page: - %NEXT_STEP_LINK% + %VALIDATION_LINK% 2. Follow the registration process of the central Identity Server, usually another email with similar content 3. Once your email address validated with the central Identity Server, click on "Continue" on page of step #1 @@ -81,7 +81,7 @@ pre, code {

If you would still like to continue, you will need to:

    -
  1. Go to your private Public registration process page
  2. +
  3. Go to your private Public registration process page
  4. Follow the registration process of the central Identity Server, usually another email with similar content
  5. Once your email address validated with the central Identity Server, click on "Continue" on page of step #1
  6. If your public association is found by our Identity server, the next step will be given to you.
  7. diff --git a/src/main/resources/templates/session/local/tokenSubmitFailure.html b/src/main/resources/templates/session/local/tokenSubmitFailure.html new file mode 100644 index 0000000..bdeed88 --- /dev/null +++ b/src/main/resources/templates/session/local/tokenSubmitFailure.html @@ -0,0 +1,40 @@ + + + + + Matrix Token Verification + + + +
    +

    Verification failed: you may need to request another verification email

    +
    + + diff --git a/src/main/resources/templates/session/local/tokenSubmitSuccess.html b/src/main/resources/templates/session/local/tokenSubmitSuccess.html new file mode 100644 index 0000000..2bcd7c6 --- /dev/null +++ b/src/main/resources/templates/session/local/tokenSubmitSuccess.html @@ -0,0 +1,40 @@ + + + + + Matrix Token Verification + + + +
    +

    Verification successful: return to your Matrix client to complete the process.

    +
    + + diff --git a/src/main/resources/templates/session/localRemote/tokenSubmitSuccess.html b/src/main/resources/templates/session/localRemote/tokenSubmitSuccess.html new file mode 100644 index 0000000..2dc81a1 --- /dev/null +++ b/src/main/resources/templates/session/localRemote/tokenSubmitSuccess.html @@ -0,0 +1,47 @@ + + + + + Matrix Token Verification + + + +
    +

    Verification successful!

    +

    Your email will remain private and you will only be discoverable with it on your own server, or any related + servers configured by your system admin.
    + If you would like to be globally discoverable, start the process here. +
    If you chose to start the global publication process, wait until it is done before returning to your + client.

    +

    If the remote process is finished, or if you do not wish to start it at this time, you can now return to your + Matrix client to complete the process.

    +
    + + diff --git a/src/main/resources/templates/session/remote/checkFailure.html b/src/main/resources/templates/session/remote/checkFailure.html new file mode 100644 index 0000000..95965fc --- /dev/null +++ b/src/main/resources/templates/session/remote/checkFailure.html @@ -0,0 +1,43 @@ + + + + + Matrix global token verification + + + +
    +

    You do not seem to have validated your session with the global server. Please check your messages for one similar + to the one you received initially.
    + Once this is done, click here to continue

    +

    If this problem persists, contact your system administrator with the following info: Reference #ABC

    +
    + + diff --git a/src/main/resources/templates/session/remote/checkSuccess.html b/src/main/resources/templates/session/remote/checkSuccess.html new file mode 100644 index 0000000..1369763 --- /dev/null +++ b/src/main/resources/templates/session/remote/checkSuccess.html @@ -0,0 +1,41 @@ + + + + + Matrix global token verification + + + +
    +

    Verification successful!

    +

    Return to your Matrix client to complete the process and make yourself globally discoverable.

    +
    + + diff --git a/src/main/resources/templates/session/remote/requestFailure.html b/src/main/resources/templates/session/remote/requestFailure.html new file mode 100644 index 0000000..d546307 --- /dev/null +++ b/src/main/resources/templates/session/remote/requestFailure.html @@ -0,0 +1,42 @@ + + + + + Matrix global token verification + + + +
    +

    The process to be globally discoverable has failed!
    You can try to refresh this page in a few seconds or + minutes.

    +

    If this problem persists, contact your system administrator with the following info: Reference #ABC

    +
    + + diff --git a/src/main/resources/templates/session/remote/requestSuccess.html b/src/main/resources/templates/session/remote/requestSuccess.html new file mode 100644 index 0000000..fbf678b --- /dev/null +++ b/src/main/resources/templates/session/remote/requestSuccess.html @@ -0,0 +1,45 @@ + + + + + Matrix global token verification + + + +
    +

    The process to be globally discoverable has started. A verification token has been requested on your behalf.

    +

    You will receive a similar communication as the first verification message.
    + Follow the instructions and come back to this page once you are told to return to your Matrix client or that the + verification was successful.

    +

    Once the validation was successful with the global server, please follow this link + to validate it with us.

    +
    + +