import { MatrixClient } from "./MatrixClient";
/**
* Functions for interacting with Matrix prior to having an access token. Intended
* to be used for logging in/registering to get a MatrixClient instance.
*
* By design, this limits the options used to create the MatrixClient. To specify
* custom elements to the client, get the access token from the returned client
* and create a new MatrixClient instance. Due to the nature of Matrix, it is
* also recommended to use the homeserverUrl from the generated MatrixClient as
* it may be different from that given to the MatrixAuth class.
*/
export class MatrixAuth {
/**
* Creates a new MatrixAuth class for creating a MatrixClient
* @param {string} homeserverUrl The homeserver URL to authenticate against.
*/
public constructor(private homeserverUrl: string) {
// nothing to do
}
/**
* Generate a client with no access token so we can reuse the doRequest
* logic already written.
*/
private createTemplateClient(): MatrixClient {
return new MatrixClient(this.homeserverUrl, "");
}
/**
* Performs simple registration using a password for the account. This will
* assume the server supports the m.login.password flow for registration, and
* will attempt to complete only that stage. The caller is expected to determine
* if the homeserver supports registration prior to invocation.
* @param {string} localpart The localpart (username) to register
* @param {string} password The password to register with
* @param {string} deviceName The name of the newly created device. Optional.
* @returns {Promise<MatrixClient>} Resolves to a logged-in MatrixClient
*/
public async passwordRegister(localpart: string, password: string, deviceName?: string): Promise<MatrixClient> {
// First try and complete the stage without UIA in hopes the server is kind to us:
const body = {
username: localpart,
password: password,
initial_device_display_name: deviceName,
};
let response;
try {
response = await this.createTemplateClient().doRequest("POST", "/_matrix/client/v3/register", null, body);
} catch (e) {
if (e.statusCode === 401) {
if (typeof (e.body) === "string") e.body = JSON.parse(e.body);
if (!e.body) throw new Error(JSON.stringify(Object.keys(e)));
// 401 means we need to do UIA, so try and complete a stage
const sessionId = e.body['session'];
const expectedFlow = ["m.login.dummy"];
let hasFlow = false;
for (const flow of e.body['flows']) {
const stages = flow['stages'];
if (stages.length !== expectedFlow.length) continue;
let stagesMatch = true;
for (let i = 0; i < stages.length; i++) {
if (stages[i] !== expectedFlow[i]) {
stagesMatch = false;
break;
}
}
if (stagesMatch) {
hasFlow = true;
break;
}
}
if (!hasFlow) throw new Error("Failed to find appropriate login flow in User-Interactive Authentication");
body['auth'] = {
type: expectedFlow[0], // HACK: We assume we only have one entry here
session: sessionId,
};
response = await this.createTemplateClient().doRequest("POST", "/_matrix/client/v3/register", null, body);
}
}
if (!response) throw new Error("Failed to register");
const accessToken = response['access_token'];
if (!accessToken) throw new Error("No access token returned");
return new MatrixClient(this.homeserverUrl, accessToken);
}
/**
* Performs simple password login with the homeserver. The caller is
* expected to confirm if the homeserver supports this login flow prior
* to invocation.
* @param {string} username The username (localpart or user ID) to log in with
* @param {string} password The password for the account
* @param {string} deviceName The name of the newly created device. Optional.
* @returns {Promise<MatrixClient>} Resolves to a logged-in MatrixClient
*/
public async passwordLogin(username: string, password: string, deviceName?: string): Promise<MatrixClient> {
const body = {
type: "m.login.password",
identifier: {
type: "m.id.user",
user: username,
},
password: password,
initial_device_display_name: deviceName,
};
const response = await this.createTemplateClient().doRequest("POST", "/_matrix/client/v3/login", null, body);
const accessToken = response["access_token"];
if (!accessToken) throw new Error("Expected access token in response - got nothing");
let homeserverUrl = this.homeserverUrl;
if (response['well_known'] && response['well_known']['m.homeserver'] && response['well_known']['m.homeserver']['base_url']) {
homeserverUrl = response['well_known']['m.homeserver']['base_url'];
}
return new MatrixClient(homeserverUrl, accessToken);
}
}
Source