2017-02-24 12:41:23 +01:00
|
|
|
/*
|
|
|
|
Copyright 2015, 2016 OpenMarket Ltd
|
|
|
|
Copyright 2017 Vector Creations Ltd
|
2018-12-05 17:39:38 +01:00
|
|
|
Copyright 2018 New Vector Ltd
|
2019-08-09 16:34:03 +02:00
|
|
|
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
|
2020-03-02 15:59:54 +01:00
|
|
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
2017-02-24 12:41:23 +01: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.
|
|
|
|
*/
|
|
|
|
|
2020-10-07 18:18:19 +02:00
|
|
|
// @ts-ignore - XXX: tsc doesn't like this: our js-sdk imports are complex so this isn't surprising
|
2017-02-24 12:41:23 +01:00
|
|
|
import Matrix from "matrix-js-sdk";
|
2020-10-06 13:42:28 +02:00
|
|
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
|
|
|
import { IMatrixClientCreds } from "./MatrixClientPeg";
|
2020-10-08 17:35:17 +02:00
|
|
|
import SecurityCustomisations from "./customisations/Security";
|
2020-10-06 13:42:28 +02:00
|
|
|
|
|
|
|
interface ILoginOptions {
|
|
|
|
defaultDeviceDisplayName?: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Move this to JS SDK
|
|
|
|
interface ILoginFlow {
|
2020-11-23 18:10:15 +01:00
|
|
|
type: "m.login.password" | "m.login.cas";
|
2020-10-06 13:42:28 +02:00
|
|
|
}
|
|
|
|
|
2020-11-23 18:10:15 +01:00
|
|
|
export interface IIdentityProvider {
|
|
|
|
id: string;
|
|
|
|
name: string;
|
|
|
|
icon?: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface ISSOFlow {
|
|
|
|
type: "m.login.sso";
|
|
|
|
// eslint-disable-next-line camelcase
|
|
|
|
identity_providers: IIdentityProvider[];
|
|
|
|
"org.matrix.msc2858.identity_providers": IIdentityProvider[]; // Unstable prefix for MSC2858
|
|
|
|
}
|
|
|
|
|
|
|
|
export type LoginFlow = ISSOFlow | ILoginFlow;
|
|
|
|
|
2020-10-06 13:42:28 +02:00
|
|
|
// TODO: Move this to JS SDK
|
|
|
|
/* eslint-disable camelcase */
|
|
|
|
interface ILoginParams {
|
|
|
|
identifier?: string;
|
|
|
|
password?: string;
|
|
|
|
token?: string;
|
|
|
|
device_id?: string;
|
|
|
|
initial_device_display_name?: string;
|
|
|
|
}
|
|
|
|
/* eslint-enable camelcase */
|
2017-02-24 12:41:23 +01:00
|
|
|
|
|
|
|
export default class Login {
|
2020-10-06 13:42:28 +02:00
|
|
|
private hsUrl: string;
|
|
|
|
private isUrl: string;
|
|
|
|
private fallbackHsUrl: string;
|
|
|
|
// TODO: Flows need a type in JS SDK
|
2020-11-23 18:10:15 +01:00
|
|
|
private flows: Array<LoginFlow>;
|
2020-10-06 13:42:28 +02:00
|
|
|
private defaultDeviceDisplayName: string;
|
|
|
|
private tempClient: MatrixClient;
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
hsUrl: string,
|
|
|
|
isUrl: string,
|
|
|
|
fallbackHsUrl?: string,
|
|
|
|
opts?: ILoginOptions,
|
|
|
|
) {
|
|
|
|
this.hsUrl = hsUrl;
|
|
|
|
this.isUrl = isUrl;
|
|
|
|
this.fallbackHsUrl = fallbackHsUrl;
|
|
|
|
this.flows = [];
|
|
|
|
this.defaultDeviceDisplayName = opts.defaultDeviceDisplayName;
|
|
|
|
this.tempClient = null; // memoize
|
2017-02-24 12:41:23 +01:00
|
|
|
}
|
|
|
|
|
2020-10-06 13:42:28 +02:00
|
|
|
public getHomeserverUrl(): string {
|
|
|
|
return this.hsUrl;
|
2017-02-24 12:41:23 +01:00
|
|
|
}
|
|
|
|
|
2020-10-06 13:42:28 +02:00
|
|
|
public getIdentityServerUrl(): string {
|
|
|
|
return this.isUrl;
|
2017-02-24 12:41:23 +01:00
|
|
|
}
|
|
|
|
|
2020-10-06 13:42:28 +02:00
|
|
|
public setHomeserverUrl(hsUrl: string): void {
|
|
|
|
this.tempClient = null; // clear memoization
|
|
|
|
this.hsUrl = hsUrl;
|
2017-02-24 12:41:23 +01:00
|
|
|
}
|
|
|
|
|
2020-10-06 13:42:28 +02:00
|
|
|
public setIdentityServerUrl(isUrl: string): void {
|
|
|
|
this.tempClient = null; // clear memoization
|
|
|
|
this.isUrl = isUrl;
|
2017-02-24 12:41:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get a temporary MatrixClient, which can be used for login or register
|
|
|
|
* requests.
|
2019-01-24 01:32:36 +01:00
|
|
|
* @returns {MatrixClient}
|
2017-02-24 12:41:23 +01:00
|
|
|
*/
|
2020-10-06 13:42:28 +02:00
|
|
|
public createTemporaryClient(): MatrixClient {
|
|
|
|
if (this.tempClient) return this.tempClient; // use memoization
|
|
|
|
return this.tempClient = Matrix.createClient({
|
|
|
|
baseUrl: this.hsUrl,
|
|
|
|
idBaseUrl: this.isUrl,
|
2017-02-24 12:41:23 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-11-23 18:10:15 +01:00
|
|
|
public async getFlows(): Promise<Array<LoginFlow>> {
|
2020-03-02 15:59:54 +01:00
|
|
|
const client = this.createTemporaryClient();
|
2020-10-06 13:42:28 +02:00
|
|
|
const { flows } = await client.loginFlows();
|
|
|
|
this.flows = flows;
|
|
|
|
return this.flows;
|
2017-02-24 12:41:23 +01:00
|
|
|
}
|
|
|
|
|
2020-10-06 13:42:28 +02:00
|
|
|
public loginViaPassword(
|
|
|
|
username: string,
|
|
|
|
phoneCountry: string,
|
|
|
|
phoneNumber: string,
|
|
|
|
password: string,
|
|
|
|
): Promise<IMatrixClientCreds> {
|
2017-03-14 12:50:13 +01:00
|
|
|
const isEmail = username.indexOf("@") > 0;
|
|
|
|
|
|
|
|
let identifier;
|
|
|
|
if (phoneCountry && phoneNumber) {
|
|
|
|
identifier = {
|
|
|
|
type: 'm.id.phone',
|
|
|
|
country: phoneCountry,
|
2020-06-12 23:36:50 +02:00
|
|
|
phone: phoneNumber,
|
|
|
|
// XXX: Synapse historically wanted `number` and not `phone`
|
2017-03-14 12:50:13 +01:00
|
|
|
number: phoneNumber,
|
|
|
|
};
|
|
|
|
} else if (isEmail) {
|
|
|
|
identifier = {
|
|
|
|
type: 'm.id.thirdparty',
|
|
|
|
medium: 'email',
|
|
|
|
address: username,
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
identifier = {
|
|
|
|
type: 'm.id.user',
|
|
|
|
user: username,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
const loginParams = {
|
2020-10-06 13:42:28 +02:00
|
|
|
password,
|
|
|
|
identifier,
|
|
|
|
initial_device_display_name: this.defaultDeviceDisplayName,
|
2017-02-24 12:41:23 +01:00
|
|
|
};
|
|
|
|
|
2017-10-27 19:36:59 +02:00
|
|
|
const tryFallbackHs = (originalError) => {
|
2018-12-05 17:39:38 +01:00
|
|
|
return sendLoginRequest(
|
2020-10-06 13:42:28 +02:00
|
|
|
this.fallbackHsUrl, this.isUrl, 'm.login.password', loginParams,
|
2019-01-24 01:32:36 +01:00
|
|
|
).catch((fallbackError) => {
|
|
|
|
console.log("fallback HS login failed", fallbackError);
|
2017-10-27 19:36:59 +02:00
|
|
|
// throw the original error
|
|
|
|
throw originalError;
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
let originalLoginError = null;
|
2018-12-05 17:39:38 +01:00
|
|
|
return sendLoginRequest(
|
2020-10-06 13:42:28 +02:00
|
|
|
this.hsUrl, this.isUrl, 'm.login.password', loginParams,
|
2018-12-05 17:39:38 +01:00
|
|
|
).catch((error) => {
|
2017-10-27 19:36:59 +02:00
|
|
|
originalLoginError = error;
|
2017-06-08 16:49:48 +02:00
|
|
|
if (error.httpStatus === 403) {
|
2020-10-06 13:42:28 +02:00
|
|
|
if (this.fallbackHsUrl) {
|
2017-10-27 19:36:59 +02:00
|
|
|
return tryFallbackHs(originalLoginError);
|
2017-02-24 12:41:23 +01:00
|
|
|
}
|
|
|
|
}
|
2017-10-27 19:36:59 +02:00
|
|
|
throw originalLoginError;
|
2017-10-30 18:04:21 +01:00
|
|
|
}).catch((error) => {
|
|
|
|
console.log("Login failed", error);
|
|
|
|
throw error;
|
2017-02-24 12:41:23 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2018-12-05 17:39:38 +01:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Send a login request to the given server, and format the response
|
|
|
|
* as a MatrixClientCreds
|
|
|
|
*
|
|
|
|
* @param {string} hsUrl the base url of the Homeserver used to log in.
|
|
|
|
* @param {string} isUrl the base url of the default identity server
|
|
|
|
* @param {string} loginType the type of login to do
|
2020-10-06 13:42:28 +02:00
|
|
|
* @param {ILoginParams} loginParams the parameters for the login
|
2018-12-05 17:39:38 +01:00
|
|
|
*
|
|
|
|
* @returns {MatrixClientCreds}
|
|
|
|
*/
|
2020-10-06 13:42:28 +02:00
|
|
|
export async function sendLoginRequest(
|
|
|
|
hsUrl: string,
|
|
|
|
isUrl: string,
|
|
|
|
loginType: string,
|
|
|
|
loginParams: ILoginParams,
|
|
|
|
): Promise<IMatrixClientCreds> {
|
2018-12-05 17:39:38 +01:00
|
|
|
const client = Matrix.createClient({
|
|
|
|
baseUrl: hsUrl,
|
|
|
|
idBaseUrl: isUrl,
|
|
|
|
});
|
|
|
|
|
|
|
|
const data = await client.login(loginType, loginParams);
|
|
|
|
|
2018-11-28 01:33:20 +01:00
|
|
|
const wellknown = data.well_known;
|
|
|
|
if (wellknown) {
|
|
|
|
if (wellknown["m.homeserver"] && wellknown["m.homeserver"]["base_url"]) {
|
|
|
|
hsUrl = wellknown["m.homeserver"]["base_url"];
|
|
|
|
console.log(`Overrode homeserver setting with ${hsUrl} from login response`);
|
|
|
|
}
|
|
|
|
if (wellknown["m.identity_server"] && wellknown["m.identity_server"]["base_url"]) {
|
|
|
|
// TODO: should we prompt here?
|
|
|
|
isUrl = wellknown["m.identity_server"]["base_url"];
|
|
|
|
console.log(`Overrode IS setting with ${isUrl} from login response`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-08 17:35:17 +02:00
|
|
|
const creds: IMatrixClientCreds = {
|
2018-12-05 17:39:38 +01:00
|
|
|
homeserverUrl: hsUrl,
|
|
|
|
identityServerUrl: isUrl,
|
|
|
|
userId: data.user_id,
|
|
|
|
deviceId: data.device_id,
|
|
|
|
accessToken: data.access_token,
|
|
|
|
};
|
2020-10-08 17:35:17 +02:00
|
|
|
|
|
|
|
SecurityCustomisations.examineLoginResponse?.(data, creds);
|
|
|
|
|
|
|
|
return creds;
|
2018-12-05 17:39:38 +01:00
|
|
|
}
|