Skip to content
This repository has been archived by the owner on Feb 18, 2024. It is now read-only.

Commit

Permalink
Merge pull request #95 from pinusc/well-known-fix
Browse files Browse the repository at this point in the history
Login fix and improvements
  • Loading branch information
farooqkz authored Feb 14, 2024
2 parents 5fb01bd + c91edc5 commit e15b3e7
Show file tree
Hide file tree
Showing 5 changed files with 198 additions and 183 deletions.
9 changes: 7 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as localforage from "localforage";
import Matrix from "./Matrix";
import Login from "./Login";
import Guide from "./Guide";
import { LoginData } from "./types";
import { LoginData, WellKnown } from "./types";

interface AppState {
state: string | null;
Expand All @@ -15,12 +15,14 @@ interface AppState {

class App extends Component<{}, AppState> {
private loginData: null | LoginData;
private well_known: null | WellKnown;
private timeout: null | number;
public state: AppState;

constructor(props: {}) {
super(props);
this.loginData = null;
this.well_known = null;
this.timeout = null;
this.state = {
state: null,
Expand All @@ -35,6 +37,9 @@ class App extends Component<{}, AppState> {
this.setState({ state: "login" });
}
});
localforage.getItem("well_known").then((well_known: unknown) => {
this.well_known = well_known as WellKnown;
});
localforage.getItem("guide").then((value: unknown) => {
this.setState({ guide: Boolean(value) });
});
Expand Down Expand Up @@ -78,7 +83,7 @@ class App extends Component<{}, AppState> {
}

if (state === "matrix") {
return <Matrix data={this.loginData} />;
return <Matrix data={this.loginData} well_known={this.well_known} />;
}
alert("Some error occured. This must not happen");
window.close();
Expand Down
111 changes: 26 additions & 85 deletions src/Login.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import { Component } from "inferno";
import { createClient } from "matrix-js-sdk";
import * as localforage from "localforage";
import { TextListItem, TextInput, Header, SoftKey, ListViewKeyed } from "KaiUI";
import shared from "./shared";
import { fetch as customFetch } from "./fetch";
import { WellKnown } from "./types";
import LoginWithQR from "./LoginWithQR";
import LoginHandler from "./LoginHandler";
import { LoginFlow } from "matrix-js-sdk/lib/@types/auth";

interface LoginState {
Expand All @@ -15,14 +11,13 @@ interface LoginState {
}

class Login extends Component<{}, LoginState> {
private homeserverUrl: string;
private homeserverName: string;
private loginFlows: LoginFlow[];
private readonly stageNames: string[];
private selectedLoginFlow?: LoginFlow;
public state: LoginState;
private loginHandler: LoginHandler;
private homeserverName: string;
private username: string;
private password: string;
public state: LoginState;

cursorChangeCb = (cursor: number) => {
this.setState({ cursor: cursor });
Expand Down Expand Up @@ -59,87 +54,33 @@ class Login extends Component<{}, LoginState> {
}
};

doLogin = () => {
if (!this.selectedLoginFlow) {
throw new Error("selectedLoginFlow is not defined");
}
switch (this.selectedLoginFlow.type) {
case "m.login.password":
shared.mClient
.loginWithPassword(
`@${this.username}:${this.homeserverName}`,
this.password
)
.then((result: any) => {
localforage.setItem("login", result).then(() => {
alert("Logged in as " + this.username);
window.location = window.location;
});
})
.catch((err: any) => {
switch (err.errcode) {
case "M_FORBIDDEN":
alert("Incorrect login credentials");
break;
case "M_USER_DEACTIVATED":
alert("This account has been deactivated");
break;
case "M_LIMIT_EXCEEDED":
const retry = Math.ceil(err.retry_after_ms / 1000);
alert("Too many requests! Retry after" + retry.toString());
break;
default:
alert("Login failed for some unknown reason");
break;
}
});
break;
default:
alert("Invalid/unsupported login method. This is likely a bug");
break;
}
};

rightCb = () => {
switch (this.state.stage) {
case 0:
this.homeserverName = this.homeserverName.replace("https://", "");
this.homeserverName = this.homeserverName.replace("http://", "");
fetch(`https://${this.homeserverName}/.well-known/matrix/client`)
.then((r: Response) => {
if (r.ok) {
r.json().then((j: WellKnown) => {
this.homeserverUrl = j["m.homeserver"].base_url;
shared.mClient = createClient({
baseUrl: this.homeserverUrl,
fetchFn: customFetch,
});
shared.mClient
.loginFlows()
.then((result) => {
this.loginFlows = result.flows;
this.setState({ cursor: 0, stage: 1 });
})
.catch((e: any) => console.log(e));
});
} else {
alert(
"Cannot connect to homeserver. Are you sure the address valid?"
);
}
})
.catch((e) => console.log(e));
this.loginHandler.findHomeserver(this.homeserverName).then( () => {
this.setState({ cursor: 0, stage: 1})
}).catch((e: any) => {
window.alert("Could not connect to homeserver");
console.log(e);
});
break;
case 1:
this.selectedLoginFlow = this.loginFlows[this.state.cursor];
this.selectedLoginFlow = this.loginHandler.loginFlows[this.state.cursor];
if (this.selectedLoginFlow.type !== "m.login.password") {
window.alert("The selected login method is not implemented, yet.");
} else {
this.setState({ cursor: 0, stage: 2 });
}
break;
case 2:
this.doLogin();
let loginData = {'username': this.username, 'password': this.password};
if (this.selectedLoginFlow !== undefined) {
this.loginHandler.doLogin(this.selectedLoginFlow, loginData).then(() => {
window.location = window.location;
}).catch((e) => alert(e.message));
} else {
throw new Error("Undefined selectedLoginFlow")
}
break;
default:
alert("Invalid stage!");
Expand All @@ -155,12 +96,11 @@ class Login extends Component<{}, LoginState> {

constructor(props: any) {
super(props);
this.stageNames = ["Login Info", "Login method", "Login"];
this.loginFlows = [];
this.homeserverName = "";
this.username = "";
this.password = "";
this.homeserverName = "";
this.homeserverUrl = "";
this.stageNames = ["Login Info", "Login method", "Login"];
this.loginHandler = new LoginHandler()
this.state = {
stage: 0,
cursor: 0,
Expand All @@ -178,7 +118,8 @@ class Login extends Component<{}, LoginState> {

render() {
if (this.state.loginWithQr) {
return <LoginWithQR />;
return <LoginWithQR loginHandler={this.loginHandler} />;
// return <LoginWithQR />;
}
let listViewChildren;
switch (this.state.stage) {
Expand All @@ -204,7 +145,7 @@ class Login extends Component<{}, LoginState> {
},
{
tertiary:
"Press Call button and scan a QR in the following format to login with QR code instead of typing all these(PASS = password authentication): PASS server_name username password",
"Press Call button and scan a QR in the following format to login with QR code instead of typing all these (PASS = password authentication): PASS server_name username password",
type: "text",
key: "qrHint",
},
Expand All @@ -214,7 +155,7 @@ class Login extends Component<{}, LoginState> {
});
break;
case 1:
listViewChildren = this.loginFlows.map((flow: LoginFlow) => {
listViewChildren = this.loginHandler.loginFlows.map((flow: LoginFlow) => {
return <TextListItem key={"flow" + flow.type} primary={flow.type} />;
});
break;
Expand Down
118 changes: 118 additions & 0 deletions src/LoginHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { fetch as customFetch } from "./fetch";
import { WellKnown } from "./types";
import { LoginFlow } from "matrix-js-sdk/lib/@types/auth";
import * as localforage from "localforage";
import { createClient } from "matrix-js-sdk";
import shared from "./shared";

export default class LoginHandler {
public base_url: string;
public homeserverName: string;
public username: string;
public password: string;
public loginFlows: LoginFlow[];

constructor() {
this.homeserverName = "";
this.username = "";
this.password = "";
this.loginFlows = [];
this.base_url = "";
}

private setWellKnown(well_known: WellKnown) {
return localforage.setItem("well_known", well_known);
}

public async doLogin(loginFlow: LoginFlow, loginData: any) {
// TODO implement more login flows
// Instead of implementing them one by one, consider using mClient.login
// and passing loginData (which needs to be properly formed according to their spec)
// (may or may not be a bad idea)
try {
let loginResult: any;
let username: string = `@${loginData.username}:${this.homeserverName}`;
switch (loginFlow.type) {
case "m.login.password":
let password: string = loginData.password;
loginResult = await shared.mClient
.loginWithPassword(username, password);
break;
default:
throw new Error("Unsupported");
break;
}
if (loginResult.well_known) {
this.setWellKnown(loginResult.well_known)
console.log("Received a well_known from client login property. Updating previous settings.")
console.log(loginResult.well_known)
}
await localforage.setItem("login", loginResult);
alert("Logged in as " + username);
} catch (e: any) {
let message: string;
switch (e.errcode) {
case "M_FORBIDDEN":
message = "Incorrect login credentials";
break;
case "M_USER_DEACTIVATED":
message = "This account has been deactivated";
break;
case "M_LIMIT_EXCEEDED":
const retry = Math.ceil(e.retry_after_ms / 1000);
message = `Too many requests! Retry after ${retry.toString()}`;
break;
default:
if (e.message === "Unsupported") {
message = "Login flow selected is unsupported"
} else if (e.errcode) {
message = e.errcode;
} else {
message = `Login failed for some unknown reason: ${e.message}`;
}
break;
}
throw new Error(message)
}
}

public async findHomeserver(name: string) {
name = name.replace("https://", "");
name = name.replace("http://", "");
this.homeserverName = name;
let base_url: string = "";
let well_known_url = `https://${name}/.well-known/matrix/client`;
try {
let r: Response = await fetch(well_known_url);
if (!r.ok) {
throw new Error("404");

}
let well_known: WellKnown = await r.json();
base_url = well_known["m.homeserver"].base_url;
} catch (e: any) {
console.log(`.well-known not found or malformed at ${well_known_url}`);
base_url = "https://" + name;
} finally {
this.base_url = base_url;
try {
shared.mClient = createClient({
baseUrl: base_url,
fetchFn: customFetch,
});
let result = await shared.mClient.loginFlows()
if (! result.flows) {
throw new Error("Got no flows");
}
this.loginFlows = result.flows;
} catch (e) {
alert(`No server found at ${base_url}`)
console.log(e);
}
this.setWellKnown({
"m.homeserver": {"base_url": base_url},
"m.identity_server": {"base_url": "https://vector.im"}, // TODO Where to infer this outside of actual .well-known?
})
}
}
}
Loading

0 comments on commit e15b3e7

Please sign in to comment.