2016-01-12 17:20:16 +00:00
|
|
|
/*
|
|
|
|
Copyright 2015, 2016 OpenMarket Ltd
|
2022-03-09 12:05:16 +00:00
|
|
|
Copyright 2019 - 2022 The Matrix.org Foundation C.I.C.
|
2016-01-12 17:20:16 +00:00
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
2021-08-19 06:59:27 +00:00
|
|
|
import { createClient, IRequestTokenResponse, MatrixClient } from "matrix-js-sdk/src/matrix";
|
2021-10-22 22:23:32 +00:00
|
|
|
|
2017-05-25 10:39:08 +00:00
|
|
|
import { _t } from "./languageHandler";
|
2016-01-12 17:20:16 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Allows a user to reset their password on a homeserver.
|
|
|
|
*
|
|
|
|
* This involves getting an email token from the identity server to "prove" that
|
|
|
|
* the client owns the given email address, which is then passed to the password
|
|
|
|
* API on the homeserver in question with the new password.
|
|
|
|
*/
|
2019-12-20 00:45:24 +00:00
|
|
|
export default class PasswordReset {
|
2021-08-19 06:59:27 +00:00
|
|
|
private client: MatrixClient;
|
|
|
|
private clientSecret: string;
|
2022-11-22 06:58:37 +00:00
|
|
|
private password = "";
|
|
|
|
private sessionId = "";
|
|
|
|
private logoutDevices = false;
|
|
|
|
private sendAttempt = 0;
|
2021-08-19 06:59:27 +00:00
|
|
|
|
2016-01-12 17:20:16 +00:00
|
|
|
/**
|
|
|
|
* Configure the endpoints for password resetting.
|
|
|
|
* @param {string} homeserverUrl The URL to the HS which has the account to reset.
|
|
|
|
* @param {string} identityUrl The URL to the IS which has linked the email -> mxid mapping.
|
|
|
|
*/
|
2022-12-16 12:29:59 +00:00
|
|
|
public constructor(homeserverUrl: string, identityUrl: string) {
|
2021-03-19 02:50:34 +00:00
|
|
|
this.client = createClient({
|
2016-01-12 17:20:16 +00:00
|
|
|
baseUrl: homeserverUrl,
|
2017-07-01 13:31:59 +00:00
|
|
|
idBaseUrl: identityUrl,
|
2016-01-12 17:20:16 +00:00
|
|
|
});
|
2016-01-18 17:50:27 +00:00
|
|
|
this.clientSecret = this.client.generateClientSecret();
|
2019-08-16 17:11:24 +00:00
|
|
|
}
|
|
|
|
|
2016-01-12 17:20:16 +00:00
|
|
|
/**
|
|
|
|
* Attempt to reset the user's password. This will trigger a side-effect of
|
|
|
|
* sending an email to the provided email address.
|
|
|
|
* @param {string} emailAddress The email address
|
|
|
|
* @param {string} newPassword The new password for the account.
|
2022-04-22 17:15:38 +00:00
|
|
|
* @param {boolean} logoutDevices Should all devices be signed out after the reset? Defaults to `true`.
|
2016-01-12 17:20:16 +00:00
|
|
|
* @return {Promise} Resolves when the email has been sent. Then call checkEmailLinkClicked().
|
|
|
|
*/
|
2022-11-22 06:58:37 +00:00
|
|
|
public async resetPassword(
|
2022-04-22 17:15:38 +00:00
|
|
|
emailAddress: string,
|
|
|
|
newPassword: string,
|
|
|
|
logoutDevices = true,
|
|
|
|
): Promise<IRequestTokenResponse> {
|
2016-01-12 17:20:16 +00:00
|
|
|
this.password = newPassword;
|
2022-04-22 17:15:38 +00:00
|
|
|
this.logoutDevices = logoutDevices;
|
2022-11-22 06:58:37 +00:00
|
|
|
this.sendAttempt++;
|
|
|
|
|
|
|
|
try {
|
|
|
|
const result = await this.client.requestPasswordEmailToken(
|
|
|
|
emailAddress,
|
|
|
|
this.clientSecret,
|
|
|
|
this.sendAttempt,
|
|
|
|
);
|
|
|
|
this.sessionId = result.sid;
|
|
|
|
return result;
|
|
|
|
} catch (err: any) {
|
|
|
|
if (err.errcode === "M_THREEPID_NOT_FOUND") {
|
|
|
|
err.message = _t("This email address was not found");
|
|
|
|
} else if (err.httpStatus) {
|
|
|
|
err.message = err.message + ` (Status ${err.httpStatus})`;
|
|
|
|
}
|
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Request a password reset token.
|
|
|
|
* This will trigger a side-effect of sending an email to the provided email address.
|
|
|
|
*/
|
|
|
|
public requestResetToken(emailAddress: string): Promise<IRequestTokenResponse> {
|
|
|
|
this.sendAttempt++;
|
|
|
|
return this.client.requestPasswordEmailToken(emailAddress, this.clientSecret, this.sendAttempt).then(
|
|
|
|
(res) => {
|
2016-01-12 17:20:16 +00:00
|
|
|
this.sessionId = res.sid;
|
|
|
|
return res;
|
|
|
|
},
|
|
|
|
function (err) {
|
2017-07-01 13:31:59 +00:00
|
|
|
if (err.errcode === "M_THREEPID_NOT_FOUND") {
|
2021-04-27 15:23:27 +00:00
|
|
|
err.message = _t("This email address was not found");
|
2016-07-08 17:06:50 +00:00
|
|
|
} else if (err.httpStatus) {
|
2016-01-12 17:20:16 +00:00
|
|
|
err.message = err.message + ` (Status ${err.httpStatus})`;
|
|
|
|
}
|
|
|
|
throw err;
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-01-18 07:25:03 +00:00
|
|
|
public setLogoutDevices(logoutDevices: boolean): void {
|
|
|
|
this.logoutDevices = logoutDevices;
|
|
|
|
}
|
|
|
|
|
2022-11-22 06:58:37 +00:00
|
|
|
public async setNewPassword(password: string): Promise<void> {
|
|
|
|
this.password = password;
|
|
|
|
await this.checkEmailLinkClicked();
|
|
|
|
}
|
|
|
|
|
2016-01-12 17:20:16 +00:00
|
|
|
/**
|
|
|
|
* Checks if the email link has been clicked by attempting to change the password
|
|
|
|
* for the mxid linked to the email.
|
|
|
|
* @return {Promise} Resolves if the password was reset. Rejects with an object
|
|
|
|
* with a "message" property which contains a human-readable message detailing why
|
|
|
|
* the reset failed, e.g. "There is no mapped matrix user ID for the given email address".
|
|
|
|
*/
|
2021-08-19 07:47:26 +00:00
|
|
|
public async checkEmailLinkClicked(): Promise<void> {
|
2019-09-24 13:47:08 +00:00
|
|
|
const creds = {
|
|
|
|
sid: this.sessionId,
|
|
|
|
client_secret: this.clientSecret,
|
|
|
|
};
|
|
|
|
|
|
|
|
try {
|
|
|
|
await this.client.setPassword(
|
|
|
|
{
|
2020-05-29 14:23:59 +00:00
|
|
|
// Note: Though this sounds like a login type for identity servers only, it
|
|
|
|
// has a dual purpose of being used for homeservers too.
|
2019-09-24 13:47:08 +00:00
|
|
|
type: "m.login.email.identity",
|
2020-05-29 14:23:59 +00:00
|
|
|
// TODO: Remove `threepid_creds` once servers support proper UIA
|
|
|
|
// See https://github.com/matrix-org/synapse/issues/5665
|
|
|
|
// See https://github.com/matrix-org/matrix-doc/issues/2220
|
2019-09-24 13:47:08 +00:00
|
|
|
threepid_creds: creds,
|
2020-05-29 14:23:59 +00:00
|
|
|
threepidCreds: creds,
|
2022-04-22 17:15:38 +00:00
|
|
|
},
|
|
|
|
this.password,
|
|
|
|
this.logoutDevices,
|
|
|
|
);
|
2022-11-22 06:58:37 +00:00
|
|
|
} catch (err: any) {
|
2016-01-12 17:20:16 +00:00
|
|
|
if (err.httpStatus === 401) {
|
2017-05-23 14:16:31 +00:00
|
|
|
err.message = _t("Failed to verify email address: make sure you clicked the link in the email");
|
2017-07-01 13:31:59 +00:00
|
|
|
} else if (err.httpStatus === 404) {
|
|
|
|
err.message = _t(
|
2023-02-09 10:04:54 +00:00
|
|
|
"Your email address does not appear to be associated with a Matrix ID on this homeserver.",
|
2017-07-01 13:31:59 +00:00
|
|
|
);
|
|
|
|
} else if (err.httpStatus) {
|
2016-01-12 17:20:16 +00:00
|
|
|
err.message += ` (Status ${err.httpStatus})`;
|
|
|
|
}
|
|
|
|
throw err;
|
2019-09-24 13:47:08 +00:00
|
|
|
}
|
2016-01-12 17:20:16 +00:00
|
|
|
}
|
|
|
|
}
|