diff --git a/src/base-apis.js b/src/base-apis.js index 107bbddbd..f939e17c5 100644 --- a/src/base-apis.js +++ b/src/base-apis.js @@ -1339,10 +1339,16 @@ MatrixBaseApis.prototype.getThreePids = function(callback) { }; /** + * Add a 3PID to your homeserver account and optionally bind it to an identity + * server as well. An identity server is required as part of the `creds` object. + * + * This API is deprecated, and you should instead use `addThreePidOnly` + * for homeservers that support it. + * * @param {Object} creds * @param {boolean} bind * @param {module:client.callback} callback Optional. - * @return {module:client.Promise} Resolves: TODO + * @return {module:client.Promise} Resolves: on success * @return {module:http-api.MatrixError} Rejects: with an error response. */ MatrixBaseApis.prototype.addThreePid = function(creds, bind, callback) { @@ -1356,6 +1362,75 @@ MatrixBaseApis.prototype.addThreePid = function(creds, bind, callback) { ); }; +/** + * Add a 3PID to your homeserver account. This API does not use an identity + * server, as the homeserver is expected to handle 3PID ownership validation. + * + * You can check whether a homeserver supports this API via + * `doesServerSupportSeparateAddAndBind`. + * + * @param {Object} data A object with 3PID validation data from having called + * `account/3pid//requestToken` on the homeserver. + * @return {module:client.Promise} Resolves: on success + * @return {module:http-api.MatrixError} Rejects: with an error response. + */ +MatrixBaseApis.prototype.addThreePidOnly = function(data) { + const path = "/account/3pid/add"; + return this._http.authedRequest( + undefined, "POST", path, null, data, { + prefix: httpApi.PREFIX_UNSTABLE, + }, + ); +}; + +/** + * Bind a 3PID for discovery onto an identity server via the homeserver. The + * identity server handles 3PID ownership validation and the homeserver records + * the new binding to track where all 3PIDs for the account are bound. + * + * You can check whether a homeserver supports this API via + * `doesServerSupportSeparateAddAndBind`. + * + * @param {Object} data A object with 3PID validation data from having called + * `validate//requestToken` on the identity server. It should also + * contain `id_server` and `id_access_token` fields as well. + * @return {module:client.Promise} Resolves: on success + * @return {module:http-api.MatrixError} Rejects: with an error response. + */ +MatrixBaseApis.prototype.bindThreePid = function(data) { + const path = "/account/3pid/bind"; + return this._http.authedRequest( + undefined, "POST", path, null, data, { + prefix: httpApi.PREFIX_UNSTABLE, + }, + ); +}; + +/** + * Unbind a 3PID for discovery on an identity server via the homeserver. The + * homeserver removes its record of the binding to keep an updated record of + * where all 3PIDs for the account are bound. + * + * @param {string} medium The threepid medium (eg. 'email') + * @param {string} address The threepid address (eg. 'bob@example.com') + * this must be as returned by getThreePids. + * @return {module:client.Promise} Resolves: on success + * @return {module:http-api.MatrixError} Rejects: with an error response. + */ +MatrixBaseApis.prototype.unbindThreePid = function(medium, address) { + const path = "/account/3pid/unbind"; + const data = { + medium, + address, + id_server: this.getIdentityServerUrl(true), + }; + return this._http.authedRequest( + undefined, "POST", path, null, data, { + prefix: httpApi.PREFIX_UNSTABLE, + }, + ); +}; + /** * @param {string} medium The threepid medium (eg. 'email') * @param {string} address The threepid address (eg. 'bob@example.com') @@ -1757,10 +1832,11 @@ MatrixBaseApis.prototype.registerWithIdentityServer = function(hsOpenIdToken) { }; /** - * Requests an email verification token directly from an Identity Server. + * Requests an email verification token directly from an identity server. * - * Note that the Homeserver offers APIs to proxy this API for specific - * situations, allowing for better feedback to the user. + * This API is used as part of binding an email for discovery on an identity + * server. The validation data that results should be passed to the + * `bindThreePid` method to complete the binding process. * * @param {string} email The email address to request a token for * @param {string} clientSecret A secret binary string generated by the client. @@ -1772,12 +1848,12 @@ MatrixBaseApis.prototype.registerWithIdentityServer = function(hsOpenIdToken) { * @param {string} nextLink Optional If specified, the client will be redirected * to this link after validation. * @param {module:client.callback} callback Optional. - * @param {string} identityAccessToken The `access_token` field of the Identity - * Server `/account/register` response (see {@link registerWithIdentityServer}). + * @param {string} identityAccessToken The `access_token` field of the identity + * server `/account/register` response (see {@link registerWithIdentityServer}). * * @return {module:client.Promise} Resolves: TODO * @return {module:http-api.MatrixError} Rejects: with an error response. - * @throws Error if no Identity Server is set + * @throws Error if no identity server is set */ MatrixBaseApis.prototype.requestEmailToken = async function( email, @@ -1818,6 +1894,74 @@ MatrixBaseApis.prototype.requestEmailToken = async function( } }; +/** + * Requests a MSISDN verification token directly from an identity server. + * + * This API is used as part of binding a MSISDN for discovery on an identity + * server. The validation data that results should be passed to the + * `bindThreePid` method to complete the binding process. + * + * @param {string} phoneCountry The ISO 3166-1 alpha-2 code for the country in + * which phoneNumber should be parsed relative to. + * @param {string} phoneNumber The phone number, in national or international + * format + * @param {string} clientSecret A secret binary string generated by the client. + * It is recommended this be around 16 ASCII characters. + * @param {number} sendAttempt If an identity server sees a duplicate request + * with the same sendAttempt, it will not send another SMS. + * To request another SMS to be sent, use a larger value for + * the sendAttempt param as was used in the previous request. + * @param {string} nextLink Optional If specified, the client will be redirected + * to this link after validation. + * @param {module:client.callback} callback Optional. + * @param {string} identityAccessToken The `access_token` field of the Identity + * Server `/account/register` response (see {@link registerWithIdentityServer}). + * + * @return {module:client.Promise} Resolves: TODO + * @return {module:http-api.MatrixError} Rejects: with an error response. + * @throws Error if no identity server is set + */ +MatrixBaseApis.prototype.requestMsisdnToken = async function( + phoneCountry, + phoneNumber, + clientSecret, + sendAttempt, + nextLink, + callback, + identityAccessToken, +) { + const params = { + client_secret: clientSecret, + country: phoneCountry, + phone_number: phoneNumber, + send_attempt: sendAttempt, + next_link: nextLink, + }; + + try { + const response = await this._http.idServerRequest( + undefined, "POST", "/validate/msisdn/requestToken", + params, httpApi.PREFIX_IDENTITY_V2, identityAccessToken, + ); + // TODO: Fold callback into above call once v1 path below is removed + if (callback) callback(null, response); + return response; + } catch (err) { + if (err.cors === "rejected" || err.httpStatus === 404) { + // Fall back to deprecated v1 API for now + // TODO: Remove this path once v2 is only supported version + // See https://github.com/vector-im/riot-web/issues/10443 + logger.warn("IS doesn't support v2, falling back to deprecated v1"); + return await this._http.idServerRequest( + callback, "POST", "/validate/msisdn/requestToken", + params, httpApi.PREFIX_IDENTITY_V1, + ); + } + if (callback) callback(err); + throw err; + } +}; + /** * Submits an MSISDN token to the identity server * diff --git a/src/client.js b/src/client.js index a86b723d8..f1e547545 100644 --- a/src/client.js +++ b/src/client.js @@ -4154,7 +4154,7 @@ MatrixClient.prototype.stopClient = function() { global.clearTimeout(this._checkTurnServersTimeoutID); }; -/* +/** * Get the API versions supported by the server, along with any * unstable APIs it supports * @return {Promise} The server /versions response @@ -4174,7 +4174,7 @@ MatrixClient.prototype.getVersions = async function() { return this._serverVersionsCache; }; -/* +/** * Query the server to see if it support members lazy loading * @return {Promise} true if server supports lazy loading */ @@ -4188,7 +4188,7 @@ MatrixClient.prototype.doesServerSupportLazyLoading = async function() { || (unstableFeatures && unstableFeatures["m.lazy_load_members"]); }; -/* +/** * Query the server to see if the `id_server` parameter is required * when registering with an 3pid, adding a 3pid or resetting password. * @return {Promise} true if id_server parameter is required @@ -4211,7 +4211,7 @@ MatrixClient.prototype.doesServerRequireIdServerParam = async function() { } }; -/* +/** * Query the server to see if the `id_access_token` parameter can be safely * passed to the homeserver. Some homeservers may trigger errors if they are not * prepared for the new parameter. @@ -4227,7 +4227,23 @@ MatrixClient.prototype.doesServerAcceptIdentityAccessToken = async function() { || (unstableFeatures && unstableFeatures["m.id_access_token"]); }; -/* +/** + * Query the server to see if it supports separate 3PID add and bind functions. + * This affects the sequence of API calls clients should use for these operations, + * so it's helpful to be able to check for support. + * @return {Promise} true if separate functions are supported + */ +MatrixClient.prototype.doesServerSupportSeparateAddAndBind = async function() { + const response = await this.getVersions(); + + const versions = response["versions"]; + const unstableFeatures = response["unstable_features"]; + + return (versions && versions.includes("r0.6.0")) + || (unstableFeatures && unstableFeatures["m.separate_add_and_bind"]); +}; + +/** * Get if lazy loading members is being used. * @return {boolean} Whether or not members are lazy loaded by this client */ @@ -4235,7 +4251,7 @@ MatrixClient.prototype.hasLazyLoadMembersEnabled = function() { return !!this._clientOpts.lazyLoadMembers; }; -/* +/** * Set a function which is called when /sync returns a 'limited' response. * It is called with a room ID and returns a boolean. It should return 'true' if the SDK * can SAFELY remove events from this room. It may not be safe to remove events if there