From e0fcec910220bfb9965b1f0c1ae6e91a93f414f9 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 27 Jul 2021 19:23:46 +0530 Subject: [PATCH 01/81] Add method to query login types Signed-off-by: RMidhunSuresh --- src/domain/LoginViewModel.js | 1 + src/matrix/SessionContainer.js | 6 ++++++ src/matrix/net/HomeServerApi.js | 4 ++++ 3 files changed, 11 insertions(+) diff --git a/src/domain/LoginViewModel.js b/src/domain/LoginViewModel.js index 1b52e1a5..9e1db503 100644 --- a/src/domain/LoginViewModel.js +++ b/src/domain/LoginViewModel.js @@ -49,6 +49,7 @@ export class LoginViewModel extends ViewModel { this._loadViewModel = this.track(new SessionLoadViewModel(this.childOptions({ createAndStartSessionContainer: () => { this._sessionContainer = this._createSessionContainer(); + this._sessionContainer.queryLogin(homeserver); this._sessionContainer.startWithLogin(homeserver, username, password); return this._sessionContainer; }, diff --git a/src/matrix/SessionContainer.js b/src/matrix/SessionContainer.js index 07c4a870..1106644c 100644 --- a/src/matrix/SessionContainer.js +++ b/src/matrix/SessionContainer.js @@ -97,6 +97,12 @@ export class SessionContainer { }); } + async queryLogin(homeServer) { + homeServer = normalizeHomeserver(homeServer); + const hsApi = new HomeServerApi({homeServer, request: this._platform.request}); + const response = hsApi.queryLogin(); + } + async startWithLogin(homeServer, username, password) { if (this._status.get() !== LoadStatus.NotLoading) { return; diff --git a/src/matrix/net/HomeServerApi.js b/src/matrix/net/HomeServerApi.js index a9b63f8e..6dec6bd6 100644 --- a/src/matrix/net/HomeServerApi.js +++ b/src/matrix/net/HomeServerApi.js @@ -134,6 +134,10 @@ export class HomeServerApi { return this._get(`/rooms/${encodeURIComponent(roomId)}/state/${encodeURIComponent(eventType)}/${encodeURIComponent(stateKey)}`, {}, null, options); } + queryLogin() { + return this._unauthedRequest("GET", this._url("/login"), null, null, null); + } + passwordLogin(username, password, initialDeviceDisplayName, options = null) { return this._unauthedRequest("POST", this._url("/login"), null, { "type": "m.login.password", From 20765d96885948abe9f163ec4a962edeb46ab1e9 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 30 Jul 2021 13:34:25 +0530 Subject: [PATCH 02/81] Create LoginMethod for password login Signed-off-by: RMidhunSuresh --- src/matrix/LoginMethod.js | 10 ++++++++++ src/matrix/PasswordLoginMethod.js | 15 +++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 src/matrix/LoginMethod.js create mode 100644 src/matrix/PasswordLoginMethod.js diff --git a/src/matrix/LoginMethod.js b/src/matrix/LoginMethod.js new file mode 100644 index 00000000..0d57499e --- /dev/null +++ b/src/matrix/LoginMethod.js @@ -0,0 +1,10 @@ +export class LoginMethod { + constructor({homeServer, platform}) { + this.homeServer = homeServer; + this._platform = platform; + } + + async login(hsApi, deviceName) { + throw("Not Implemented"); + } +} diff --git a/src/matrix/PasswordLoginMethod.js b/src/matrix/PasswordLoginMethod.js new file mode 100644 index 00000000..81f2204f --- /dev/null +++ b/src/matrix/PasswordLoginMethod.js @@ -0,0 +1,15 @@ +import { LoginMethod } from "./LoginMethod.js"; + +export class PasswordLoginMethod extends LoginMethod { + constructor(options) { + super(options); + this.username = options.username; + this.password = options.password; + } + + async login(hsApi, deviceName) { + return this._platform.logger.run("passwordLogin", async log => + await hsApi.passwordLogin(this.username, this.password, deviceName, {log}).response() + ); + } +} From a53e29767f8d69a03c68dcd7128d77c883400dd5 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 30 Jul 2021 13:45:35 +0530 Subject: [PATCH 03/81] Rewrite password login to use PasswordLoginMethod Signed-off-by: RMidhunSuresh --- src/domain/LoginViewModel.js | 8 +++++--- src/domain/SessionLoadViewModel.js | 2 +- src/matrix/SessionContainer.js | 24 ++++++++++++++++++++---- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/domain/LoginViewModel.js b/src/domain/LoginViewModel.js index 9e1db503..4e9993aa 100644 --- a/src/domain/LoginViewModel.js +++ b/src/domain/LoginViewModel.js @@ -47,10 +47,12 @@ export class LoginViewModel extends ViewModel { this._loadViewModel = this.disposeTracked(this._loadViewModel); } this._loadViewModel = this.track(new SessionLoadViewModel(this.childOptions({ - createAndStartSessionContainer: () => { + createAndStartSessionContainer: async () => { this._sessionContainer = this._createSessionContainer(); - this._sessionContainer.queryLogin(homeserver); - this._sessionContainer.startWithLogin(homeserver, username, password); + const loginOptions = await this._sessionContainer.queryLogin(homeserver); + if (loginOptions.password) { + this._sessionContainer.startWithLogin(loginOptions.password(username, password)); + } return this._sessionContainer; }, ready: sessionContainer => { diff --git a/src/domain/SessionLoadViewModel.js b/src/domain/SessionLoadViewModel.js index 0b785e47..83bf5966 100644 --- a/src/domain/SessionLoadViewModel.js +++ b/src/domain/SessionLoadViewModel.js @@ -38,7 +38,7 @@ export class SessionLoadViewModel extends ViewModel { try { this._loading = true; this.emitChange("loading"); - this._sessionContainer = this._createAndStartSessionContainer(); + this._sessionContainer = await this._createAndStartSessionContainer(); this._waitHandle = this._sessionContainer.loadStatus.waitFor(s => { this.emitChange("loadLabel"); // wait for initial sync, but not catchup sync diff --git a/src/matrix/SessionContainer.js b/src/matrix/SessionContainer.js index 1106644c..cfc83255 100644 --- a/src/matrix/SessionContainer.js +++ b/src/matrix/SessionContainer.js @@ -23,6 +23,7 @@ import {MediaRepository} from "./net/MediaRepository.js"; import {RequestScheduler} from "./net/RequestScheduler.js"; import {Sync, SyncStatus} from "./Sync.js"; import {Session} from "./Session.js"; +import {PasswordLoginMethod} from "./PasswordLoginMethod.js"; export const LoadStatus = createEnum( "NotLoading", @@ -97,25 +98,40 @@ export class SessionContainer { }); } + parseLoginOptions(options, homeServer) { + /* Take server response and return new object which has two props password and sso which + implements LoginMethod + */ + const flows = options.flows; + const result = {}; + for (const flow of flows) { + if (flow.type === "m.login.password") { + result.password = (username, password) => new PasswordLoginMethod({homeServer, username, password, platform: this._platform}); + } + } + return result; + } + async queryLogin(homeServer) { homeServer = normalizeHomeserver(homeServer); const hsApi = new HomeServerApi({homeServer, request: this._platform.request}); - const response = hsApi.queryLogin(); + const response = await hsApi.queryLogin().response(); + return this.parseLoginOptions(response, homeServer); } - async startWithLogin(homeServer, username, password) { + async startWithLogin(loginMethod) { if (this._status.get() !== LoadStatus.NotLoading) { return; } await this._platform.logger.run("login", async log => { this._status.set(LoadStatus.Login); - homeServer = normalizeHomeserver(homeServer); const clock = this._platform.clock; let sessionInfo; try { const request = this._platform.request; + const homeServer = normalizeHomeserver(loginMethod.homeServer); const hsApi = new HomeServerApi({homeServer, request}); - const loginData = await hsApi.passwordLogin(username, password, "Hydrogen", {log}).response(); + const loginData = await loginMethod.login(hsApi, "Hydrogen", log); const sessionId = this.createNewSessionId(); sessionInfo = { id: sessionId, From acfe3f30db265af82cd7092ae6f5eeae2ac76b80 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 30 Jul 2021 14:06:32 +0530 Subject: [PATCH 04/81] Make lint happy Signed-off-by: RMidhunSuresh --- src/matrix/LoginMethod.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/matrix/LoginMethod.js b/src/matrix/LoginMethod.js index 0d57499e..51f3754a 100644 --- a/src/matrix/LoginMethod.js +++ b/src/matrix/LoginMethod.js @@ -4,6 +4,7 @@ export class LoginMethod { this._platform = platform; } + // eslint-disable-next-line no-unused-vars async login(hsApi, deviceName) { throw("Not Implemented"); } From 72fb7f679b31c131fd535d3889312ffc2c7ba5f2 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 30 Jul 2021 14:07:18 +0530 Subject: [PATCH 05/81] Add license headers Signed-off-by: RMidhunSuresh --- src/matrix/LoginMethod.js | 16 ++++++++++++++++ src/matrix/PasswordLoginMethod.js | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/matrix/LoginMethod.js b/src/matrix/LoginMethod.js index 51f3754a..9ec51a90 100644 --- a/src/matrix/LoginMethod.js +++ b/src/matrix/LoginMethod.js @@ -1,3 +1,19 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +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. +*/ + export class LoginMethod { constructor({homeServer, platform}) { this.homeServer = homeServer; diff --git a/src/matrix/PasswordLoginMethod.js b/src/matrix/PasswordLoginMethod.js index 81f2204f..155542a6 100644 --- a/src/matrix/PasswordLoginMethod.js +++ b/src/matrix/PasswordLoginMethod.js @@ -1,3 +1,19 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +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. +*/ + import { LoginMethod } from "./LoginMethod.js"; export class PasswordLoginMethod extends LoginMethod { From 46b7d9a3737d184767c7ab984a1d478dc4e040ab Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 30 Jul 2021 14:11:00 +0530 Subject: [PATCH 06/81] Add explaining comment Signed-off-by: RMidhunSuresh --- src/matrix/LoginMethod.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/matrix/LoginMethod.js b/src/matrix/LoginMethod.js index 9ec51a90..2dbab85e 100644 --- a/src/matrix/LoginMethod.js +++ b/src/matrix/LoginMethod.js @@ -22,6 +22,10 @@ export class LoginMethod { // eslint-disable-next-line no-unused-vars async login(hsApi, deviceName) { + /* + Regardless of the login method, SessionContainer.startWithLogin() + can do SomeLoginMethod.login() + */ throw("Not Implemented"); } } From 730a6b2d0aaf1bf5836e8a7d9bf9556635f8a8d3 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 30 Jul 2021 23:23:52 +0530 Subject: [PATCH 07/81] Move files to separate directory Signed-off-by: RMidhunSuresh --- src/matrix/SessionContainer.js | 2 +- src/matrix/{ => login}/LoginMethod.js | 0 src/matrix/{ => login}/PasswordLoginMethod.js | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename src/matrix/{ => login}/LoginMethod.js (100%) rename src/matrix/{ => login}/PasswordLoginMethod.js (100%) diff --git a/src/matrix/SessionContainer.js b/src/matrix/SessionContainer.js index cfc83255..4b112080 100644 --- a/src/matrix/SessionContainer.js +++ b/src/matrix/SessionContainer.js @@ -23,7 +23,7 @@ import {MediaRepository} from "./net/MediaRepository.js"; import {RequestScheduler} from "./net/RequestScheduler.js"; import {Sync, SyncStatus} from "./Sync.js"; import {Session} from "./Session.js"; -import {PasswordLoginMethod} from "./PasswordLoginMethod.js"; +import {PasswordLoginMethod} from "./login/PasswordLoginMethod.js"; export const LoadStatus = createEnum( "NotLoading", diff --git a/src/matrix/LoginMethod.js b/src/matrix/login/LoginMethod.js similarity index 100% rename from src/matrix/LoginMethod.js rename to src/matrix/login/LoginMethod.js diff --git a/src/matrix/PasswordLoginMethod.js b/src/matrix/login/PasswordLoginMethod.js similarity index 100% rename from src/matrix/PasswordLoginMethod.js rename to src/matrix/login/PasswordLoginMethod.js From 9651817c5bef3699bfb4cd6dad6e36f7b764aa3c Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 30 Jul 2021 23:24:32 +0530 Subject: [PATCH 08/81] Formatting fix Signed-off-by: RMidhunSuresh --- src/matrix/login/PasswordLoginMethod.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/matrix/login/PasswordLoginMethod.js b/src/matrix/login/PasswordLoginMethod.js index 155542a6..abe9c23d 100644 --- a/src/matrix/login/PasswordLoginMethod.js +++ b/src/matrix/login/PasswordLoginMethod.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { LoginMethod } from "./LoginMethod.js"; +import {LoginMethod} from "./LoginMethod.js"; export class PasswordLoginMethod extends LoginMethod { constructor(options) { From f3946fcdf37b2470871ffdcc569f34ececa71527 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 2 Aug 2021 15:30:21 +0530 Subject: [PATCH 09/81] Pass log as argument Signed-off-by: RMidhunSuresh --- src/matrix/SessionContainer.js | 2 +- src/matrix/login/LoginMethod.js | 5 ++--- src/matrix/login/PasswordLoginMethod.js | 6 ++---- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/matrix/SessionContainer.js b/src/matrix/SessionContainer.js index 4b112080..85aa979c 100644 --- a/src/matrix/SessionContainer.js +++ b/src/matrix/SessionContainer.js @@ -106,7 +106,7 @@ export class SessionContainer { const result = {}; for (const flow of flows) { if (flow.type === "m.login.password") { - result.password = (username, password) => new PasswordLoginMethod({homeServer, username, password, platform: this._platform}); + result.password = (username, password) => new PasswordLoginMethod({homeServer, username, password}); } } return result; diff --git a/src/matrix/login/LoginMethod.js b/src/matrix/login/LoginMethod.js index 2dbab85e..fee8e845 100644 --- a/src/matrix/login/LoginMethod.js +++ b/src/matrix/login/LoginMethod.js @@ -15,13 +15,12 @@ limitations under the License. */ export class LoginMethod { - constructor({homeServer, platform}) { + constructor({homeServer}) { this.homeServer = homeServer; - this._platform = platform; } // eslint-disable-next-line no-unused-vars - async login(hsApi, deviceName) { + async login(hsApi, deviceName, log) { /* Regardless of the login method, SessionContainer.startWithLogin() can do SomeLoginMethod.login() diff --git a/src/matrix/login/PasswordLoginMethod.js b/src/matrix/login/PasswordLoginMethod.js index abe9c23d..5c90ccf8 100644 --- a/src/matrix/login/PasswordLoginMethod.js +++ b/src/matrix/login/PasswordLoginMethod.js @@ -23,9 +23,7 @@ export class PasswordLoginMethod extends LoginMethod { this.password = options.password; } - async login(hsApi, deviceName) { - return this._platform.logger.run("passwordLogin", async log => - await hsApi.passwordLogin(this.username, this.password, deviceName, {log}).response() - ); + async login(hsApi, deviceName, log) { + return await hsApi.passwordLogin(this.username, this.password, deviceName, {log}).response(); } } From 18e1c305f5da4804ceac7008370a45a986729cec Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 30 Jul 2021 22:51:54 +0530 Subject: [PATCH 10/81] Allow sso to be a root segment Signed-off-by: RMidhunSuresh --- src/domain/navigation/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/domain/navigation/index.js b/src/domain/navigation/index.js index dbac16ac..46839239 100644 --- a/src/domain/navigation/index.js +++ b/src/domain/navigation/index.js @@ -30,7 +30,7 @@ function allowsChild(parent, child) { switch (parent?.type) { case undefined: // allowed root segments - return type === "login" || type === "session"; + return type === "login" || type === "session" || type === "sso"; case "session": return type === "room" || type === "rooms" || type === "settings"; case "rooms": From 19664e54be7dddadf2516c32fcaea48a60b258b5 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 30 Jul 2021 22:52:40 +0530 Subject: [PATCH 11/81] Parse loginToken from query parameter Signed-off-by: RMidhunSuresh --- src/domain/navigation/index.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/domain/navigation/index.js b/src/domain/navigation/index.js index 46839239..a3b587df 100644 --- a/src/domain/navigation/index.js +++ b/src/domain/navigation/index.js @@ -152,6 +152,10 @@ export function parseUrlPath(urlPath, currentNavPath, defaultSessionId) { const userId = iterator.next().value; if (!userId) { break; } pushRightPanelSegment(segments, type, userId); + } else if (type.includes("loginToken")) { + // Special case for SSO-login with query parameter loginToken= + const loginToken = type.split("=").pop(); + segments.push(new Segment("sso", loginToken)); } else { // might be undefined, which will be turned into true by Segment const value = iterator.next().value; From e2d2291d8d3ac7c093ae110b270ee8217463d417 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 30 Jul 2021 23:01:44 +0530 Subject: [PATCH 12/81] Add test Signed-off-by: RMidhunSuresh --- src/domain/navigation/index.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/domain/navigation/index.js b/src/domain/navigation/index.js index a3b587df..868eec0e 100644 --- a/src/domain/navigation/index.js +++ b/src/domain/navigation/index.js @@ -232,6 +232,12 @@ export function tests() { const urlPath = stringifyPath(path); assert.equal(urlPath, "/session/1/rooms/a,b,c/1/details"); }, + "Parse loginToken query parameter into SSO segment": assert => { + const segments = parseUrlPath("/?loginToken=a1232aSD123"); + assert.equal(segments.length, 1); + assert.equal(segments[0].type, "sso"); + assert.equal(segments[0].value, "a1232aSD123"); + }, "parse grid url path with focused empty tile": assert => { const segments = parseUrlPath("/session/1/rooms/a,b,c/3"); assert.equal(segments.length, 3); From 3efadcb72cf6920a940ef97e743e7148408d1a76 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 30 Jul 2021 23:02:53 +0530 Subject: [PATCH 13/81] Add method that returns callback url Signed-off-by: RMidhunSuresh --- src/domain/navigation/URLRouter.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/domain/navigation/URLRouter.js b/src/domain/navigation/URLRouter.js index 28488129..b1b9950a 100644 --- a/src/domain/navigation/URLRouter.js +++ b/src/domain/navigation/URLRouter.js @@ -120,4 +120,8 @@ export class URLRouter { const urlPath = `${this._stringifyPath(this._navigation.path.until("session"))}/open-room/${roomId}`; return this._history.pathAsUrl(urlPath); } + + createSSOCallbackURL() { + return window.location.origin; + } } From 987a83b4cf4d28276e2a803a6df6d82f57848a45 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 30 Jul 2021 23:03:18 +0530 Subject: [PATCH 14/81] Add method to redirect to a specific URL Signed-off-by: RMidhunSuresh --- src/platform/web/Platform.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index f1410106..7e28f36f 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -240,6 +240,10 @@ export class Platform { return promise; } + openUrl(url) { + location.href = url; + } + parseHTML(html) { return parseHTML(html); } From 74f5e3048716acf6ae55e878d84bc4b29cafaa90 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Sat, 31 Jul 2021 17:10:22 +0530 Subject: [PATCH 15/81] Ignore sso segment in url Signed-off-by: RMidhunSuresh --- src/domain/navigation/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/domain/navigation/index.js b/src/domain/navigation/index.js index 868eec0e..8aa3cf97 100644 --- a/src/domain/navigation/index.js +++ b/src/domain/navigation/index.js @@ -185,7 +185,8 @@ export function stringifyPath(path) { } break; case "right-panel": - // Ignore right-panel in url + case "sso": + // Do not put these segments in URL continue; default: urlPath += `/${segment.type}`; From bed01851860e5ea0bce1253eff2fc732c91210b8 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Sun, 1 Aug 2021 13:54:16 +0530 Subject: [PATCH 16/81] Support loginToken query in History Signed-off-by: RMidhunSuresh --- src/platform/web/dom/History.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/platform/web/dom/History.js b/src/platform/web/dom/History.js index 92927d3f..68e4ef78 100644 --- a/src/platform/web/dom/History.js +++ b/src/platform/web/dom/History.js @@ -25,6 +25,14 @@ export class History extends BaseObservableValue { } get() { + /* + All URLS in Hydrogen will use /#/segment/value/... + But for SSO, we need to handle /?loginToken= + Handle that as a special case for now. + */ + if (document.location.search.includes("loginToken")) { + return document.location.search; + } return document.location.hash; } From b2613740b8c4beb21eac618c7765d711662201d2 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Sun, 15 Aug 2021 12:32:29 +0530 Subject: [PATCH 17/81] Add functionality to remove loginToken from URL Signed-off-by: RMidhunSuresh --- src/domain/navigation/URLRouter.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/domain/navigation/URLRouter.js b/src/domain/navigation/URLRouter.js index b1b9950a..586eec8a 100644 --- a/src/domain/navigation/URLRouter.js +++ b/src/domain/navigation/URLRouter.js @@ -124,4 +124,10 @@ export class URLRouter { createSSOCallbackURL() { return window.location.origin; } + + normalizeUrl() { + // Remove any queryParameters from the URL + // Gets rid of the loginToken after SSO + this._history.replaceUrlSilently(`${window.location.origin}/${window.location.hash}`); + } } From 75d71717d8d2d3a76f2fc7345bc38da07f7223f9 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Sat, 31 Jul 2021 15:24:50 +0530 Subject: [PATCH 18/81] Show link for SSO login Signed-off-by: RMidhunSuresh --- src/domain/LoginViewModel.js | 45 +++++++++++++++++++++++--- src/platform/web/ui/login/LoginView.js | 4 ++- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/src/domain/LoginViewModel.js b/src/domain/LoginViewModel.js index 4e9993aa..0cd474a9 100644 --- a/src/domain/LoginViewModel.js +++ b/src/domain/LoginViewModel.js @@ -17,6 +17,14 @@ limitations under the License. import {ViewModel} from "./ViewModel.js"; import {SessionLoadViewModel} from "./SessionLoadViewModel.js"; +function normalizeHomeserver(homeServer) { + try { + return new URL(homeServer).origin; + } catch (err) { + return new URL(`https://${homeServer}`).origin; + } +} + export class LoginViewModel extends ViewModel { constructor(options) { super(options); @@ -27,6 +35,8 @@ export class LoginViewModel extends ViewModel { this._sessionContainer = null; this._loadViewModel = null; this._loadViewModelSubscription = null; + this._supportsSSOLogin = false; + this.queryLogin(); } get defaultHomeServer() { return this._defaultHomeServer; } @@ -41,6 +51,31 @@ export class LoginViewModel extends ViewModel { } } + async queryLogin(homeServer = this.defaultHomeServer) { + // See if we support SSO, if so shows SSO link + /* For this, we'd need to poll queryLogin before we do login() + */ + if (!this._sessionContainer) { + this._sessionContainer = this._createSessionContainer(); + } + const normalizedHS = normalizeHomeserver(homeServer); + try { + this.loginOptions = await this._sessionContainer.queryLogin(normalizedHS); + this._supportsSSOLogin = !!this.loginOptions.sso; + } + catch (e) { + // Something went wrong, assume SSO is not supported + this._supportsSSOLogin = false; + console.error("Could not query login methods supported by the homeserver"); + } + this.emitChange("supportsSSOLogin"); + } + + queryLoginFromInput() { + const homeServer = document.querySelector("#homeserver").value; + this.queryLogin(homeServer); + } + async login(username, password, homeserver) { this._loadViewModelSubscription = this.disposeTracked(this._loadViewModelSubscription); if (this._loadViewModel) { @@ -48,10 +83,8 @@ export class LoginViewModel extends ViewModel { } this._loadViewModel = this.track(new SessionLoadViewModel(this.childOptions({ createAndStartSessionContainer: async () => { - this._sessionContainer = this._createSessionContainer(); - const loginOptions = await this._sessionContainer.queryLogin(homeserver); - if (loginOptions.password) { - this._sessionContainer.startWithLogin(loginOptions.password(username, password)); + if (this.loginOptions.password) { + this._sessionContainer.startWithLogin(this.loginOptions.password(username, password)); } return this._sessionContainer; }, @@ -76,6 +109,10 @@ export class LoginViewModel extends ViewModel { return this.urlCreator.urlForSegment("session"); } + get supportsSSOLogin() { + return this._supportsSSOLogin; + } + dispose() { super.dispose(); if (this._sessionContainer) { diff --git a/src/platform/web/ui/login/LoginView.js b/src/platform/web/ui/login/LoginView.js index 683bf42d..f8ddcf4c 100644 --- a/src/platform/web/ui/login/LoginView.js +++ b/src/platform/web/ui/login/LoginView.js @@ -37,7 +37,8 @@ export class LoginView extends TemplateView { id: "homeserver", type: "text", placeholder: vm.i18n`Your matrix homeserver`, - value: vm.defaultHomeServer, + value: vm.defaultHomeServer, + onChange: () => vm.queryLoginFromInput(), disabled }); @@ -67,6 +68,7 @@ export class LoginView extends TemplateView { }, vm.i18n`Log In`), ]), ]), + t.if(vm => vm.supportsSSOLogin, () => t.p(t.a({className:"SSO", href:"#"}, "Login with SSO"))), // use t.mapView rather than t.if to create a new view when the view model changes too t.p(hydrogenGithubLink(t)) ]) From 4b87887a4fa885f4f848807cc082191f718a978d Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Sat, 31 Jul 2021 17:07:44 +0530 Subject: [PATCH 19/81] Show completion view on sso segment Signed-off-by: RMidhunSuresh --- src/domain/RootViewModel.js | 11 ++++++++++- src/platform/web/ui/RootView.js | 3 +++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/domain/RootViewModel.js b/src/domain/RootViewModel.js index fca8d779..15b9d19e 100644 --- a/src/domain/RootViewModel.js +++ b/src/domain/RootViewModel.js @@ -35,12 +35,16 @@ export class RootViewModel extends ViewModel { async load() { this.track(this.navigation.observe("login").subscribe(() => this._applyNavigation())); this.track(this.navigation.observe("session").subscribe(() => this._applyNavigation())); + this.track(this.navigation.observe("sso").subscribe(() => { + this._applyNavigation(); + })); this._applyNavigation(true); } async _applyNavigation(shouldRestoreLastUrl) { const isLogin = this.navigation.observe("login").get(); const sessionId = this.navigation.observe("session").get(); + const SSOSegment = this.navigation.path.get("sso"); if (isLogin) { if (this.activeSection !== "login") { this._showLogin(); @@ -65,7 +69,10 @@ export class RootViewModel extends ViewModel { this._showSessionLoader(sessionId); } } - } else { + } else if (SSOSegment) { + this._setSection(() => this.showCompletionView = true); + } + else { try { if (!(shouldRestoreLastUrl && this.urlCreator.tryRestoreLastUrl())) { const sessionInfos = await this.platform.sessionInfoStorage.getAll(); @@ -147,6 +154,8 @@ export class RootViewModel extends ViewModel { return "picker"; } else if (this._sessionLoadViewModel) { return "loading"; + } else if (this.showCompletionView) { + return "sso"; } else { return "redirecting"; } diff --git a/src/platform/web/ui/RootView.js b/src/platform/web/ui/RootView.js index f60bb984..55cdac14 100644 --- a/src/platform/web/ui/RootView.js +++ b/src/platform/web/ui/RootView.js @@ -20,6 +20,7 @@ import {SessionLoadView} from "./login/SessionLoadView.js"; import {SessionPickerView} from "./login/SessionPickerView.js"; import {TemplateView} from "./general/TemplateView.js"; import {StaticView} from "./general/StaticView.js"; +import {CompleteSSOView} from "../../../domain/CompleteSSOView.js"; export class RootView extends TemplateView { render(t, vm) { @@ -42,6 +43,8 @@ export class RootView extends TemplateView { return new StaticView(t => t.p("Redirecting...")); case "loading": return new SessionLoadView(vm.sessionLoadViewModel); + case "sso": + return new CompleteSSOView(); default: throw new Error(`Unknown section: ${vm.activeSection}`); } From 2103adfc03d3405e4d37f2b64ff9214bd64cd1be Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Sat, 31 Jul 2021 17:08:41 +0530 Subject: [PATCH 20/81] Add view Signed-off-by: RMidhunSuresh --- src/domain/CompleteSSOView.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 src/domain/CompleteSSOView.js diff --git a/src/domain/CompleteSSOView.js b/src/domain/CompleteSSOView.js new file mode 100644 index 00000000..7a7f63f1 --- /dev/null +++ b/src/domain/CompleteSSOView.js @@ -0,0 +1,23 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +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. +*/ + +import {TemplateView} from "../platform/web/ui/general/TemplateView.js"; + +export class CompleteSSOView extends TemplateView { + render(t) { + return t.div({ className: "CompleteSSOView" }, "Finishing up SSO Login ..."); + } +} From 2c953e361d08ccebb41abe777cca69082addbd61 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Sun, 1 Aug 2021 20:21:04 +0530 Subject: [PATCH 21/81] Remove queryLoginFromInput() Signed-off-by: RMidhunSuresh --- src/domain/LoginViewModel.js | 5 ----- src/platform/web/ui/login/LoginView.js | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/domain/LoginViewModel.js b/src/domain/LoginViewModel.js index 0cd474a9..d75a715e 100644 --- a/src/domain/LoginViewModel.js +++ b/src/domain/LoginViewModel.js @@ -71,11 +71,6 @@ export class LoginViewModel extends ViewModel { this.emitChange("supportsSSOLogin"); } - queryLoginFromInput() { - const homeServer = document.querySelector("#homeserver").value; - this.queryLogin(homeServer); - } - async login(username, password, homeserver) { this._loadViewModelSubscription = this.disposeTracked(this._loadViewModelSubscription); if (this._loadViewModel) { diff --git a/src/platform/web/ui/login/LoginView.js b/src/platform/web/ui/login/LoginView.js index f8ddcf4c..56dfa96b 100644 --- a/src/platform/web/ui/login/LoginView.js +++ b/src/platform/web/ui/login/LoginView.js @@ -38,7 +38,7 @@ export class LoginView extends TemplateView { type: "text", placeholder: vm.i18n`Your matrix homeserver`, value: vm.defaultHomeServer, - onChange: () => vm.queryLoginFromInput(), + onChange: () => vm.queryLogin(homeserver.value), disabled }); From 0af27fc8dd963a343bb6925a4c77d08274bf4ba4 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Sun, 1 Aug 2021 20:35:04 +0530 Subject: [PATCH 22/81] Move normalizeHomeserver to LoginViewModel Signed-off-by: RMidhunSuresh --- src/domain/LoginViewModel.js | 1 + src/matrix/SessionContainer.js | 14 ++------------ 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/src/domain/LoginViewModel.js b/src/domain/LoginViewModel.js index d75a715e..5e0702da 100644 --- a/src/domain/LoginViewModel.js +++ b/src/domain/LoginViewModel.js @@ -72,6 +72,7 @@ export class LoginViewModel extends ViewModel { } async login(username, password, homeserver) { + homeserver = normalizeHomeserver(homeserver); this._loadViewModelSubscription = this.disposeTracked(this._loadViewModelSubscription); if (this._loadViewModel) { this._loadViewModel = this.disposeTracked(this._loadViewModel); diff --git a/src/matrix/SessionContainer.js b/src/matrix/SessionContainer.js index 85aa979c..4511d982 100644 --- a/src/matrix/SessionContainer.js +++ b/src/matrix/SessionContainer.js @@ -43,14 +43,6 @@ export const LoginFailure = createEnum( "Unknown", ); -function normalizeHomeserver(homeServer) { - try { - return new URL(homeServer).origin; - } catch (err) { - return new URL(`https://${homeServer}`).origin; - } -} - export class SessionContainer { constructor({platform, olmPromise, workerPromise}) { this._platform = platform; @@ -113,7 +105,6 @@ export class SessionContainer { } async queryLogin(homeServer) { - homeServer = normalizeHomeserver(homeServer); const hsApi = new HomeServerApi({homeServer, request: this._platform.request}); const response = await hsApi.queryLogin().response(); return this.parseLoginOptions(response, homeServer); @@ -129,15 +120,14 @@ export class SessionContainer { let sessionInfo; try { const request = this._platform.request; - const homeServer = normalizeHomeserver(loginMethod.homeServer); - const hsApi = new HomeServerApi({homeServer, request}); + const hsApi = new HomeServerApi({homeServer: loginMethod.homeServer, request}); const loginData = await loginMethod.login(hsApi, "Hydrogen", log); const sessionId = this.createNewSessionId(); sessionInfo = { id: sessionId, deviceId: loginData.device_id, userId: loginData.user_id, - homeServer: homeServer, + homeServer: loginMethod.homeServer, accessToken: loginData.access_token, lastUsed: clock.now() }; From ce5fdd465c83ebf70eacdc2ad267e7e53b7dc481 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Sun, 1 Aug 2021 20:41:57 +0530 Subject: [PATCH 23/81] Remove unnecessary braces Signed-off-by: RMidhunSuresh --- src/domain/RootViewModel.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/domain/RootViewModel.js b/src/domain/RootViewModel.js index 15b9d19e..643655dc 100644 --- a/src/domain/RootViewModel.js +++ b/src/domain/RootViewModel.js @@ -35,9 +35,7 @@ export class RootViewModel extends ViewModel { async load() { this.track(this.navigation.observe("login").subscribe(() => this._applyNavigation())); this.track(this.navigation.observe("session").subscribe(() => this._applyNavigation())); - this.track(this.navigation.observe("sso").subscribe(() => { - this._applyNavigation(); - })); + this.track(this.navigation.observe("sso").subscribe(() => this._applyNavigation())); this._applyNavigation(true); } From c82af5a0a373c223235c42ae6cf3c10615ed62d5 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 11 Aug 2021 15:30:36 +0530 Subject: [PATCH 24/81] Replace link with button Signed-off-by: RMidhunSuresh --- src/platform/web/ui/login/LoginView.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/web/ui/login/LoginView.js b/src/platform/web/ui/login/LoginView.js index 56dfa96b..6ba9a575 100644 --- a/src/platform/web/ui/login/LoginView.js +++ b/src/platform/web/ui/login/LoginView.js @@ -68,7 +68,7 @@ export class LoginView extends TemplateView { }, vm.i18n`Log In`), ]), ]), - t.if(vm => vm.supportsSSOLogin, () => t.p(t.a({className:"SSO", href:"#"}, "Login with SSO"))), + t.if(vm => vm.supportsSSOLogin, () => t.button({className: "SSO"}, "Login with SSO")), // use t.mapView rather than t.if to create a new view when the view model changes too t.p(hydrogenGithubLink(t)) ]) From cabffd5e3fde7de02a9a47ba4d04566ea4a7a9fd Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 11 Aug 2021 17:28:07 +0530 Subject: [PATCH 25/81] Move view to correct directory Signed-off-by: RMidhunSuresh --- src/platform/web/ui/RootView.js | 2 +- src/{domain => platform/web/ui/login}/CompleteSSOView.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename src/{domain => platform/web/ui/login}/CompleteSSOView.js (91%) diff --git a/src/platform/web/ui/RootView.js b/src/platform/web/ui/RootView.js index 55cdac14..02fd4d7d 100644 --- a/src/platform/web/ui/RootView.js +++ b/src/platform/web/ui/RootView.js @@ -20,7 +20,7 @@ import {SessionLoadView} from "./login/SessionLoadView.js"; import {SessionPickerView} from "./login/SessionPickerView.js"; import {TemplateView} from "./general/TemplateView.js"; import {StaticView} from "./general/StaticView.js"; -import {CompleteSSOView} from "../../../domain/CompleteSSOView.js"; +import {CompleteSSOView} from "./login/CompleteSSOView.js"; export class RootView extends TemplateView { render(t, vm) { diff --git a/src/domain/CompleteSSOView.js b/src/platform/web/ui/login/CompleteSSOView.js similarity index 91% rename from src/domain/CompleteSSOView.js rename to src/platform/web/ui/login/CompleteSSOView.js index 7a7f63f1..2b6e9d02 100644 --- a/src/domain/CompleteSSOView.js +++ b/src/platform/web/ui/login/CompleteSSOView.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {TemplateView} from "../platform/web/ui/general/TemplateView.js"; +import {TemplateView} from "../general/TemplateView.js"; export class CompleteSSOView extends TemplateView { render(t) { From b8f0361157a255b0525714b64f7d0dd3aedaddc2 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Sun, 15 Aug 2021 16:21:00 +0530 Subject: [PATCH 26/81] Split login view into password and sso components Signed-off-by: RMidhunSuresh --- src/domain/LoginViewModel.js | 120 ------------------ src/domain/RootViewModel.js | 17 ++- src/domain/login/LoginViewModel.js | 105 +++++++++++++++ src/domain/login/PasswordLoginViewModel.js | 77 +++++++++++ src/domain/login/SSOLoginViewModel.js | 46 +++++++ src/domain/login/common.js | 23 ++++ src/platform/web/ui/RootView.js | 3 - src/platform/web/ui/login/CompleteSSOView.js | 8 +- src/platform/web/ui/login/LoginView.js | 76 ++++------- .../web/ui/login/PasswordLoginView.js | 71 +++++++++++ 10 files changed, 361 insertions(+), 185 deletions(-) delete mode 100644 src/domain/LoginViewModel.js create mode 100644 src/domain/login/LoginViewModel.js create mode 100644 src/domain/login/PasswordLoginViewModel.js create mode 100644 src/domain/login/SSOLoginViewModel.js create mode 100644 src/domain/login/common.js create mode 100644 src/platform/web/ui/login/PasswordLoginView.js diff --git a/src/domain/LoginViewModel.js b/src/domain/LoginViewModel.js deleted file mode 100644 index 5e0702da..00000000 --- a/src/domain/LoginViewModel.js +++ /dev/null @@ -1,120 +0,0 @@ -/* -Copyright 2020 Bruno Windels - -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. -*/ - -import {ViewModel} from "./ViewModel.js"; -import {SessionLoadViewModel} from "./SessionLoadViewModel.js"; - -function normalizeHomeserver(homeServer) { - try { - return new URL(homeServer).origin; - } catch (err) { - return new URL(`https://${homeServer}`).origin; - } -} - -export class LoginViewModel extends ViewModel { - constructor(options) { - super(options); - const {ready, defaultHomeServer, createSessionContainer} = options; - this._createSessionContainer = createSessionContainer; - this._ready = ready; - this._defaultHomeServer = defaultHomeServer; - this._sessionContainer = null; - this._loadViewModel = null; - this._loadViewModelSubscription = null; - this._supportsSSOLogin = false; - this.queryLogin(); - } - - get defaultHomeServer() { return this._defaultHomeServer; } - - get loadViewModel() {return this._loadViewModel; } - - get isBusy() { - if (!this._loadViewModel) { - return false; - } else { - return this._loadViewModel.loading; - } - } - - async queryLogin(homeServer = this.defaultHomeServer) { - // See if we support SSO, if so shows SSO link - /* For this, we'd need to poll queryLogin before we do login() - */ - if (!this._sessionContainer) { - this._sessionContainer = this._createSessionContainer(); - } - const normalizedHS = normalizeHomeserver(homeServer); - try { - this.loginOptions = await this._sessionContainer.queryLogin(normalizedHS); - this._supportsSSOLogin = !!this.loginOptions.sso; - } - catch (e) { - // Something went wrong, assume SSO is not supported - this._supportsSSOLogin = false; - console.error("Could not query login methods supported by the homeserver"); - } - this.emitChange("supportsSSOLogin"); - } - - async login(username, password, homeserver) { - homeserver = normalizeHomeserver(homeserver); - this._loadViewModelSubscription = this.disposeTracked(this._loadViewModelSubscription); - if (this._loadViewModel) { - this._loadViewModel = this.disposeTracked(this._loadViewModel); - } - this._loadViewModel = this.track(new SessionLoadViewModel(this.childOptions({ - createAndStartSessionContainer: async () => { - if (this.loginOptions.password) { - this._sessionContainer.startWithLogin(this.loginOptions.password(username, password)); - } - return this._sessionContainer; - }, - ready: sessionContainer => { - // make sure we don't delete the session in dispose when navigating away - this._sessionContainer = null; - this._ready(sessionContainer); - }, - homeserver, - }))); - this._loadViewModel.start(); - this.emitChange("loadViewModel"); - this._loadViewModelSubscription = this.track(this._loadViewModel.disposableOn("change", () => { - if (!this._loadViewModel.loading) { - this._loadViewModelSubscription = this.disposeTracked(this._loadViewModelSubscription); - } - this.emitChange("isBusy"); - })); - } - - get cancelUrl() { - return this.urlCreator.urlForSegment("session"); - } - - get supportsSSOLogin() { - return this._supportsSSOLogin; - } - - dispose() { - super.dispose(); - if (this._sessionContainer) { - // if we move away before we're done with initial sync - // delete the session - this._sessionContainer.deleteSession(); - } - } -} diff --git a/src/domain/RootViewModel.js b/src/domain/RootViewModel.js index 643655dc..c47762c5 100644 --- a/src/domain/RootViewModel.js +++ b/src/domain/RootViewModel.js @@ -16,7 +16,7 @@ limitations under the License. import {SessionViewModel} from "./session/SessionViewModel.js"; import {SessionLoadViewModel} from "./SessionLoadViewModel.js"; -import {LoginViewModel} from "./LoginViewModel.js"; +import {LoginViewModel} from "./login/LoginViewModel.js"; import {SessionPickerViewModel} from "./SessionPickerViewModel.js"; import {ViewModel} from "./ViewModel.js"; @@ -42,7 +42,8 @@ export class RootViewModel extends ViewModel { async _applyNavigation(shouldRestoreLastUrl) { const isLogin = this.navigation.observe("login").get(); const sessionId = this.navigation.observe("session").get(); - const SSOSegment = this.navigation.path.get("sso"); + // TODO: why not observe? + const ssoSegment = this.navigation.path.get("sso"); if (isLogin) { if (this.activeSection !== "login") { this._showLogin(); @@ -67,8 +68,11 @@ export class RootViewModel extends ViewModel { this._showSessionLoader(sessionId); } } - } else if (SSOSegment) { - this._setSection(() => this.showCompletionView = true); + } else if (ssoSegment) { + this.urlCreator.normalizeUrl(); + if (this.activeSection !== "login") { + this._showLogin({loginToken: ssoSegment.value}); + } } else { try { @@ -99,7 +103,7 @@ export class RootViewModel extends ViewModel { } } - _showLogin() { + _showLogin(options) { this._setSection(() => { this._loginViewModel = new LoginViewModel(this.childOptions({ defaultHomeServer: this.platform.config["defaultHomeServer"], @@ -116,6 +120,7 @@ export class RootViewModel extends ViewModel { this._pendingSessionContainer = sessionContainer; this.navigation.push("session", sessionContainer.sessionId); }, + ...options })); }); } @@ -152,8 +157,6 @@ export class RootViewModel extends ViewModel { return "picker"; } else if (this._sessionLoadViewModel) { return "loading"; - } else if (this.showCompletionView) { - return "sso"; } else { return "redirecting"; } diff --git a/src/domain/login/LoginViewModel.js b/src/domain/login/LoginViewModel.js new file mode 100644 index 00000000..3d747024 --- /dev/null +++ b/src/domain/login/LoginViewModel.js @@ -0,0 +1,105 @@ +/* +Copyright 2020 Bruno Windels + +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. +*/ + +import {ViewModel} from "../ViewModel.js"; +import {PasswordLoginViewModel} from "./PasswordLoginViewModel.js"; +import {SSOLoginViewModel} from "./SSOLoginViewModel.js"; +import {normalizeHomeserver} from "./common.js"; + +export class LoginViewModel extends ViewModel { + constructor(options) { + super(options); + const {ready, defaultHomeServer, createSessionContainer, loginToken} = options; + this._createSessionContainer = createSessionContainer; + this._ready = ready; + this._defaultHomeServer = defaultHomeServer; + this._loginToken = loginToken; + this._sessionContainer = this._createSessionContainer(); + this._loginOptions = null; + this._start(); + } + + get passwordLoginViewModel() { return this._passwordLoginViewModel; } + get ssoLoginViewModel() { return this._ssoLoginViewModel; } + get loadViewModel() {return this._loadViewModel; } + + async _start() { + if (this._loginToken) { + this._ssoLoginViewModel = this.track(new SSOLoginViewModel(this.childOptions({loginToken: this._loginToken}))); + this.emitChange("ssoLoginViewModel"); + } + else { + const defaultHomeServer = normalizeHomeserver(this._defaultHomeServer); + await this.queryLogin(defaultHomeServer); + this._showPasswordLogin(); + this._showSSOLogin(defaultHomeServer); + } + } + + _showPasswordLogin() { + this._passwordLoginViewModel = new PasswordLoginViewModel(this.childOptions({defaultHomeServer: this._defaultHomeServer})); + const observable = this._passwordLoginViewModel.homeserverObservable; + this.track(observable.subscribe(newHomeServer => this._onHomeServerChange(newHomeServer))); + this.emitChange("passwordLoginViewModel"); + } + + _showSSOLogin(homeserver) { + this._ssoLoginViewModel = this.disposeTracked(this._ssoLoginViewModel); + this.emitChange("ssoLoginViewModel"); + if (this._loginOptions?.sso && !this._loginToken) { + this._ssoLoginViewModel = this.track(new SSOLoginViewModel(this.childOptions({homeserver}))); + this.emitChange("ssoLoginViewModel"); + } + } + + async queryLogin(homeserver) { + try { + this._loginOptions = await this._sessionContainer.queryLogin(homeserver); + } + catch (e) { + this._loginOptions = null; + console.error("Could not query login methods supported by the homeserver"); + } + } + + async _onHomeServerChange(homeserver) { + const normalizedHS = normalizeHomeserver(homeserver); + await this.queryLogin(normalizedHS); + this._showSSOLogin(normalizedHS); + } + + childOptions(options) { + return { + ...super.childOptions(options), + ready: sessionContainer => { + // make sure we don't delete the session in dispose when navigating away + this._sessionContainer = null; + this._ready(sessionContainer); + }, + sessionContainer: this._sessionContainer, + loginOptions: this._loginOptions + } + } + + dispose() { + super.dispose(); + if (this._sessionContainer) { + // if we move away before we're done with initial sync + // delete the session + this._sessionContainer.deleteSession(); + } + } +} diff --git a/src/domain/login/PasswordLoginViewModel.js b/src/domain/login/PasswordLoginViewModel.js new file mode 100644 index 00000000..2b4700d3 --- /dev/null +++ b/src/domain/login/PasswordLoginViewModel.js @@ -0,0 +1,77 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +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. +*/ + +import {ViewModel} from "../ViewModel.js"; +import {SessionLoadViewModel} from "../SessionLoadViewModel.js"; +import {ObservableValue} from "../../observable/ObservableValue.js"; +import {normalizeHomeserver} from "./common.js"; + +export class PasswordLoginViewModel extends ViewModel { + constructor(options) { + super(options); + const {ready, defaultHomeServer, loginOptions, sessionContainer} = options; + this._ready = ready; + this._defaultHomeServer = defaultHomeServer; + this._sessionContainer = sessionContainer; + this._loadViewModel = null; + this._loadViewModelSubscription = null; + this._loginOptions = loginOptions; + this._homeserverObservable = new ObservableValue(this._defaultHomeServer); + } + + get defaultHomeServer() { return this._defaultHomeServer; } + get loadViewModel() {return this._loadViewModel; } + get homeserverObservable() { return this._homeserverObservable; } + get cancelUrl() { return this.urlCreator.urlForSegment("session"); } + + updateHomeServer(homeserver) { + this._homeserverObservable.set(homeserver); + } + + get isBusy() { + if (!this._loadViewModel) { + return false; + } else { + return this._loadViewModel.loading; + } + } + + async login(username, password, homeserver) { + homeserver = normalizeHomeserver(homeserver); + this._loadViewModelSubscription = this.disposeTracked(this._loadViewModelSubscription); + if (this._loadViewModel) { + this._loadViewModel = this.disposeTracked(this._loadViewModel); + } + this._loadViewModel = this.track(new SessionLoadViewModel(this.childOptions({ + createAndStartSessionContainer: async () => { + if (this._loginOptions.password) { + this._sessionContainer.startWithLogin(this._loginOptions.password(username, password)); + } + return this._sessionContainer; + }, + ready: this._ready, + homeserver, + }))); + this._loadViewModel.start(); + this.emitChange("loadViewModel"); + this._loadViewModelSubscription = this.track(this._loadViewModel.disposableOn("change", () => { + if (!this._loadViewModel.loading) { + this._loadViewModelSubscription = this.disposeTracked(this._loadViewModelSubscription); + } + this.emitChange("isBusy"); + })); + } +} diff --git a/src/domain/login/SSOLoginViewModel.js b/src/domain/login/SSOLoginViewModel.js new file mode 100644 index 00000000..10691a37 --- /dev/null +++ b/src/domain/login/SSOLoginViewModel.js @@ -0,0 +1,46 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +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. +*/ + +import {ViewModel} from "../ViewModel.js"; + +export class SSOLoginViewModel extends ViewModel{ + constructor(options) { + super(options); + const { + loginToken, + sessionContainer, + loginOptions, + ready, + homeserver + } = options; + this._loginToken = loginToken; + this._ready = ready; + this._sessionContainer = sessionContainer; + this._homeserver = homeserver; + this._loadViewModelSubscription = null; + this._loadViewModel = null; + this._loginOptions = loginOptions; + } + + get loadViewModel() { return this._loadViewModel; } + get supportsSSOLogin() { return this._supportsSSOLogin; } + get isSSOCompletion() { return !!this._loginToken; } + + + async startSSOLogin() { + console.log("Next PR"); + } +} diff --git a/src/domain/login/common.js b/src/domain/login/common.js new file mode 100644 index 00000000..8bc162e9 --- /dev/null +++ b/src/domain/login/common.js @@ -0,0 +1,23 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +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. +*/ + +export function normalizeHomeserver(homeServer) { + try { + return new URL(homeServer).origin; + } catch (err) { + return new URL(`https://${homeServer}`).origin; + } +} diff --git a/src/platform/web/ui/RootView.js b/src/platform/web/ui/RootView.js index 02fd4d7d..f60bb984 100644 --- a/src/platform/web/ui/RootView.js +++ b/src/platform/web/ui/RootView.js @@ -20,7 +20,6 @@ import {SessionLoadView} from "./login/SessionLoadView.js"; import {SessionPickerView} from "./login/SessionPickerView.js"; import {TemplateView} from "./general/TemplateView.js"; import {StaticView} from "./general/StaticView.js"; -import {CompleteSSOView} from "./login/CompleteSSOView.js"; export class RootView extends TemplateView { render(t, vm) { @@ -43,8 +42,6 @@ export class RootView extends TemplateView { return new StaticView(t => t.p("Redirecting...")); case "loading": return new SessionLoadView(vm.sessionLoadViewModel); - case "sso": - return new CompleteSSOView(); default: throw new Error(`Unknown section: ${vm.activeSection}`); } diff --git a/src/platform/web/ui/login/CompleteSSOView.js b/src/platform/web/ui/login/CompleteSSOView.js index 2b6e9d02..a31072bb 100644 --- a/src/platform/web/ui/login/CompleteSSOView.js +++ b/src/platform/web/ui/login/CompleteSSOView.js @@ -15,9 +15,15 @@ limitations under the License. */ import {TemplateView} from "../general/TemplateView.js"; +import {SessionLoadStatusView} from "./SessionLoadStatusView.js"; export class CompleteSSOView extends TemplateView { render(t) { - return t.div({ className: "CompleteSSOView" }, "Finishing up SSO Login ..."); + return t.div({ className: "CompleteSSOView" }, + [ + "Finishing up SSO Login ...", + t.mapView(vm => vm.loadViewModel, loadViewModel => loadViewModel ? new SessionLoadStatusView(loadViewModel) : null) + ] + ); } } diff --git a/src/platform/web/ui/login/LoginView.js b/src/platform/web/ui/login/LoginView.js index 6ba9a575..3e7a4fa6 100644 --- a/src/platform/web/ui/login/LoginView.js +++ b/src/platform/web/ui/login/LoginView.js @@ -16,63 +16,31 @@ limitations under the License. import {TemplateView} from "../general/TemplateView.js"; import {hydrogenGithubLink} from "./common.js"; -import {SessionLoadStatusView} from "./SessionLoadStatusView.js"; +import {PasswordLoginView} from "./PasswordLoginView.js"; +import {CompleteSSOView} from "./CompleteSSOView.js"; export class LoginView extends TemplateView { - render(t, vm) { - const disabled = vm => !!vm.isBusy; - const username = t.input({ - id: "username", - type: "text", - placeholder: vm.i18n`Username`, - disabled - }); - const password = t.input({ - id: "password", - type: "password", - placeholder: vm.i18n`Password`, - disabled - }); - const homeserver = t.input({ - id: "homeserver", - type: "text", - placeholder: vm.i18n`Your matrix homeserver`, - value: vm.defaultHomeServer, - onChange: () => vm.queryLogin(homeserver.value), - disabled - }); - - return t.div({className: "PreSessionScreen"}, [ - t.div({className: "logo"}), - t.div({className: "LoginView form"}, [ - t.h1([vm.i18n`Sign In`]), - t.if(vm => vm.error, t => t.div({className: "error"}, vm => vm.error)), - t.form({ - onSubmit: evnt => { - evnt.preventDefault(); - vm.login(username.value, password.value, homeserver.value); - } - }, [ - t.div({className: "form-row"}, [t.label({for: "username"}, vm.i18n`Username`), username]), - t.div({className: "form-row"}, [t.label({for: "password"}, vm.i18n`Password`), password]), - t.div({className: "form-row"}, [t.label({for: "homeserver"}, vm.i18n`Homeserver`), homeserver]), - t.mapView(vm => vm.loadViewModel, loadViewModel => loadViewModel ? new SessionLoadStatusView(loadViewModel) : null), - t.div({className: "button-row"}, [ - t.a({ - className: "button-action secondary", - href: vm.cancelUrl - }, [vm.i18n`Go Back`]), - t.button({ - className: "button-action primary", - type: "submit" - }, vm.i18n`Log In`), - ]), - ]), - t.if(vm => vm.supportsSSOLogin, () => t.button({className: "SSO"}, "Login with SSO")), - // use t.mapView rather than t.if to create a new view when the view model changes too - t.p(hydrogenGithubLink(t)) - ]) + render(t) { + return t.div({ className: "PreSessionScreen" }, [ + t.div({ className: "logo" }), + t.mapView(vm => vm.passwordLoginViewModel, vm => vm? new PasswordLoginView(vm): null), + t.mapView(vm => vm.ssoLoginViewModel, vm => { + if (vm?.isSSOCompletion) { + return new CompleteSSOView(vm); + } + else if (vm) { + return new SSOLoginView(vm); + } + return null; + } ), + // use t.mapView rather than t.if to create a new view when the view model changes too + t.p(hydrogenGithubLink(t)) ]); } } +class SSOLoginView extends TemplateView { + render(t, vm) { + return t.button({className: "SSO", type: "button", onClick: () => vm.startSSOLogin()}, "Login with SSO"); + } +} diff --git a/src/platform/web/ui/login/PasswordLoginView.js b/src/platform/web/ui/login/PasswordLoginView.js new file mode 100644 index 00000000..7a41f2b5 --- /dev/null +++ b/src/platform/web/ui/login/PasswordLoginView.js @@ -0,0 +1,71 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +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. +*/ + +import {TemplateView} from "../general/TemplateView.js"; +import {SessionLoadStatusView} from "./SessionLoadStatusView.js"; + +export class PasswordLoginView extends TemplateView { + render(t, vm) { + const disabled = vm => !!vm.isBusy; + const username = t.input({ + id: "username", + type: "text", + placeholder: vm.i18n`Username`, + disabled + }); + const password = t.input({ + id: "password", + type: "password", + placeholder: vm.i18n`Password`, + disabled + }); + const homeserver = t.input({ + id: "homeserver", + type: "text", + placeholder: vm.i18n`Your matrix homeserver`, + value: vm.defaultHomeServer, + onChange: () => vm.updateHomeServer(homeserver.value), + disabled + }); + + return t.div({className: "LoginView form"}, [ + t.h1([vm.i18n`Sign In`]), + t.if(vm => vm.error, t => t.div({ className: "error" }, vm => vm.error)), + t.form({ + onSubmit: evnt => { + evnt.preventDefault(); + vm.login(username.value, password.value, homeserver.value); + } + }, [ + t.div({ className: "form-row" }, [t.label({ for: "username" }, vm.i18n`Username`), username]), + t.div({ className: "form-row" }, [t.label({ for: "password" }, vm.i18n`Password`), password]), + t.div({ className: "form-row" }, [t.label({ for: "homeserver" }, vm.i18n`Homeserver`), homeserver]), + t.mapView(vm => vm.loadViewModel, loadViewModel => loadViewModel ? new SessionLoadStatusView(loadViewModel) : null), + t.div({ className: "button-row" }, [ + t.a({ + className: "button-action secondary", + href: vm.cancelUrl + }, [vm.i18n`Go Back`]), + t.button({ + className: "button-action primary", + type: "submit" + }, vm.i18n`Log In`), + ]), + ]) + ]); + } +} + From 93720f602556c6792c2ddc302f3698cc618a7298 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Sun, 15 Aug 2021 17:37:53 +0530 Subject: [PATCH 27/81] Style sso button Signed-off-by: RMidhunSuresh --- src/platform/web/ui/css/login.css | 16 ++++++++++++++++ src/platform/web/ui/css/themes/element/theme.css | 10 ++++++++++ src/platform/web/ui/login/LoginView.js | 7 ++++++- 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/platform/web/ui/css/login.css b/src/platform/web/ui/css/login.css index aefdac42..3ca08a63 100644 --- a/src/platform/web/ui/css/login.css +++ b/src/platform/web/ui/css/login.css @@ -70,3 +70,19 @@ limitations under the License. .SessionLoadStatusView .spinner { --size: 20px; } + +.SSOLoginView { + display: flex; + flex-direction: column; +} + +.SSOLoginView_button { + flex: 1; + margin-top: 10px; +} + +.SSOLoginView_separator { + justify-content: center; + display: flex; + margin: 8px; +} diff --git a/src/platform/web/ui/css/themes/element/theme.css b/src/platform/web/ui/css/themes/element/theme.css index 08a872b8..5c182d09 100644 --- a/src/platform/web/ui/css/themes/element/theme.css +++ b/src/platform/web/ui/css/themes/element/theme.css @@ -223,6 +223,16 @@ a.button-action { padding-top: 16px; } +.SSOLoginView_button { + border: 1px solid #03B381; + border-radius: 8px; +} + +.SSOLoginView_separator { + font-weight: 500; + font-size: 1.5rem; +} + @media screen and (min-width: 600px) { .PreSessionScreen { box-shadow: 0px 6px 32px rgba(0, 0, 0, 0.1); diff --git a/src/platform/web/ui/login/LoginView.js b/src/platform/web/ui/login/LoginView.js index 3e7a4fa6..34187154 100644 --- a/src/platform/web/ui/login/LoginView.js +++ b/src/platform/web/ui/login/LoginView.js @@ -41,6 +41,11 @@ export class LoginView extends TemplateView { class SSOLoginView extends TemplateView { render(t, vm) { - return t.button({className: "SSO", type: "button", onClick: () => vm.startSSOLogin()}, "Login with SSO"); + return t.div({ className: "SSOLoginView" }, + [ + t.p({ className: "SSOLoginView_separator" }, "or"), + t.button({ className: "SSOLoginView_button button-action secondary", type: "button", onClick: () => vm.startSSOLogin() }, "Log in with SSO") + ] + ); } } From 6c6c4c7dfdce14c690e8cb9fe1118a9d3eebd952 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Sun, 15 Aug 2021 18:08:01 +0530 Subject: [PATCH 28/81] Style CompleteSSOLoginView Signed-off-by: RMidhunSuresh --- src/platform/web/ui/css/login.css | 9 +++++++-- src/platform/web/ui/css/themes/element/theme.css | 4 ++++ src/platform/web/ui/login/CompleteSSOView.js | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/platform/web/ui/css/login.css b/src/platform/web/ui/css/login.css index 3ca08a63..9a979024 100644 --- a/src/platform/web/ui/css/login.css +++ b/src/platform/web/ui/css/login.css @@ -36,7 +36,7 @@ limitations under the License. align-items: center; } -.SessionPickerView .session-info > :not(:first-child) { +.SessionPickerView .session-info> :not(:first-child) { margin-left: 8px; } @@ -58,7 +58,7 @@ limitations under the License. display: flex; } -.SessionLoadStatusView > :not(:first-child) { +.SessionLoadStatusView> :not(:first-child) { margin-left: 12px; } @@ -86,3 +86,8 @@ limitations under the License. display: flex; margin: 8px; } + +.CompleteSSOView_title { + display: flex; + justify-content: center; +} diff --git a/src/platform/web/ui/css/themes/element/theme.css b/src/platform/web/ui/css/themes/element/theme.css index 5c182d09..121f5e51 100644 --- a/src/platform/web/ui/css/themes/element/theme.css +++ b/src/platform/web/ui/css/themes/element/theme.css @@ -233,6 +233,10 @@ a.button-action { font-size: 1.5rem; } +.CompleteSSOView_title { + font-weight: 500; +} + @media screen and (min-width: 600px) { .PreSessionScreen { box-shadow: 0px 6px 32px rgba(0, 0, 0, 0.1); diff --git a/src/platform/web/ui/login/CompleteSSOView.js b/src/platform/web/ui/login/CompleteSSOView.js index a31072bb..cece7184 100644 --- a/src/platform/web/ui/login/CompleteSSOView.js +++ b/src/platform/web/ui/login/CompleteSSOView.js @@ -21,7 +21,7 @@ export class CompleteSSOView extends TemplateView { render(t) { return t.div({ className: "CompleteSSOView" }, [ - "Finishing up SSO Login ...", + t.p({ className: "CompleteSSOView_title" }, "Finishing up your SSO Login"), t.mapView(vm => vm.loadViewModel, loadViewModel => loadViewModel ? new SessionLoadStatusView(loadViewModel) : null) ] ); From e424293293692e781ec724b211dcb9ec7dd0cc64 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 11 Aug 2021 17:13:49 +0530 Subject: [PATCH 29/81] Save homeserver before redirecting Signed-off-by: RMidhunSuresh --- src/domain/LoginViewModel.js | 132 +++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 src/domain/LoginViewModel.js diff --git a/src/domain/LoginViewModel.js b/src/domain/LoginViewModel.js new file mode 100644 index 00000000..a33b4c46 --- /dev/null +++ b/src/domain/LoginViewModel.js @@ -0,0 +1,132 @@ +/* +Copyright 2020 Bruno Windels + +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. +*/ + +import {ViewModel} from "./ViewModel.js"; +import {SessionLoadViewModel} from "./SessionLoadViewModel.js"; + +function normalizeHomeserver(homeServer) { + try { + return new URL(homeServer).origin; + } catch (err) { + return new URL(`https://${homeServer}`).origin; + } +} + +export class LoginViewModel extends ViewModel { + constructor(options) { + super(options); + const {ready, defaultHomeServer, createSessionContainer} = options; + this._createSessionContainer = createSessionContainer; + this._ready = ready; + this._defaultHomeServer = defaultHomeServer; + this._sessionContainer = null; + this._loadViewModel = null; + this._loadViewModelSubscription = null; + this._supportsSSOLogin = false; + this.queryLogin(); + } + + get defaultHomeServer() { return this._defaultHomeServer; } + + get loadViewModel() {return this._loadViewModel; } + + get isBusy() { + if (!this._loadViewModel) { + return false; + } else { + return this._loadViewModel.loading; + } + } + + async queryLogin(homeServer = this.defaultHomeServer) { + // See if we support SSO, if so shows SSO link + /* For this, we'd need to poll queryLogin before we do login() + */ + this._supportsSSOLogin = false; + this.emitChange("supportsSSOLogin"); + if (!this._sessionContainer) { + this._sessionContainer = this._createSessionContainer(); + } + const normalizedHS = normalizeHomeserver(homeServer); + try { + this.loginOptions = await this._sessionContainer.queryLogin(normalizedHS); + this._supportsSSOLogin = !!this.loginOptions.sso; + } + catch (e) { + // Something went wrong, assume SSO is not supported + console.error("Could not query login methods supported by the homeserver"); + } + this.emitChange("supportsSSOLogin"); + } + + async login(username, password, homeserver) { + homeserver = normalizeHomeserver(homeserver); + this._loadViewModelSubscription = this.disposeTracked(this._loadViewModelSubscription); + if (this._loadViewModel) { + this._loadViewModel = this.disposeTracked(this._loadViewModel); + } + this._loadViewModel = this.track(new SessionLoadViewModel(this.childOptions({ + createAndStartSessionContainer: async () => { + if (this.loginOptions.password) { + this._sessionContainer.startWithLogin(this.loginOptions.password(username, password)); + } + return this._sessionContainer; + }, + ready: sessionContainer => { + // make sure we don't delete the session in dispose when navigating away + this._sessionContainer = null; + this._ready(sessionContainer); + }, + homeserver, + }))); + this._loadViewModel.start(); + this.emitChange("loadViewModel"); + this._loadViewModelSubscription = this.track(this._loadViewModel.disposableOn("change", () => { + if (!this._loadViewModel.loading) { + this._loadViewModelSubscription = this.disposeTracked(this._loadViewModelSubscription); + } + this.emitChange("isBusy"); + })); + } + + getSSOLink(homeserver) { + const link = `${homeserver}/_matrix/client/r0/login/sso/redirect?redirectUrl=${this.urlCreator.createSSOCallbackURL()}`; + return link; + } + + async startSSOLogin(homeserver) { + const hs = normalizeHomeserver(homeserver); + await this.platform.settingsStorage.setString("homeserver", hs); + this.platform.openUrl(this.getSSOLink(hs)); + } + + get cancelUrl() { + return this.urlCreator.urlForSegment("session"); + } + + get supportsSSOLogin() { + return this._supportsSSOLogin; + } + + dispose() { + super.dispose(); + if (this._sessionContainer) { + // if we move away before we're done with initial sync + // delete the session + this._sessionContainer.deleteSession(); + } + } +} From daeeaa2869bbfc3b3c084c611fb58fd61746db79 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 11 Aug 2021 17:14:17 +0530 Subject: [PATCH 30/81] Set and get strings Signed-off-by: RMidhunSuresh --- src/platform/web/dom/SettingsStorage.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/platform/web/dom/SettingsStorage.js b/src/platform/web/dom/SettingsStorage.js index 4e4c18c7..1590cec5 100644 --- a/src/platform/web/dom/SettingsStorage.js +++ b/src/platform/web/dom/SettingsStorage.js @@ -43,6 +43,14 @@ export class SettingsStorage { return defaultValue; } + async setString(key, value) { + this._set(key, value); + } + + async getString(key) { + return window.localStorage.getItem(`${this._prefix}${key}`); + } + async remove(key) { window.localStorage.removeItem(`${this._prefix}${key}`); } From 66f28b90fce04804cb57769be576b44c43844056 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Sun, 15 Aug 2021 12:23:32 +0530 Subject: [PATCH 31/81] Implement token login Signed-off-by: RMidhunSuresh --- src/matrix/login/TokenLoginMethod.js | 29 ++++++++++++++++++++++++++++ src/matrix/net/HomeServerApi.js | 12 ++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 src/matrix/login/TokenLoginMethod.js diff --git a/src/matrix/login/TokenLoginMethod.js b/src/matrix/login/TokenLoginMethod.js new file mode 100644 index 00000000..e55cedcf --- /dev/null +++ b/src/matrix/login/TokenLoginMethod.js @@ -0,0 +1,29 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +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. +*/ + +import {LoginMethod} from "./LoginMethod.js"; +import {makeTxnId} from "../common.js"; + +export class TokenLoginMethod extends LoginMethod { + constructor(options) { + super(options); + this._loginToken = options.loginToken; + } + + async login(hsApi, deviceName, log) { + return await hsApi.tokenLogin(this._loginToken, makeTxnId(), deviceName, {log}).response(); + } +} diff --git a/src/matrix/net/HomeServerApi.js b/src/matrix/net/HomeServerApi.js index 6dec6bd6..13baf00c 100644 --- a/src/matrix/net/HomeServerApi.js +++ b/src/matrix/net/HomeServerApi.js @@ -150,6 +150,18 @@ export class HomeServerApi { }, options); } + tokenLogin(loginToken, txnId, initialDeviceDisplayName, options = null) { + return this._unauthedRequest("POST", this._url("/login"), null, { + "type": "m.login.token", + "identifier": { + "type": "m.id.user", + }, + "token": loginToken, + "txn_id": txnId, + "initial_device_display_name": initialDeviceDisplayName + }, options); + } + createFilter(userId, filter, options = null) { return this._post(`/user/${encodeURIComponent(userId)}/filter`, null, filter, options); } From 3fa955e594b2672f0eb45ad78a64572cd6d50ac4 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Sun, 15 Aug 2021 12:24:37 +0530 Subject: [PATCH 32/81] Parse token/sso login in loginOptions Signed-off-by: RMidhunSuresh --- src/matrix/SessionContainer.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/matrix/SessionContainer.js b/src/matrix/SessionContainer.js index 4511d982..83696009 100644 --- a/src/matrix/SessionContainer.js +++ b/src/matrix/SessionContainer.js @@ -24,6 +24,7 @@ import {RequestScheduler} from "./net/RequestScheduler.js"; import {Sync, SyncStatus} from "./Sync.js"; import {Session} from "./Session.js"; import {PasswordLoginMethod} from "./login/PasswordLoginMethod.js"; +import {TokenLoginMethod} from "./login/TokenLoginMethod.js"; export const LoadStatus = createEnum( "NotLoading", @@ -91,7 +92,8 @@ export class SessionContainer { } parseLoginOptions(options, homeServer) { - /* Take server response and return new object which has two props password and sso which + /* + Take server response and return new object which has two props password and sso which implements LoginMethod */ const flows = options.flows; @@ -100,6 +102,12 @@ export class SessionContainer { if (flow.type === "m.login.password") { result.password = (username, password) => new PasswordLoginMethod({homeServer, username, password}); } + else if (flow.type === "m.login.sso" && flows.find(flow => flow.type === "m.login.token")) { + result.sso = loginToken => new TokenLoginMethod({homeServer, loginToken}); + } + else if (flow.type === "m.login.token") { + result.token = loginToken => new TokenLoginMethod({homeServer, loginToken}); + } } return result; } From 683d2c21eb7d4971050dc18314cc3e43dd7f659b Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Sun, 15 Aug 2021 12:26:45 +0530 Subject: [PATCH 33/81] Use generic language in session load status Signed-off-by: RMidhunSuresh --- src/domain/SessionLoadViewModel.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/domain/SessionLoadViewModel.js b/src/domain/SessionLoadViewModel.js index 83bf5966..80837677 100644 --- a/src/domain/SessionLoadViewModel.js +++ b/src/domain/SessionLoadViewModel.js @@ -114,15 +114,15 @@ export class SessionLoadViewModel extends ViewModel { case LoadStatus.NotLoading: return `Preparing…`; case LoadStatus.Login: - return `Checking your login and password…`; + return `Checking your credentials…`; case LoadStatus.LoginFailed: switch (sc.loginFailure) { case LoginFailure.LoginFailure: - return `Your username and/or password don't seem to be correct.`; + return `Your credentials don't seem to be correct.`; case LoginFailure.Connection: return `Can't connect to ${this._homeserver}.`; case LoginFailure.Unknown: - return `Something went wrong while checking your login and password.`; + return `Something went wrong while checking your credentials.`; } break; case LoadStatus.SessionSetup: From 474a4bb19a04b1403fc05557c477d5093c587dc9 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Sun, 15 Aug 2021 16:26:52 +0530 Subject: [PATCH 34/81] Remove Login vm Signed-off-by: RMidhunSuresh --- src/domain/LoginViewModel.js | 132 ----------------------------------- 1 file changed, 132 deletions(-) delete mode 100644 src/domain/LoginViewModel.js diff --git a/src/domain/LoginViewModel.js b/src/domain/LoginViewModel.js deleted file mode 100644 index a33b4c46..00000000 --- a/src/domain/LoginViewModel.js +++ /dev/null @@ -1,132 +0,0 @@ -/* -Copyright 2020 Bruno Windels - -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. -*/ - -import {ViewModel} from "./ViewModel.js"; -import {SessionLoadViewModel} from "./SessionLoadViewModel.js"; - -function normalizeHomeserver(homeServer) { - try { - return new URL(homeServer).origin; - } catch (err) { - return new URL(`https://${homeServer}`).origin; - } -} - -export class LoginViewModel extends ViewModel { - constructor(options) { - super(options); - const {ready, defaultHomeServer, createSessionContainer} = options; - this._createSessionContainer = createSessionContainer; - this._ready = ready; - this._defaultHomeServer = defaultHomeServer; - this._sessionContainer = null; - this._loadViewModel = null; - this._loadViewModelSubscription = null; - this._supportsSSOLogin = false; - this.queryLogin(); - } - - get defaultHomeServer() { return this._defaultHomeServer; } - - get loadViewModel() {return this._loadViewModel; } - - get isBusy() { - if (!this._loadViewModel) { - return false; - } else { - return this._loadViewModel.loading; - } - } - - async queryLogin(homeServer = this.defaultHomeServer) { - // See if we support SSO, if so shows SSO link - /* For this, we'd need to poll queryLogin before we do login() - */ - this._supportsSSOLogin = false; - this.emitChange("supportsSSOLogin"); - if (!this._sessionContainer) { - this._sessionContainer = this._createSessionContainer(); - } - const normalizedHS = normalizeHomeserver(homeServer); - try { - this.loginOptions = await this._sessionContainer.queryLogin(normalizedHS); - this._supportsSSOLogin = !!this.loginOptions.sso; - } - catch (e) { - // Something went wrong, assume SSO is not supported - console.error("Could not query login methods supported by the homeserver"); - } - this.emitChange("supportsSSOLogin"); - } - - async login(username, password, homeserver) { - homeserver = normalizeHomeserver(homeserver); - this._loadViewModelSubscription = this.disposeTracked(this._loadViewModelSubscription); - if (this._loadViewModel) { - this._loadViewModel = this.disposeTracked(this._loadViewModel); - } - this._loadViewModel = this.track(new SessionLoadViewModel(this.childOptions({ - createAndStartSessionContainer: async () => { - if (this.loginOptions.password) { - this._sessionContainer.startWithLogin(this.loginOptions.password(username, password)); - } - return this._sessionContainer; - }, - ready: sessionContainer => { - // make sure we don't delete the session in dispose when navigating away - this._sessionContainer = null; - this._ready(sessionContainer); - }, - homeserver, - }))); - this._loadViewModel.start(); - this.emitChange("loadViewModel"); - this._loadViewModelSubscription = this.track(this._loadViewModel.disposableOn("change", () => { - if (!this._loadViewModel.loading) { - this._loadViewModelSubscription = this.disposeTracked(this._loadViewModelSubscription); - } - this.emitChange("isBusy"); - })); - } - - getSSOLink(homeserver) { - const link = `${homeserver}/_matrix/client/r0/login/sso/redirect?redirectUrl=${this.urlCreator.createSSOCallbackURL()}`; - return link; - } - - async startSSOLogin(homeserver) { - const hs = normalizeHomeserver(homeserver); - await this.platform.settingsStorage.setString("homeserver", hs); - this.platform.openUrl(this.getSSOLink(hs)); - } - - get cancelUrl() { - return this.urlCreator.urlForSegment("session"); - } - - get supportsSSOLogin() { - return this._supportsSSOLogin; - } - - dispose() { - super.dispose(); - if (this._sessionContainer) { - // if we move away before we're done with initial sync - // delete the session - this._sessionContainer.deleteSession(); - } - } -} From 98f8f04c74b2eb5b25a53bc70cec96fab1a67391 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Sun, 15 Aug 2021 16:36:03 +0530 Subject: [PATCH 35/81] Implement SSO Signed-off-by: RMidhunSuresh --- src/domain/login/SSOLoginViewModel.js | 35 ++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/domain/login/SSOLoginViewModel.js b/src/domain/login/SSOLoginViewModel.js index 10691a37..d084727c 100644 --- a/src/domain/login/SSOLoginViewModel.js +++ b/src/domain/login/SSOLoginViewModel.js @@ -15,6 +15,7 @@ limitations under the License. */ import {ViewModel} from "../ViewModel.js"; +import {SessionLoadViewModel} from "../SessionLoadViewModel.js"; export class SSOLoginViewModel extends ViewModel{ constructor(options) { @@ -33,14 +34,46 @@ export class SSOLoginViewModel extends ViewModel{ this._loadViewModelSubscription = null; this._loadViewModel = null; this._loginOptions = loginOptions; + this.performSSOLoginCompletion(); } get loadViewModel() { return this._loadViewModel; } get supportsSSOLogin() { return this._supportsSSOLogin; } get isSSOCompletion() { return !!this._loginToken; } + async performSSOLoginCompletion() { + if (!this._loginToken) { + return; + } + const homeserver = await this.platform.settingsStorage.getString("homeserver"); + const loginOptions = await this._sessionContainer.queryLogin(homeserver); + this._loadViewModelSubscription = this.disposeTracked(this._loadViewModelSubscription); + if (this._loadViewModel) { + this._loadViewModel = this.disposeTracked(this._loadViewModel); + } + this._loadViewModel = this.track(new SessionLoadViewModel(this.childOptions({ + createAndStartSessionContainer: async () => { + if (loginOptions.sso) { + this._sessionContainer.startWithLogin(loginOptions.sso(this._loginToken)); + } + return this._sessionContainer; + }, + ready: this._ready, + homeserver, + }))); + this._loadViewModel.start(); + this.emitChange("loadViewModel"); + this._loadViewModelSubscription = this.track(this._loadViewModel.disposableOn("change", () => { + if (!this._loadViewModel.loading) { + this._loadViewModelSubscription = this.disposeTracked(this._loadViewModelSubscription); + } + this.emitChange("isBusy"); + })); + } async startSSOLogin() { - console.log("Next PR"); + await this.platform.settingsStorage.setString("homeserver", this._homeserver); + const link = `${this._homeserver}/_matrix/client/r0/login/sso/redirect?redirectUrl=${this.urlCreator.createSSOCallbackURL()}`; + this.platform.openUrl(link); } } From f8b0ef052f0dcdcc84bcba470be60a15208a4822 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 17 Aug 2021 14:20:48 +0530 Subject: [PATCH 36/81] Give sso homeserver storage key a better name Signed-off-by: RMidhunSuresh --- src/domain/login/SSOLoginViewModel.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/domain/login/SSOLoginViewModel.js b/src/domain/login/SSOLoginViewModel.js index d084727c..8bd9cd84 100644 --- a/src/domain/login/SSOLoginViewModel.js +++ b/src/domain/login/SSOLoginViewModel.js @@ -45,7 +45,7 @@ export class SSOLoginViewModel extends ViewModel{ if (!this._loginToken) { return; } - const homeserver = await this.platform.settingsStorage.getString("homeserver"); + const homeserver = await this.platform.settingsStorage.getString("sso_ongoing_login_homeserver"); const loginOptions = await this._sessionContainer.queryLogin(homeserver); this._loadViewModelSubscription = this.disposeTracked(this._loadViewModelSubscription); if (this._loadViewModel) { @@ -72,7 +72,7 @@ export class SSOLoginViewModel extends ViewModel{ } async startSSOLogin() { - await this.platform.settingsStorage.setString("homeserver", this._homeserver); + await this.platform.settingsStorage.setString("sso_ongoing_login_homeserver", this._homeserver); const link = `${this._homeserver}/_matrix/client/r0/login/sso/redirect?redirectUrl=${this.urlCreator.createSSOCallbackURL()}`; this.platform.openUrl(link); } From 4b72b64a2ea0da38cb51c81ccac367184329fb6c Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 17 Aug 2021 14:59:38 +0530 Subject: [PATCH 37/81] Implement SSOLoginHelper Signed-off-by: RMidhunSuresh --- src/domain/login/SSOLoginViewModel.js | 4 ++-- src/matrix/SessionContainer.js | 3 ++- src/matrix/login/SSOLoginHelper.js | 25 +++++++++++++++++++++++++ 3 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 src/matrix/login/SSOLoginHelper.js diff --git a/src/domain/login/SSOLoginViewModel.js b/src/domain/login/SSOLoginViewModel.js index 8bd9cd84..baa478f1 100644 --- a/src/domain/login/SSOLoginViewModel.js +++ b/src/domain/login/SSOLoginViewModel.js @@ -54,7 +54,7 @@ export class SSOLoginViewModel extends ViewModel{ this._loadViewModel = this.track(new SessionLoadViewModel(this.childOptions({ createAndStartSessionContainer: async () => { if (loginOptions.sso) { - this._sessionContainer.startWithLogin(loginOptions.sso(this._loginToken)); + this._sessionContainer.startWithLogin(loginOptions.token(this._loginToken)); } return this._sessionContainer; }, @@ -73,7 +73,7 @@ export class SSOLoginViewModel extends ViewModel{ async startSSOLogin() { await this.platform.settingsStorage.setString("sso_ongoing_login_homeserver", this._homeserver); - const link = `${this._homeserver}/_matrix/client/r0/login/sso/redirect?redirectUrl=${this.urlCreator.createSSOCallbackURL()}`; + const link = this._loginOptions.sso.ssoEndpointLink(this.urlCreator.createSSOCallbackURL()); this.platform.openUrl(link); } } diff --git a/src/matrix/SessionContainer.js b/src/matrix/SessionContainer.js index 83696009..7492d0c3 100644 --- a/src/matrix/SessionContainer.js +++ b/src/matrix/SessionContainer.js @@ -25,6 +25,7 @@ import {Sync, SyncStatus} from "./Sync.js"; import {Session} from "./Session.js"; import {PasswordLoginMethod} from "./login/PasswordLoginMethod.js"; import {TokenLoginMethod} from "./login/TokenLoginMethod.js"; +import {SSOLoginHelper} from "./login/SSOLoginHelper.js"; export const LoadStatus = createEnum( "NotLoading", @@ -103,7 +104,7 @@ export class SessionContainer { result.password = (username, password) => new PasswordLoginMethod({homeServer, username, password}); } else if (flow.type === "m.login.sso" && flows.find(flow => flow.type === "m.login.token")) { - result.sso = loginToken => new TokenLoginMethod({homeServer, loginToken}); + result.sso = new SSOLoginHelper(homeServer); } else if (flow.type === "m.login.token") { result.token = loginToken => new TokenLoginMethod({homeServer, loginToken}); diff --git a/src/matrix/login/SSOLoginHelper.js b/src/matrix/login/SSOLoginHelper.js new file mode 100644 index 00000000..701d00b4 --- /dev/null +++ b/src/matrix/login/SSOLoginHelper.js @@ -0,0 +1,25 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +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. +*/ + +export class SSOLoginHelper{ + constructor(homeserver) { + this._homeserver = homeserver; + } + + ssoEndpointLink(redirectURL) { + return `${this._homeserver}/_matrix/client/r0/login/sso/redirect?redirectUrl=${redirectURL}`; + } +} From c4e7dc3b5aad7becaf013189f4e59fd47d2e2c8f Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 17 Aug 2021 17:01:03 +0530 Subject: [PATCH 38/81] Split SSOLoginViewModel Signed-off-by: RMidhunSuresh --- ...wModel.js => CompleteSSOLoginViewModel.js} | 14 +------- src/domain/login/LoginViewModel.js | 22 +++++++------ src/domain/login/StartSSOLoginViewModel.js | 32 +++++++++++++++++++ src/platform/web/ui/css/login.css | 6 ++-- .../web/ui/css/themes/element/theme.css | 4 +-- src/platform/web/ui/login/LoginView.js | 19 ++++------- 6 files changed, 57 insertions(+), 40 deletions(-) rename src/domain/login/{SSOLoginViewModel.js => CompleteSSOLoginViewModel.js} (81%) create mode 100644 src/domain/login/StartSSOLoginViewModel.js diff --git a/src/domain/login/SSOLoginViewModel.js b/src/domain/login/CompleteSSOLoginViewModel.js similarity index 81% rename from src/domain/login/SSOLoginViewModel.js rename to src/domain/login/CompleteSSOLoginViewModel.js index baa478f1..f64c5b94 100644 --- a/src/domain/login/SSOLoginViewModel.js +++ b/src/domain/login/CompleteSSOLoginViewModel.js @@ -17,29 +17,23 @@ limitations under the License. import {ViewModel} from "../ViewModel.js"; import {SessionLoadViewModel} from "../SessionLoadViewModel.js"; -export class SSOLoginViewModel extends ViewModel{ +export class CompleteSSOLoginViewModel extends ViewModel { constructor(options) { super(options); const { loginToken, sessionContainer, - loginOptions, ready, - homeserver } = options; this._loginToken = loginToken; this._ready = ready; this._sessionContainer = sessionContainer; - this._homeserver = homeserver; this._loadViewModelSubscription = null; this._loadViewModel = null; - this._loginOptions = loginOptions; this.performSSOLoginCompletion(); } get loadViewModel() { return this._loadViewModel; } - get supportsSSOLogin() { return this._supportsSSOLogin; } - get isSSOCompletion() { return !!this._loginToken; } async performSSOLoginCompletion() { if (!this._loginToken) { @@ -70,10 +64,4 @@ export class SSOLoginViewModel extends ViewModel{ this.emitChange("isBusy"); })); } - - async startSSOLogin() { - await this.platform.settingsStorage.setString("sso_ongoing_login_homeserver", this._homeserver); - const link = this._loginOptions.sso.ssoEndpointLink(this.urlCreator.createSSOCallbackURL()); - this.platform.openUrl(link); - } } diff --git a/src/domain/login/LoginViewModel.js b/src/domain/login/LoginViewModel.js index 3d747024..ba86baf5 100644 --- a/src/domain/login/LoginViewModel.js +++ b/src/domain/login/LoginViewModel.js @@ -16,8 +16,9 @@ limitations under the License. import {ViewModel} from "../ViewModel.js"; import {PasswordLoginViewModel} from "./PasswordLoginViewModel.js"; -import {SSOLoginViewModel} from "./SSOLoginViewModel.js"; +import {StartSSOLoginViewModel} from "./StartSSOLoginViewModel.js"; import {normalizeHomeserver} from "./common.js"; +import {CompleteSSOLoginViewModel} from "./CompleteSSOLoginViewModel.js"; export class LoginViewModel extends ViewModel { constructor(options) { @@ -29,17 +30,20 @@ export class LoginViewModel extends ViewModel { this._loginToken = loginToken; this._sessionContainer = this._createSessionContainer(); this._loginOptions = null; + this._passwordLoginViewModel = null; + this._startSSOLoginViewModel = null; + this._completeSSOLoginViewModel = null; this._start(); } get passwordLoginViewModel() { return this._passwordLoginViewModel; } - get ssoLoginViewModel() { return this._ssoLoginViewModel; } - get loadViewModel() {return this._loadViewModel; } + get startSSOLoginViewModel() { return this._startSSOLoginViewModel; } + get completeSSOLoginViewModel(){ return this._completeSSOLoginViewModel; } async _start() { if (this._loginToken) { - this._ssoLoginViewModel = this.track(new SSOLoginViewModel(this.childOptions({loginToken: this._loginToken}))); - this.emitChange("ssoLoginViewModel"); + this._completeSSOLoginViewModel = this.track(new CompleteSSOLoginViewModel(this.childOptions({loginToken: this._loginToken}))); + this.emitChange("completeSSOLoginViewModel"); } else { const defaultHomeServer = normalizeHomeserver(this._defaultHomeServer); @@ -57,11 +61,11 @@ export class LoginViewModel extends ViewModel { } _showSSOLogin(homeserver) { - this._ssoLoginViewModel = this.disposeTracked(this._ssoLoginViewModel); - this.emitChange("ssoLoginViewModel"); + this._startSSOLoginViewModel = this.disposeTracked(this._ssoLoginViewModel); + this.emitChange("startSSOLoginViewModel"); if (this._loginOptions?.sso && !this._loginToken) { - this._ssoLoginViewModel = this.track(new SSOLoginViewModel(this.childOptions({homeserver}))); - this.emitChange("ssoLoginViewModel"); + this._startSSOLoginViewModel = this.track(new StartSSOLoginViewModel(this.childOptions({homeserver}))); + this.emitChange("startSSOLoginViewModel"); } } diff --git a/src/domain/login/StartSSOLoginViewModel.js b/src/domain/login/StartSSOLoginViewModel.js new file mode 100644 index 00000000..315106ce --- /dev/null +++ b/src/domain/login/StartSSOLoginViewModel.js @@ -0,0 +1,32 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +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. +*/ + +import {ViewModel} from "../ViewModel.js"; + +export class StartSSOLoginViewModel extends ViewModel{ + constructor(options) { + super(options); + const {loginOptions, homeserver} = options; + this._sso = loginOptions.sso; + this._homeserver = homeserver; + } + + async startSSOLogin() { + await this.platform.settingsStorage.setString("sso_ongoing_login_homeserver", this._homeserver); + const link = this._sso.ssoEndpointLink(this.urlCreator.createSSOCallbackURL()); + this.platform.openUrl(link); + } +} diff --git a/src/platform/web/ui/css/login.css b/src/platform/web/ui/css/login.css index 9a979024..b6f474dd 100644 --- a/src/platform/web/ui/css/login.css +++ b/src/platform/web/ui/css/login.css @@ -71,17 +71,17 @@ limitations under the License. --size: 20px; } -.SSOLoginView { +.StartSSOLoginView { display: flex; flex-direction: column; } -.SSOLoginView_button { +.StartSSOLoginView_button { flex: 1; margin-top: 10px; } -.SSOLoginView_separator { +.StartSSOLoginView_separator { justify-content: center; display: flex; margin: 8px; diff --git a/src/platform/web/ui/css/themes/element/theme.css b/src/platform/web/ui/css/themes/element/theme.css index 121f5e51..74a1f5f8 100644 --- a/src/platform/web/ui/css/themes/element/theme.css +++ b/src/platform/web/ui/css/themes/element/theme.css @@ -223,12 +223,12 @@ a.button-action { padding-top: 16px; } -.SSOLoginView_button { +.StartSSOLoginView_button { border: 1px solid #03B381; border-radius: 8px; } -.SSOLoginView_separator { +.StartSSOLoginView_separator { font-weight: 500; font-size: 1.5rem; } diff --git a/src/platform/web/ui/login/LoginView.js b/src/platform/web/ui/login/LoginView.js index 34187154..45c773bd 100644 --- a/src/platform/web/ui/login/LoginView.js +++ b/src/platform/web/ui/login/LoginView.js @@ -23,28 +23,21 @@ export class LoginView extends TemplateView { render(t) { return t.div({ className: "PreSessionScreen" }, [ t.div({ className: "logo" }), + t.mapView(vm => vm.completeSSOLoginViewModel, vm => vm? new CompleteSSOView(vm): null), t.mapView(vm => vm.passwordLoginViewModel, vm => vm? new PasswordLoginView(vm): null), - t.mapView(vm => vm.ssoLoginViewModel, vm => { - if (vm?.isSSOCompletion) { - return new CompleteSSOView(vm); - } - else if (vm) { - return new SSOLoginView(vm); - } - return null; - } ), + t.mapView(vm => vm.startSSOLoginViewModel, vm => vm? new StartSSOLoginView(vm): null), // use t.mapView rather than t.if to create a new view when the view model changes too t.p(hydrogenGithubLink(t)) ]); } } -class SSOLoginView extends TemplateView { +class StartSSOLoginView extends TemplateView { render(t, vm) { - return t.div({ className: "SSOLoginView" }, + return t.div({ className: "StartSSOLoginView" }, [ - t.p({ className: "SSOLoginView_separator" }, "or"), - t.button({ className: "SSOLoginView_button button-action secondary", type: "button", onClick: () => vm.startSSOLogin() }, "Log in with SSO") + t.p({ className: "StartSSOLoginView_separator" }, "or"), + t.button({ className: "StartSSOLoginView_button button-action secondary", type: "button", onClick: () => vm.startSSOLogin() }, "Log in with SSO") ] ); } From 7b9ec5516a004070b5f23dfb0be715cb030f8128 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 17 Aug 2021 17:31:14 +0530 Subject: [PATCH 39/81] Move normalizeHomeserver into session container Signed-off-by: RMidhunSuresh --- src/domain/login/LoginViewModel.js | 11 ++++------- src/domain/login/PasswordLoginViewModel.js | 2 -- src/domain/login/common.js | 23 ---------------------- src/matrix/SessionContainer.js | 13 ++++++++++-- 4 files changed, 15 insertions(+), 34 deletions(-) delete mode 100644 src/domain/login/common.js diff --git a/src/domain/login/LoginViewModel.js b/src/domain/login/LoginViewModel.js index ba86baf5..4f3b3cbd 100644 --- a/src/domain/login/LoginViewModel.js +++ b/src/domain/login/LoginViewModel.js @@ -17,7 +17,6 @@ limitations under the License. import {ViewModel} from "../ViewModel.js"; import {PasswordLoginViewModel} from "./PasswordLoginViewModel.js"; import {StartSSOLoginViewModel} from "./StartSSOLoginViewModel.js"; -import {normalizeHomeserver} from "./common.js"; import {CompleteSSOLoginViewModel} from "./CompleteSSOLoginViewModel.js"; export class LoginViewModel extends ViewModel { @@ -46,10 +45,9 @@ export class LoginViewModel extends ViewModel { this.emitChange("completeSSOLoginViewModel"); } else { - const defaultHomeServer = normalizeHomeserver(this._defaultHomeServer); - await this.queryLogin(defaultHomeServer); + await this.queryLogin(this._defaultHomeServer); this._showPasswordLogin(); - this._showSSOLogin(defaultHomeServer); + this._showSSOLogin(this._defaultHomeServer); } } @@ -80,9 +78,8 @@ export class LoginViewModel extends ViewModel { } async _onHomeServerChange(homeserver) { - const normalizedHS = normalizeHomeserver(homeserver); - await this.queryLogin(normalizedHS); - this._showSSOLogin(normalizedHS); + await this.queryLogin(homeserver); + this._showSSOLogin(homeserver); } childOptions(options) { diff --git a/src/domain/login/PasswordLoginViewModel.js b/src/domain/login/PasswordLoginViewModel.js index 2b4700d3..496d431d 100644 --- a/src/domain/login/PasswordLoginViewModel.js +++ b/src/domain/login/PasswordLoginViewModel.js @@ -17,7 +17,6 @@ limitations under the License. import {ViewModel} from "../ViewModel.js"; import {SessionLoadViewModel} from "../SessionLoadViewModel.js"; import {ObservableValue} from "../../observable/ObservableValue.js"; -import {normalizeHomeserver} from "./common.js"; export class PasswordLoginViewModel extends ViewModel { constructor(options) { @@ -50,7 +49,6 @@ export class PasswordLoginViewModel extends ViewModel { } async login(username, password, homeserver) { - homeserver = normalizeHomeserver(homeserver); this._loadViewModelSubscription = this.disposeTracked(this._loadViewModelSubscription); if (this._loadViewModel) { this._loadViewModel = this.disposeTracked(this._loadViewModel); diff --git a/src/domain/login/common.js b/src/domain/login/common.js deleted file mode 100644 index 8bc162e9..00000000 --- a/src/domain/login/common.js +++ /dev/null @@ -1,23 +0,0 @@ -/* -Copyright 2021 The Matrix.org Foundation C.I.C. - -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. -*/ - -export function normalizeHomeserver(homeServer) { - try { - return new URL(homeServer).origin; - } catch (err) { - return new URL(`https://${homeServer}`).origin; - } -} diff --git a/src/matrix/SessionContainer.js b/src/matrix/SessionContainer.js index 7492d0c3..3aaa782b 100644 --- a/src/matrix/SessionContainer.js +++ b/src/matrix/SessionContainer.js @@ -27,6 +27,14 @@ import {PasswordLoginMethod} from "./login/PasswordLoginMethod.js"; import {TokenLoginMethod} from "./login/TokenLoginMethod.js"; import {SSOLoginHelper} from "./login/SSOLoginHelper.js"; +function normalizeHomeserver(homeServer) { + try { + return new URL(homeServer).origin; + } catch (err) { + return new URL(`https://${homeServer}`).origin; + } +} + export const LoadStatus = createEnum( "NotLoading", "Login", @@ -114,9 +122,10 @@ export class SessionContainer { } async queryLogin(homeServer) { - const hsApi = new HomeServerApi({homeServer, request: this._platform.request}); + const normalizedHS = normalizeHomeserver(homeServer); + const hsApi = new HomeServerApi({homeServer: normalizedHS, request: this._platform.request}); const response = await hsApi.queryLogin().response(); - return this.parseLoginOptions(response, homeServer); + return this.parseLoginOptions(response, normalizedHS); } async startWithLogin(loginMethod) { From db3fd3d1aeccf273eb963b013c45aaa5e73a78cc Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 17 Aug 2021 17:48:06 +0530 Subject: [PATCH 40/81] Fix test Signed-off-by: RMidhunSuresh --- src/domain/navigation/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/domain/navigation/index.js b/src/domain/navigation/index.js index 8aa3cf97..d21bcad4 100644 --- a/src/domain/navigation/index.js +++ b/src/domain/navigation/index.js @@ -234,7 +234,7 @@ export function tests() { assert.equal(urlPath, "/session/1/rooms/a,b,c/1/details"); }, "Parse loginToken query parameter into SSO segment": assert => { - const segments = parseUrlPath("/?loginToken=a1232aSD123"); + const segments = parseUrlPath("?loginToken=a1232aSD123"); assert.equal(segments.length, 1); assert.equal(segments[0].type, "sso"); assert.equal(segments[0].value, "a1232aSD123"); From d2c94b0d3eb68a36db7210bab3fd32e77ca9f4ba Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 17 Aug 2021 17:53:59 +0530 Subject: [PATCH 41/81] Give argument better name Signed-off-by: RMidhunSuresh --- src/domain/RootViewModel.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/domain/RootViewModel.js b/src/domain/RootViewModel.js index c47762c5..24982a71 100644 --- a/src/domain/RootViewModel.js +++ b/src/domain/RootViewModel.js @@ -103,7 +103,7 @@ export class RootViewModel extends ViewModel { } } - _showLogin(options) { + _showLogin(loginToken) { this._setSection(() => { this._loginViewModel = new LoginViewModel(this.childOptions({ defaultHomeServer: this.platform.config["defaultHomeServer"], @@ -120,7 +120,7 @@ export class RootViewModel extends ViewModel { this._pendingSessionContainer = sessionContainer; this.navigation.push("session", sessionContainer.sessionId); }, - ...options + ...loginToken })); }); } From 83f4095d880ffd64cda24ecc07793c2a06fbef13 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 17 Aug 2021 17:59:35 +0530 Subject: [PATCH 42/81] rename queryLogin to getLoginFlows Signed-off-by: RMidhunSuresh --- src/matrix/SessionContainer.js | 2 +- src/matrix/net/HomeServerApi.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/matrix/SessionContainer.js b/src/matrix/SessionContainer.js index 3aaa782b..edbe0f28 100644 --- a/src/matrix/SessionContainer.js +++ b/src/matrix/SessionContainer.js @@ -124,7 +124,7 @@ export class SessionContainer { async queryLogin(homeServer) { const normalizedHS = normalizeHomeserver(homeServer); const hsApi = new HomeServerApi({homeServer: normalizedHS, request: this._platform.request}); - const response = await hsApi.queryLogin().response(); + const response = await hsApi.getLoginFlows().response(); return this.parseLoginOptions(response, normalizedHS); } diff --git a/src/matrix/net/HomeServerApi.js b/src/matrix/net/HomeServerApi.js index 13baf00c..8641e374 100644 --- a/src/matrix/net/HomeServerApi.js +++ b/src/matrix/net/HomeServerApi.js @@ -134,7 +134,7 @@ export class HomeServerApi { return this._get(`/rooms/${encodeURIComponent(roomId)}/state/${encodeURIComponent(eventType)}/${encodeURIComponent(stateKey)}`, {}, null, options); } - queryLogin() { + getLoginFlows() { return this._unauthedRequest("GET", this._url("/login"), null, null, null); } From 13cb8979ace2637d7bf44fc13f21d4181c9de104 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 17 Aug 2021 20:54:24 +0530 Subject: [PATCH 43/81] Check correct login method early Signed-off-by: RMidhunSuresh --- src/domain/login/CompleteSSOLoginViewModel.js | 9 ++++++--- src/domain/login/PasswordLoginViewModel.js | 9 ++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/domain/login/CompleteSSOLoginViewModel.js b/src/domain/login/CompleteSSOLoginViewModel.js index f64c5b94..bf98cefc 100644 --- a/src/domain/login/CompleteSSOLoginViewModel.js +++ b/src/domain/login/CompleteSSOLoginViewModel.js @@ -41,15 +41,18 @@ export class CompleteSSOLoginViewModel extends ViewModel { } const homeserver = await this.platform.settingsStorage.getString("sso_ongoing_login_homeserver"); const loginOptions = await this._sessionContainer.queryLogin(homeserver); + if (!loginOptions.token) { + const path = this.navigation.pathFrom([this.navigation.segment("session")]); + this.navigation.applyPath(path); + return; + } this._loadViewModelSubscription = this.disposeTracked(this._loadViewModelSubscription); if (this._loadViewModel) { this._loadViewModel = this.disposeTracked(this._loadViewModel); } this._loadViewModel = this.track(new SessionLoadViewModel(this.childOptions({ createAndStartSessionContainer: async () => { - if (loginOptions.sso) { - this._sessionContainer.startWithLogin(loginOptions.token(this._loginToken)); - } + this._sessionContainer.startWithLogin(loginOptions.token(this._loginToken)); return this._sessionContainer; }, ready: this._ready, diff --git a/src/domain/login/PasswordLoginViewModel.js b/src/domain/login/PasswordLoginViewModel.js index 496d431d..5feee9b5 100644 --- a/src/domain/login/PasswordLoginViewModel.js +++ b/src/domain/login/PasswordLoginViewModel.js @@ -49,15 +49,18 @@ export class PasswordLoginViewModel extends ViewModel { } async login(username, password, homeserver) { + if (!this._loginOptions.password) { + const path = this.navigation.pathFrom([this.navigation.segment("session")]); + this.navigation.applyPath(path); + return; + } this._loadViewModelSubscription = this.disposeTracked(this._loadViewModelSubscription); if (this._loadViewModel) { this._loadViewModel = this.disposeTracked(this._loadViewModel); } this._loadViewModel = this.track(new SessionLoadViewModel(this.childOptions({ createAndStartSessionContainer: async () => { - if (this._loginOptions.password) { - this._sessionContainer.startWithLogin(this._loginOptions.password(username, password)); - } + this._sessionContainer.startWithLogin(this._loginOptions.password(username, password)); return this._sessionContainer; }, ready: this._ready, From 10a6aca477505bf871737d7e13f64f394415b376 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 18 Aug 2021 13:05:05 +0530 Subject: [PATCH 44/81] Move homeserver input into LoginView Signed-off-by: RMidhunSuresh --- src/domain/login/LoginViewModel.js | 59 ++++++++++++------- src/domain/login/PasswordLoginViewModel.js | 15 ++--- src/platform/web/ui/css/login.css | 14 ++++- .../web/ui/css/themes/element/theme.css | 2 +- src/platform/web/ui/login/LoginView.js | 34 +++++++---- .../web/ui/login/PasswordLoginView.js | 14 +---- 6 files changed, 81 insertions(+), 57 deletions(-) diff --git a/src/domain/login/LoginViewModel.js b/src/domain/login/LoginViewModel.js index 4f3b3cbd..42be2ae1 100644 --- a/src/domain/login/LoginViewModel.js +++ b/src/domain/login/LoginViewModel.js @@ -32,39 +32,51 @@ export class LoginViewModel extends ViewModel { this._passwordLoginViewModel = null; this._startSSOLoginViewModel = null; this._completeSSOLoginViewModel = null; - this._start(); + this._homeserver = null; + this._errorMessage = ""; + this._start(this._defaultHomeServer); } get passwordLoginViewModel() { return this._passwordLoginViewModel; } get startSSOLoginViewModel() { return this._startSSOLoginViewModel; } get completeSSOLoginViewModel(){ return this._completeSSOLoginViewModel; } + get defaultHomeServer() { return this._defaultHomeServer; } + get errorMessage() { return this._errorMessage; } - async _start() { + async _start(homeserver) { if (this._loginToken) { this._completeSSOLoginViewModel = this.track(new CompleteSSOLoginViewModel(this.childOptions({loginToken: this._loginToken}))); this.emitChange("completeSSOLoginViewModel"); } else { - await this.queryLogin(this._defaultHomeServer); - this._showPasswordLogin(); - this._showSSOLogin(this._defaultHomeServer); + this._errorMessage = ""; + await this.queryLogin(homeserver); + if (this._loginOptions) { + if (this._loginOptions.sso) { this._showSSOLogin(); } + if (this._loginOptions.password) { this._showPasswordLogin(); } + if (!this._loginOptions.sso && !this._loginOptions.password) { + this._showError("This homeserver neither supports SSO nor Password based login flows"); + } + } + else { + this._showError("Could not query login methods supported by the homeserver"); + } } } _showPasswordLogin() { - this._passwordLoginViewModel = new PasswordLoginViewModel(this.childOptions({defaultHomeServer: this._defaultHomeServer})); - const observable = this._passwordLoginViewModel.homeserverObservable; - this.track(observable.subscribe(newHomeServer => this._onHomeServerChange(newHomeServer))); + this._passwordLoginViewModel = this.track(new PasswordLoginViewModel(this.childOptions())); this.emitChange("passwordLoginViewModel"); } - _showSSOLogin(homeserver) { - this._startSSOLoginViewModel = this.disposeTracked(this._ssoLoginViewModel); + _showSSOLogin() { + this._startSSOLoginViewModel = this.track(new StartSSOLoginViewModel(this.childOptions())); this.emitChange("startSSOLoginViewModel"); - if (this._loginOptions?.sso && !this._loginToken) { - this._startSSOLoginViewModel = this.track(new StartSSOLoginViewModel(this.childOptions({homeserver}))); - this.emitChange("startSSOLoginViewModel"); - } + } + + _showError(message) { + this._errorMessage = message; + this.emitChange("errorMessage"); } async queryLogin(homeserver) { @@ -73,16 +85,22 @@ export class LoginViewModel extends ViewModel { } catch (e) { this._loginOptions = null; - console.error("Could not query login methods supported by the homeserver"); } } - async _onHomeServerChange(homeserver) { - await this.queryLogin(homeserver); - this._showSSOLogin(homeserver); + _disposeViewModels() { + this._startSSOLoginViewModel = this.disposeTracked(this._ssoLoginViewModel); + this._passwordLoginViewModel = this.disposeTracked(this._passwordLoginViewModel); + this.emitChange("disposeViewModels"); } - childOptions(options) { + updateHomeServer(newHomeserver) { + this._homeserver = newHomeserver; + this._disposeViewModels(); + this._start(newHomeserver); + } + + childOptions(options = {}) { return { ...super.childOptions(options), ready: sessionContainer => { @@ -91,7 +109,8 @@ export class LoginViewModel extends ViewModel { this._ready(sessionContainer); }, sessionContainer: this._sessionContainer, - loginOptions: this._loginOptions + loginOptions: this._loginOptions, + homeserver: this._homeserver ?? this._defaultHomeServer } } diff --git a/src/domain/login/PasswordLoginViewModel.js b/src/domain/login/PasswordLoginViewModel.js index 5feee9b5..32aef2dd 100644 --- a/src/domain/login/PasswordLoginViewModel.js +++ b/src/domain/login/PasswordLoginViewModel.js @@ -16,30 +16,22 @@ limitations under the License. import {ViewModel} from "../ViewModel.js"; import {SessionLoadViewModel} from "../SessionLoadViewModel.js"; -import {ObservableValue} from "../../observable/ObservableValue.js"; export class PasswordLoginViewModel extends ViewModel { constructor(options) { super(options); - const {ready, defaultHomeServer, loginOptions, sessionContainer} = options; + const {ready, loginOptions, sessionContainer, homeserver} = options; this._ready = ready; - this._defaultHomeServer = defaultHomeServer; this._sessionContainer = sessionContainer; this._loadViewModel = null; this._loadViewModelSubscription = null; this._loginOptions = loginOptions; - this._homeserverObservable = new ObservableValue(this._defaultHomeServer); + this._homeserver = homeserver; } - get defaultHomeServer() { return this._defaultHomeServer; } get loadViewModel() {return this._loadViewModel; } - get homeserverObservable() { return this._homeserverObservable; } get cancelUrl() { return this.urlCreator.urlForSegment("session"); } - updateHomeServer(homeserver) { - this._homeserverObservable.set(homeserver); - } - get isBusy() { if (!this._loadViewModel) { return false; @@ -48,7 +40,8 @@ export class PasswordLoginViewModel extends ViewModel { } } - async login(username, password, homeserver) { + async login(username, password) { + const homeserver = this._homeserver; if (!this._loginOptions.password) { const path = this.navigation.pathFrom([this.navigation.segment("session")]); this.navigation.applyPath(path); diff --git a/src/platform/web/ui/css/login.css b/src/platform/web/ui/css/login.css index b6f474dd..2b65f82c 100644 --- a/src/platform/web/ui/css/login.css +++ b/src/platform/web/ui/css/login.css @@ -50,8 +50,8 @@ limitations under the License. margin: 0 20px; } -.LoginView { - padding: 0.4em; +.PasswordLoginView { + padding: 0 0.4em 0.4em; } .SessionLoadStatusView { @@ -81,7 +81,7 @@ limitations under the License. margin-top: 10px; } -.StartSSOLoginView_separator { +.LoginView_separator { justify-content: center; display: flex; margin: 8px; @@ -91,3 +91,11 @@ limitations under the License. display: flex; justify-content: center; } + +.LoginView_sso { + padding: 0.4em 0.4em 0; +} + +.LoginView_error { + padding: 0.4em +} diff --git a/src/platform/web/ui/css/themes/element/theme.css b/src/platform/web/ui/css/themes/element/theme.css index 74a1f5f8..facbefc8 100644 --- a/src/platform/web/ui/css/themes/element/theme.css +++ b/src/platform/web/ui/css/themes/element/theme.css @@ -228,7 +228,7 @@ a.button-action { border-radius: 8px; } -.StartSSOLoginView_separator { +.LoginView_separator { font-weight: 500; font-size: 1.5rem; } diff --git a/src/platform/web/ui/login/LoginView.js b/src/platform/web/ui/login/LoginView.js index 45c773bd..f260314d 100644 --- a/src/platform/web/ui/login/LoginView.js +++ b/src/platform/web/ui/login/LoginView.js @@ -20,12 +20,25 @@ import {PasswordLoginView} from "./PasswordLoginView.js"; import {CompleteSSOView} from "./CompleteSSOView.js"; export class LoginView extends TemplateView { - render(t) { - return t.div({ className: "PreSessionScreen" }, [ - t.div({ className: "logo" }), - t.mapView(vm => vm.completeSSOLoginViewModel, vm => vm? new CompleteSSOView(vm): null), - t.mapView(vm => vm.passwordLoginViewModel, vm => vm? new PasswordLoginView(vm): null), - t.mapView(vm => vm.startSSOLoginViewModel, vm => vm? new StartSSOLoginView(vm): null), + render(t, vm) { + const homeserver = t.input({ + id: "homeserver", + type: "text", + placeholder: vm.i18n`Your matrix homeserver`, + value: vm.defaultHomeServer, + onChange: () => vm.updateHomeServer(homeserver.value), + }); + + return t.div({className: "PreSessionScreen"}, [ + t.div({className: "logo"}), + t.h1([vm.i18n`Sign In`]), + t.mapView(vm => vm.completeSSOLoginViewModel, vm => vm ? new CompleteSSOView(vm) : null), + t.if(vm => !vm.completeSSOLoginViewModel, + (t, vm) => t.div({ className: "LoginView_sso form form-row" }, [t.label({ for: "homeserver" }, vm.i18n`Homeserver`), homeserver])), + t.mapView(vm => vm.passwordLoginViewModel, vm => vm ? new PasswordLoginView(vm): null), + t.if(vm => vm.passwordLoginViewModel && vm.startSSOLoginViewModel, t => t.p({className: "LoginView_separator"}, vm.i18n`or`)), + t.mapView(vm => vm.startSSOLoginViewModel, vm => vm ? new StartSSOLoginView(vm) : null), + t.if(vm => vm.errorMessage, (t, vm) => t.h5({className: "LoginView_error"}, vm.i18n(vm.errorMessage))), // use t.mapView rather than t.if to create a new view when the view model changes too t.p(hydrogenGithubLink(t)) ]); @@ -35,10 +48,11 @@ export class LoginView extends TemplateView { class StartSSOLoginView extends TemplateView { render(t, vm) { return t.div({ className: "StartSSOLoginView" }, - [ - t.p({ className: "StartSSOLoginView_separator" }, "or"), - t.button({ className: "StartSSOLoginView_button button-action secondary", type: "button", onClick: () => vm.startSSOLogin() }, "Log in with SSO") - ] + t.button({ + className: "StartSSOLoginView_button button-action secondary", + type: "button", + onClick: () => vm.startSSOLogin() + }, vm.i18n`Log in with SSO`) ); } } diff --git a/src/platform/web/ui/login/PasswordLoginView.js b/src/platform/web/ui/login/PasswordLoginView.js index 7a41f2b5..b19daa2a 100644 --- a/src/platform/web/ui/login/PasswordLoginView.js +++ b/src/platform/web/ui/login/PasswordLoginView.js @@ -32,27 +32,17 @@ export class PasswordLoginView extends TemplateView { placeholder: vm.i18n`Password`, disabled }); - const homeserver = t.input({ - id: "homeserver", - type: "text", - placeholder: vm.i18n`Your matrix homeserver`, - value: vm.defaultHomeServer, - onChange: () => vm.updateHomeServer(homeserver.value), - disabled - }); - return t.div({className: "LoginView form"}, [ - t.h1([vm.i18n`Sign In`]), + return t.div({className: "PasswordLoginView form"}, [ t.if(vm => vm.error, t => t.div({ className: "error" }, vm => vm.error)), t.form({ onSubmit: evnt => { evnt.preventDefault(); - vm.login(username.value, password.value, homeserver.value); + vm.login(username.value, password.value); } }, [ t.div({ className: "form-row" }, [t.label({ for: "username" }, vm.i18n`Username`), username]), t.div({ className: "form-row" }, [t.label({ for: "password" }, vm.i18n`Password`), password]), - t.div({ className: "form-row" }, [t.label({ for: "homeserver" }, vm.i18n`Homeserver`), homeserver]), t.mapView(vm => vm.loadViewModel, loadViewModel => loadViewModel ? new SessionLoadStatusView(loadViewModel) : null), t.div({ className: "button-row" }, [ t.a({ From 17f1d6b16a6ed217b496a1a228d09c3a84b5f7a4 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 19 Aug 2021 20:13:34 +0530 Subject: [PATCH 45/81] Remove defaultHomeServer prop Signed-off-by: RMidhunSuresh --- src/domain/login/LoginViewModel.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/domain/login/LoginViewModel.js b/src/domain/login/LoginViewModel.js index 42be2ae1..6460fbf7 100644 --- a/src/domain/login/LoginViewModel.js +++ b/src/domain/login/LoginViewModel.js @@ -25,22 +25,21 @@ export class LoginViewModel extends ViewModel { const {ready, defaultHomeServer, createSessionContainer, loginToken} = options; this._createSessionContainer = createSessionContainer; this._ready = ready; - this._defaultHomeServer = defaultHomeServer; this._loginToken = loginToken; this._sessionContainer = this._createSessionContainer(); this._loginOptions = null; this._passwordLoginViewModel = null; this._startSSOLoginViewModel = null; this._completeSSOLoginViewModel = null; - this._homeserver = null; + this._homeserver = defaultHomeServer; this._errorMessage = ""; - this._start(this._defaultHomeServer); + this._start(this._homeserver); } get passwordLoginViewModel() { return this._passwordLoginViewModel; } get startSSOLoginViewModel() { return this._startSSOLoginViewModel; } get completeSSOLoginViewModel(){ return this._completeSSOLoginViewModel; } - get defaultHomeServer() { return this._defaultHomeServer; } + get defaultHomeServer() { return this._homeserver; } get errorMessage() { return this._errorMessage; } async _start(homeserver) { @@ -110,7 +109,7 @@ export class LoginViewModel extends ViewModel { }, sessionContainer: this._sessionContainer, loginOptions: this._loginOptions, - homeserver: this._homeserver ?? this._defaultHomeServer + homeserver: this._homeserver } } From 738603e8909589a15232d32ef0759ed309a0c54b Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 19 Aug 2021 20:25:54 +0530 Subject: [PATCH 46/81] Rename start to createViewModels Signed-off-by: RMidhunSuresh --- src/domain/login/LoginViewModel.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/domain/login/LoginViewModel.js b/src/domain/login/LoginViewModel.js index 6460fbf7..0f4a5915 100644 --- a/src/domain/login/LoginViewModel.js +++ b/src/domain/login/LoginViewModel.js @@ -33,7 +33,7 @@ export class LoginViewModel extends ViewModel { this._completeSSOLoginViewModel = null; this._homeserver = defaultHomeServer; this._errorMessage = ""; - this._start(this._homeserver); + this._createViewModels(this._homeserver); } get passwordLoginViewModel() { return this._passwordLoginViewModel; } @@ -42,7 +42,7 @@ export class LoginViewModel extends ViewModel { get defaultHomeServer() { return this._homeserver; } get errorMessage() { return this._errorMessage; } - async _start(homeserver) { + async _createViewModels(homeserver) { if (this._loginToken) { this._completeSSOLoginViewModel = this.track(new CompleteSSOLoginViewModel(this.childOptions({loginToken: this._loginToken}))); this.emitChange("completeSSOLoginViewModel"); @@ -96,7 +96,7 @@ export class LoginViewModel extends ViewModel { updateHomeServer(newHomeserver) { this._homeserver = newHomeserver; this._disposeViewModels(); - this._start(newHomeserver); + this._createViewModels(newHomeserver); } childOptions(options = {}) { From 068fba3616c4f9937632bc9c95f6b7fa50442785 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 19 Aug 2021 20:30:14 +0530 Subject: [PATCH 47/81] Inline method Signed-off-by: RMidhunSuresh --- src/domain/login/LoginViewModel.js | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/domain/login/LoginViewModel.js b/src/domain/login/LoginViewModel.js index 0f4a5915..76876a9b 100644 --- a/src/domain/login/LoginViewModel.js +++ b/src/domain/login/LoginViewModel.js @@ -49,7 +49,12 @@ export class LoginViewModel extends ViewModel { } else { this._errorMessage = ""; - await this.queryLogin(homeserver); + try { + this._loginOptions = await this._sessionContainer.queryLogin(homeserver); + } + catch (e) { + this._loginOptions = null; + } if (this._loginOptions) { if (this._loginOptions.sso) { this._showSSOLogin(); } if (this._loginOptions.password) { this._showPasswordLogin(); } @@ -78,15 +83,6 @@ export class LoginViewModel extends ViewModel { this.emitChange("errorMessage"); } - async queryLogin(homeserver) { - try { - this._loginOptions = await this._sessionContainer.queryLogin(homeserver); - } - catch (e) { - this._loginOptions = null; - } - } - _disposeViewModels() { this._startSSOLoginViewModel = this.disposeTracked(this._ssoLoginViewModel); this._passwordLoginViewModel = this.disposeTracked(this._passwordLoginViewModel); From daf7af17b10200b5651682ad0a383737c2d9e78f Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 19 Aug 2021 20:35:07 +0530 Subject: [PATCH 48/81] Move logic to vm Signed-off-by: RMidhunSuresh --- src/domain/login/LoginViewModel.js | 1 + src/platform/web/ui/login/LoginView.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/domain/login/LoginViewModel.js b/src/domain/login/LoginViewModel.js index 76876a9b..73547957 100644 --- a/src/domain/login/LoginViewModel.js +++ b/src/domain/login/LoginViewModel.js @@ -41,6 +41,7 @@ export class LoginViewModel extends ViewModel { get completeSSOLoginViewModel(){ return this._completeSSOLoginViewModel; } get defaultHomeServer() { return this._homeserver; } get errorMessage() { return this._errorMessage; } + get showHomeserver() { return !this._completeSSOLoginViewModel; } async _createViewModels(homeserver) { if (this._loginToken) { diff --git a/src/platform/web/ui/login/LoginView.js b/src/platform/web/ui/login/LoginView.js index f260314d..ebcac433 100644 --- a/src/platform/web/ui/login/LoginView.js +++ b/src/platform/web/ui/login/LoginView.js @@ -33,7 +33,7 @@ export class LoginView extends TemplateView { t.div({className: "logo"}), t.h1([vm.i18n`Sign In`]), t.mapView(vm => vm.completeSSOLoginViewModel, vm => vm ? new CompleteSSOView(vm) : null), - t.if(vm => !vm.completeSSOLoginViewModel, + t.if(vm => vm.showHomeserver, (t, vm) => t.div({ className: "LoginView_sso form form-row" }, [t.label({ for: "homeserver" }, vm.i18n`Homeserver`), homeserver])), t.mapView(vm => vm.passwordLoginViewModel, vm => vm ? new PasswordLoginView(vm): null), t.if(vm => vm.passwordLoginViewModel && vm.startSSOLoginViewModel, t => t.p({className: "LoginView_separator"}, vm.i18n`or`)), From 2d842c71743078cd49f2705edab716b8862e8c25 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 19 Aug 2021 20:39:20 +0530 Subject: [PATCH 49/81] rename ssoEndpointLink to createSSORedirectURL Signed-off-by: RMidhunSuresh --- src/domain/login/StartSSOLoginViewModel.js | 2 +- src/matrix/login/SSOLoginHelper.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/domain/login/StartSSOLoginViewModel.js b/src/domain/login/StartSSOLoginViewModel.js index 315106ce..9a54d2c4 100644 --- a/src/domain/login/StartSSOLoginViewModel.js +++ b/src/domain/login/StartSSOLoginViewModel.js @@ -26,7 +26,7 @@ export class StartSSOLoginViewModel extends ViewModel{ async startSSOLogin() { await this.platform.settingsStorage.setString("sso_ongoing_login_homeserver", this._homeserver); - const link = this._sso.ssoEndpointLink(this.urlCreator.createSSOCallbackURL()); + const link = this._sso.createSSORedirectURL(this.urlCreator.createSSOCallbackURL()); this.platform.openUrl(link); } } diff --git a/src/matrix/login/SSOLoginHelper.js b/src/matrix/login/SSOLoginHelper.js index 701d00b4..376e81ba 100644 --- a/src/matrix/login/SSOLoginHelper.js +++ b/src/matrix/login/SSOLoginHelper.js @@ -19,7 +19,7 @@ export class SSOLoginHelper{ this._homeserver = homeserver; } - ssoEndpointLink(redirectURL) { - return `${this._homeserver}/_matrix/client/r0/login/sso/redirect?redirectUrl=${redirectURL}`; + createSSORedirectURL(returnURL) { + return `${this._homeserver}/_matrix/client/r0/login/sso/redirect?redirectUrl=${returnURL}`; } } From ed278e3e5a4e9d038a01720231232ff0d872057c Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 19 Aug 2021 20:41:24 +0530 Subject: [PATCH 50/81] Remove unwanted check Signed-off-by: RMidhunSuresh --- src/domain/login/PasswordLoginViewModel.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/domain/login/PasswordLoginViewModel.js b/src/domain/login/PasswordLoginViewModel.js index 32aef2dd..00707a0a 100644 --- a/src/domain/login/PasswordLoginViewModel.js +++ b/src/domain/login/PasswordLoginViewModel.js @@ -42,11 +42,6 @@ export class PasswordLoginViewModel extends ViewModel { async login(username, password) { const homeserver = this._homeserver; - if (!this._loginOptions.password) { - const path = this.navigation.pathFrom([this.navigation.segment("session")]); - this.navigation.applyPath(path); - return; - } this._loadViewModelSubscription = this.disposeTracked(this._loadViewModelSubscription); if (this._loadViewModel) { this._loadViewModel = this.disposeTracked(this._loadViewModel); From 3af2ae3bddee60b53b07718a68f25be59d1fbbcf Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 19 Aug 2021 20:43:03 +0530 Subject: [PATCH 51/81] make method private Signed-off-by: RMidhunSuresh --- src/matrix/SessionContainer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/matrix/SessionContainer.js b/src/matrix/SessionContainer.js index edbe0f28..40bc1be0 100644 --- a/src/matrix/SessionContainer.js +++ b/src/matrix/SessionContainer.js @@ -100,7 +100,7 @@ export class SessionContainer { }); } - parseLoginOptions(options, homeServer) { + _parseLoginOptions(options, homeServer) { /* Take server response and return new object which has two props password and sso which implements LoginMethod @@ -125,7 +125,7 @@ export class SessionContainer { const normalizedHS = normalizeHomeserver(homeServer); const hsApi = new HomeServerApi({homeServer: normalizedHS, request: this._platform.request}); const response = await hsApi.getLoginFlows().response(); - return this.parseLoginOptions(response, normalizedHS); + return this._parseLoginOptions(response, normalizedHS); } async startWithLogin(loginMethod) { From 5ab405fc30211b525ae39204a30419eb58c1105f Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 19 Aug 2021 21:37:26 +0530 Subject: [PATCH 52/81] Move back-button to login view Signed-off-by: RMidhunSuresh --- src/domain/login/LoginViewModel.js | 1 + src/domain/login/PasswordLoginViewModel.js | 1 - src/platform/web/ui/css/login.css | 3 ++- src/platform/web/ui/css/themes/element/theme.css | 5 +++++ src/platform/web/ui/login/LoginView.js | 1 + src/platform/web/ui/login/PasswordLoginView.js | 4 ---- 6 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/domain/login/LoginViewModel.js b/src/domain/login/LoginViewModel.js index 73547957..d6cd6eed 100644 --- a/src/domain/login/LoginViewModel.js +++ b/src/domain/login/LoginViewModel.js @@ -42,6 +42,7 @@ export class LoginViewModel extends ViewModel { get defaultHomeServer() { return this._homeserver; } get errorMessage() { return this._errorMessage; } get showHomeserver() { return !this._completeSSOLoginViewModel; } + get cancelUrl() { return this.urlCreator.urlForSegment("session"); } async _createViewModels(homeserver) { if (this._loginToken) { diff --git a/src/domain/login/PasswordLoginViewModel.js b/src/domain/login/PasswordLoginViewModel.js index 00707a0a..f53869ec 100644 --- a/src/domain/login/PasswordLoginViewModel.js +++ b/src/domain/login/PasswordLoginViewModel.js @@ -30,7 +30,6 @@ export class PasswordLoginViewModel extends ViewModel { } get loadViewModel() {return this._loadViewModel; } - get cancelUrl() { return this.urlCreator.urlForSegment("session"); } get isBusy() { if (!this._loadViewModel) { diff --git a/src/platform/web/ui/css/login.css b/src/platform/web/ui/css/login.css index 2b65f82c..01b6bc6e 100644 --- a/src/platform/web/ui/css/login.css +++ b/src/platform/web/ui/css/login.css @@ -74,11 +74,12 @@ limitations under the License. .StartSSOLoginView { display: flex; flex-direction: column; + padding: 0 0.4em 0; } .StartSSOLoginView_button { flex: 1; - margin-top: 10px; + margin-top: 12px; } .LoginView_separator { diff --git a/src/platform/web/ui/css/themes/element/theme.css b/src/platform/web/ui/css/themes/element/theme.css index facbefc8..08f1df06 100644 --- a/src/platform/web/ui/css/themes/element/theme.css +++ b/src/platform/web/ui/css/themes/element/theme.css @@ -228,6 +228,11 @@ a.button-action { border-radius: 8px; } +.LoginView_back { + background-image: url("./icons/chevron-left.svg"); + background-color: transparent; +} + .LoginView_separator { font-weight: 500; font-size: 1.5rem; diff --git a/src/platform/web/ui/login/LoginView.js b/src/platform/web/ui/login/LoginView.js index ebcac433..c15d69ba 100644 --- a/src/platform/web/ui/login/LoginView.js +++ b/src/platform/web/ui/login/LoginView.js @@ -30,6 +30,7 @@ export class LoginView extends TemplateView { }); return t.div({className: "PreSessionScreen"}, [ + t.a({className: "button-utility LoginView_back", href: vm.cancelUrl}), t.div({className: "logo"}), t.h1([vm.i18n`Sign In`]), t.mapView(vm => vm.completeSSOLoginViewModel, vm => vm ? new CompleteSSOView(vm) : null), diff --git a/src/platform/web/ui/login/PasswordLoginView.js b/src/platform/web/ui/login/PasswordLoginView.js index b19daa2a..0f330993 100644 --- a/src/platform/web/ui/login/PasswordLoginView.js +++ b/src/platform/web/ui/login/PasswordLoginView.js @@ -45,10 +45,6 @@ export class PasswordLoginView extends TemplateView { t.div({ className: "form-row" }, [t.label({ for: "password" }, vm.i18n`Password`), password]), t.mapView(vm => vm.loadViewModel, loadViewModel => loadViewModel ? new SessionLoadStatusView(loadViewModel) : null), t.div({ className: "button-row" }, [ - t.a({ - className: "button-action secondary", - href: vm.cancelUrl - }, [vm.i18n`Go Back`]), t.button({ className: "button-action primary", type: "submit" From 80ea48e8a19f2633418a2bb338eba6d5c05083fc Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 19 Aug 2021 21:43:44 +0530 Subject: [PATCH 53/81] Move input into t.if Signed-off-by: RMidhunSuresh --- src/platform/web/ui/login/LoginView.js | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/platform/web/ui/login/LoginView.js b/src/platform/web/ui/login/LoginView.js index c15d69ba..bd087dd2 100644 --- a/src/platform/web/ui/login/LoginView.js +++ b/src/platform/web/ui/login/LoginView.js @@ -21,21 +21,23 @@ import {CompleteSSOView} from "./CompleteSSOView.js"; export class LoginView extends TemplateView { render(t, vm) { - const homeserver = t.input({ - id: "homeserver", - type: "text", - placeholder: vm.i18n`Your matrix homeserver`, - value: vm.defaultHomeServer, - onChange: () => vm.updateHomeServer(homeserver.value), - }); - return t.div({className: "PreSessionScreen"}, [ t.a({className: "button-utility LoginView_back", href: vm.cancelUrl}), t.div({className: "logo"}), t.h1([vm.i18n`Sign In`]), t.mapView(vm => vm.completeSSOLoginViewModel, vm => vm ? new CompleteSSOView(vm) : null), - t.if(vm => vm.showHomeserver, - (t, vm) => t.div({ className: "LoginView_sso form form-row" }, [t.label({ for: "homeserver" }, vm.i18n`Homeserver`), homeserver])), + t.if(vm => vm.showHomeserver, (t, vm) => t.div({ className: "LoginView_sso form form-row" }, + [ + t.label({for: "homeserver"}, vm.i18n`Homeserver`), + t.input({ + id: "homeserver", + type: "text", + placeholder: vm.i18n`Your matrix homeserver`, + value: vm.defaultHomeServer, + onChange: event => vm.updateHomeServer(event.target.value), + }) + ] + )), t.mapView(vm => vm.passwordLoginViewModel, vm => vm ? new PasswordLoginView(vm): null), t.if(vm => vm.passwordLoginViewModel && vm.startSSOLoginViewModel, t => t.p({className: "LoginView_separator"}, vm.i18n`or`)), t.mapView(vm => vm.startSSOLoginViewModel, vm => vm ? new StartSSOLoginView(vm) : null), From bdc860eb798fdae49bd08c0e6983e6aa31791bea Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 20 Aug 2021 15:19:42 +0530 Subject: [PATCH 54/81] Refactor to pull loadvm into login vm Signed-off-by: RMidhunSuresh --- src/domain/RootViewModel.js | 8 +-- src/domain/SessionLoadViewModel.js | 21 +------ src/domain/login/CompleteSSOLoginViewModel.js | 45 +++++++------ src/domain/login/LoginViewModel.js | 63 +++++++++++++++++-- src/domain/login/PasswordLoginViewModel.js | 58 ++++++++--------- src/matrix/SessionContainer.js | 10 +++ src/platform/web/ui/login/LoginView.js | 5 +- .../web/ui/login/PasswordLoginView.js | 5 +- 8 files changed, 128 insertions(+), 87 deletions(-) diff --git a/src/domain/RootViewModel.js b/src/domain/RootViewModel.js index 24982a71..4a0a969e 100644 --- a/src/domain/RootViewModel.js +++ b/src/domain/RootViewModel.js @@ -133,13 +133,11 @@ export class RootViewModel extends ViewModel { } _showSessionLoader(sessionId) { + const sessionContainer = this._createSessionContainer(); + sessionContainer.startWithExistingSession(sessionId); this._setSection(() => { this._sessionLoadViewModel = new SessionLoadViewModel(this.childOptions({ - createAndStartSessionContainer: () => { - const sessionContainer = this._createSessionContainer(); - sessionContainer.startWithExistingSession(sessionId); - return sessionContainer; - }, + sessionContainer, ready: sessionContainer => this._showSession(sessionContainer) })); this._sessionLoadViewModel.start(); diff --git a/src/domain/SessionLoadViewModel.js b/src/domain/SessionLoadViewModel.js index 80837677..956ba3a4 100644 --- a/src/domain/SessionLoadViewModel.js +++ b/src/domain/SessionLoadViewModel.js @@ -14,15 +14,15 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {LoadStatus, LoginFailure} from "../matrix/SessionContainer.js"; +import {LoadStatus} from "../matrix/SessionContainer.js"; import {SyncStatus} from "../matrix/Sync.js"; import {ViewModel} from "./ViewModel.js"; export class SessionLoadViewModel extends ViewModel { constructor(options) { super(options); - const {createAndStartSessionContainer, ready, homeserver, deleteSessionOnCancel} = options; - this._createAndStartSessionContainer = createAndStartSessionContainer; + const {sessionContainer, ready, homeserver, deleteSessionOnCancel} = options; + this._sessionContainer = sessionContainer; this._ready = ready; this._homeserver = homeserver; this._deleteSessionOnCancel = deleteSessionOnCancel; @@ -38,7 +38,6 @@ export class SessionLoadViewModel extends ViewModel { try { this._loading = true; this.emitChange("loading"); - this._sessionContainer = await this._createAndStartSessionContainer(); this._waitHandle = this._sessionContainer.loadStatus.waitFor(s => { this.emitChange("loadLabel"); // wait for initial sync, but not catchup sync @@ -111,20 +110,6 @@ export class SessionLoadViewModel extends ViewModel { if (sc) { switch (sc.loadStatus.get()) { - case LoadStatus.NotLoading: - return `Preparing…`; - case LoadStatus.Login: - return `Checking your credentials…`; - case LoadStatus.LoginFailed: - switch (sc.loginFailure) { - case LoginFailure.LoginFailure: - return `Your credentials don't seem to be correct.`; - case LoginFailure.Connection: - return `Can't connect to ${this._homeserver}.`; - case LoginFailure.Unknown: - return `Something went wrong while checking your credentials.`; - } - break; case LoadStatus.SessionSetup: return `Setting up your encryption keys…`; case LoadStatus.Loading: diff --git a/src/domain/login/CompleteSSOLoginViewModel.js b/src/domain/login/CompleteSSOLoginViewModel.js index bf98cefc..90d25fda 100644 --- a/src/domain/login/CompleteSSOLoginViewModel.js +++ b/src/domain/login/CompleteSSOLoginViewModel.js @@ -15,7 +15,7 @@ limitations under the License. */ import {ViewModel} from "../ViewModel.js"; -import {SessionLoadViewModel} from "../SessionLoadViewModel.js"; +import {LoginFailure} from "../../matrix/SessionContainer.js"; export class CompleteSSOLoginViewModel extends ViewModel { constructor(options) { @@ -24,17 +24,17 @@ export class CompleteSSOLoginViewModel extends ViewModel { loginToken, sessionContainer, ready, + attemptLogin, + showError, } = options; this._loginToken = loginToken; this._ready = ready; this._sessionContainer = sessionContainer; - this._loadViewModelSubscription = null; - this._loadViewModel = null; + this._attemptLogin = attemptLogin; + this._showError = showError; this.performSSOLoginCompletion(); } - get loadViewModel() { return this._loadViewModel; } - async performSSOLoginCompletion() { if (!this._loginToken) { return; @@ -46,25 +46,22 @@ export class CompleteSSOLoginViewModel extends ViewModel { this.navigation.applyPath(path); return; } - this._loadViewModelSubscription = this.disposeTracked(this._loadViewModelSubscription); - if (this._loadViewModel) { - this._loadViewModel = this.disposeTracked(this._loadViewModel); + const status = await this._attemptLogin(loginOptions.token(this._loginToken)); + let error = ""; + switch (status) { + case LoginFailure.Credentials: + error = `Your login-token is invalid.`; + break; + case LoginFailure.Connection: + error = `Can't connect to ${this._homeserver}.`; + break; + case LoginFailure.Unknown: + error = `Something went wrong while checking your login-token.`; + break; + } + if (error) { + this._showError(error); + this._sessionContainer.resetStatus(); } - this._loadViewModel = this.track(new SessionLoadViewModel(this.childOptions({ - createAndStartSessionContainer: async () => { - this._sessionContainer.startWithLogin(loginOptions.token(this._loginToken)); - return this._sessionContainer; - }, - ready: this._ready, - homeserver, - }))); - this._loadViewModel.start(); - this.emitChange("loadViewModel"); - this._loadViewModelSubscription = this.track(this._loadViewModel.disposableOn("change", () => { - if (!this._loadViewModel.loading) { - this._loadViewModelSubscription = this.disposeTracked(this._loadViewModelSubscription); - } - this.emitChange("isBusy"); - })); } } diff --git a/src/domain/login/LoginViewModel.js b/src/domain/login/LoginViewModel.js index d6cd6eed..63c4d253 100644 --- a/src/domain/login/LoginViewModel.js +++ b/src/domain/login/LoginViewModel.js @@ -18,6 +18,8 @@ import {ViewModel} from "../ViewModel.js"; import {PasswordLoginViewModel} from "./PasswordLoginViewModel.js"; import {StartSSOLoginViewModel} from "./StartSSOLoginViewModel.js"; import {CompleteSSOLoginViewModel} from "./CompleteSSOLoginViewModel.js"; +import {LoadStatus} from "../../matrix/SessionContainer.js"; +import {SessionLoadViewModel} from "../SessionLoadViewModel.js"; export class LoginViewModel extends ViewModel { constructor(options) { @@ -31,8 +33,12 @@ export class LoginViewModel extends ViewModel { this._passwordLoginViewModel = null; this._startSSOLoginViewModel = null; this._completeSSOLoginViewModel = null; + this._loadViewModel = null; + this._loadViewModelSubscription = null; this._homeserver = defaultHomeServer; this._errorMessage = ""; + this._hideHomeserver = false; + this._isBusy = false; this._createViewModels(this._homeserver); } @@ -41,11 +47,14 @@ export class LoginViewModel extends ViewModel { get completeSSOLoginViewModel(){ return this._completeSSOLoginViewModel; } get defaultHomeServer() { return this._homeserver; } get errorMessage() { return this._errorMessage; } - get showHomeserver() { return !this._completeSSOLoginViewModel; } + get showHomeserver() { return !this._hideHomeserver; } get cancelUrl() { return this.urlCreator.urlForSegment("session"); } + get loadViewModel() {return this._loadViewModel; } + get isBusy() { return this._isBusy; } async _createViewModels(homeserver) { if (this._loginToken) { + this._hideHomeserver = true; this._completeSSOLoginViewModel = this.track(new CompleteSSOLoginViewModel(this.childOptions({loginToken: this._loginToken}))); this.emitChange("completeSSOLoginViewModel"); } @@ -61,11 +70,11 @@ export class LoginViewModel extends ViewModel { if (this._loginOptions.sso) { this._showSSOLogin(); } if (this._loginOptions.password) { this._showPasswordLogin(); } if (!this._loginOptions.sso && !this._loginOptions.password) { - this._showError("This homeserver neither supports SSO nor Password based login flows"); + this.showError("This homeserver neither supports SSO nor Password based login flows"); } } else { - this._showError("Could not query login methods supported by the homeserver"); + this.showError("Could not query login methods supported by the homeserver"); } } } @@ -80,14 +89,56 @@ export class LoginViewModel extends ViewModel { this.emitChange("startSSOLoginViewModel"); } - _showError(message) { + showError(message) { this._errorMessage = message; this.emitChange("errorMessage"); + this._errorMessage = ""; + } + + _toggleBusy(status) { + this._isBusy = status; + this.emitChange("isBusy"); + } + + async attemptLogin(loginMethod) { + this._toggleBusy(true); + this._sessionContainer.startWithLogin(loginMethod); + const loadStatus = this._sessionContainer.loadStatus; + const handle = loadStatus.waitFor(status => status !== LoadStatus.Login); + await handle.promise; + this._toggleBusy(false); + const status = loadStatus.get(); + if (status === LoadStatus.LoginFailed) { + return this._sessionContainer.loginFailure; + } + this._hideHomeserver = true; + this._disposeViewModels(); + this._createLoadViewModel(); + return null; + } + + _createLoadViewModel() { + this._loadViewModelSubscription = this.disposeTracked(this._loadViewModelSubscription); + if (this._loadViewModel) { + this._loadViewModel = this.disposeTracked(this._loadViewModel); + } + this._loadViewModel = this.track(new SessionLoadViewModel(this.childOptions())); + this._loadViewModel.start(); + this.emitChange("loadViewModel"); + this._loadViewModelSubscription = this.track( + this._loadViewModel.disposableOn("change", () => { + if (!this._loadViewModel.loading) { + this._loadViewModelSubscription = this.disposeTracked(this._loadViewModelSubscription); + } + this.emitChange("isBusy"); + }) + ); } _disposeViewModels() { this._startSSOLoginViewModel = this.disposeTracked(this._ssoLoginViewModel); this._passwordLoginViewModel = this.disposeTracked(this._passwordLoginViewModel); + this._completeSSOLoginViewModel = this.disposeTracked(this._completeSSOLoginViewModel); this.emitChange("disposeViewModels"); } @@ -107,7 +158,9 @@ export class LoginViewModel extends ViewModel { }, sessionContainer: this._sessionContainer, loginOptions: this._loginOptions, - homeserver: this._homeserver + homeserver: this._homeserver, + attemptLogin: loginMethod => this.attemptLogin(loginMethod), + showError: message => this.showError(message) } } diff --git a/src/domain/login/PasswordLoginViewModel.js b/src/domain/login/PasswordLoginViewModel.js index f53869ec..99f54826 100644 --- a/src/domain/login/PasswordLoginViewModel.js +++ b/src/domain/login/PasswordLoginViewModel.js @@ -15,51 +15,47 @@ limitations under the License. */ import {ViewModel} from "../ViewModel.js"; -import {SessionLoadViewModel} from "../SessionLoadViewModel.js"; +import {LoginFailure} from "../../matrix/SessionContainer.js"; export class PasswordLoginViewModel extends ViewModel { constructor(options) { super(options); - const {ready, loginOptions, sessionContainer, homeserver} = options; + const {ready, loginOptions, sessionContainer, homeserver, attemptLogin, showError} = options; this._ready = ready; this._sessionContainer = sessionContainer; - this._loadViewModel = null; - this._loadViewModelSubscription = null; this._loginOptions = loginOptions; + this._attemptLogin = attemptLogin; + this._showError = showError; this._homeserver = homeserver; + this._isBusy = false; } - get loadViewModel() {return this._loadViewModel; } + get isBusy() { return this._isBusy; } - get isBusy() { - if (!this._loadViewModel) { - return false; - } else { - return this._loadViewModel.loading; - } + _toggleBusy(state) { + this._isBusy = state; + this.emitChange("isBusy"); } async login(username, password) { - const homeserver = this._homeserver; - this._loadViewModelSubscription = this.disposeTracked(this._loadViewModelSubscription); - if (this._loadViewModel) { - this._loadViewModel = this.disposeTracked(this._loadViewModel); + this._toggleBusy(true); + const status = await this._attemptLogin(this._loginOptions.password(username, password)); + this._toggleBusy(false); + let error = ""; + switch (status) { + case LoginFailure.Credentials: + error = `Your credentials don't seem to be correct.`; + break; + case LoginFailure.Connection: + error = `Can't connect to ${this._homeserver}.`; + break; + case LoginFailure.Unknown: + error = `Something went wrong while checking your credentials.`; + break; + } + if (error) { + this._showError(error); + this._sessionContainer.resetStatus(); } - this._loadViewModel = this.track(new SessionLoadViewModel(this.childOptions({ - createAndStartSessionContainer: async () => { - this._sessionContainer.startWithLogin(this._loginOptions.password(username, password)); - return this._sessionContainer; - }, - ready: this._ready, - homeserver, - }))); - this._loadViewModel.start(); - this.emitChange("loadViewModel"); - this._loadViewModelSubscription = this.track(this._loadViewModel.disposableOn("change", () => { - if (!this._loadViewModel.loading) { - this._loadViewModelSubscription = this.disposeTracked(this._loadViewModelSubscription); - } - this.emitChange("isBusy"); - })); } } diff --git a/src/matrix/SessionContainer.js b/src/matrix/SessionContainer.js index 40bc1be0..c078f95f 100644 --- a/src/matrix/SessionContainer.js +++ b/src/matrix/SessionContainer.js @@ -300,6 +300,10 @@ export class SessionContainer { return this._error; } + get loginFailure() { + return this._loginFailure; + } + /** only set at loadStatus InitialSync, CatchupSync or Ready */ get sync() { return this._sync; @@ -349,4 +353,10 @@ export class SessionContainer { this._sessionId = null; } } + + resetStatus() { + this._status.set(LoadStatus.NotLoading); + this._error = null; + this._loginFailure = null; + } } diff --git a/src/platform/web/ui/login/LoginView.js b/src/platform/web/ui/login/LoginView.js index bd087dd2..ad7f72a4 100644 --- a/src/platform/web/ui/login/LoginView.js +++ b/src/platform/web/ui/login/LoginView.js @@ -18,6 +18,7 @@ import {TemplateView} from "../general/TemplateView.js"; import {hydrogenGithubLink} from "./common.js"; import {PasswordLoginView} from "./PasswordLoginView.js"; import {CompleteSSOView} from "./CompleteSSOView.js"; +import {SessionLoadStatusView} from "./SessionLoadStatusView.js"; export class LoginView extends TemplateView { render(t, vm) { @@ -34,6 +35,7 @@ export class LoginView extends TemplateView { type: "text", placeholder: vm.i18n`Your matrix homeserver`, value: vm.defaultHomeServer, + disabled: vm => vm.isBusy, onChange: event => vm.updateHomeServer(event.target.value), }) ] @@ -41,7 +43,8 @@ export class LoginView extends TemplateView { t.mapView(vm => vm.passwordLoginViewModel, vm => vm ? new PasswordLoginView(vm): null), t.if(vm => vm.passwordLoginViewModel && vm.startSSOLoginViewModel, t => t.p({className: "LoginView_separator"}, vm.i18n`or`)), t.mapView(vm => vm.startSSOLoginViewModel, vm => vm ? new StartSSOLoginView(vm) : null), - t.if(vm => vm.errorMessage, (t, vm) => t.h5({className: "LoginView_error"}, vm.i18n(vm.errorMessage))), + t.if(vm => vm.errorMessage, (t, vm) => t.p({className: "LoginView_error"}, vm.i18n(vm.errorMessage))), + t.mapView(vm => vm.loadViewModel, loadViewModel => loadViewModel ? new SessionLoadStatusView(loadViewModel) : null), // use t.mapView rather than t.if to create a new view when the view model changes too t.p(hydrogenGithubLink(t)) ]); diff --git a/src/platform/web/ui/login/PasswordLoginView.js b/src/platform/web/ui/login/PasswordLoginView.js index 0f330993..ca26d1cc 100644 --- a/src/platform/web/ui/login/PasswordLoginView.js +++ b/src/platform/web/ui/login/PasswordLoginView.js @@ -15,7 +15,6 @@ limitations under the License. */ import {TemplateView} from "../general/TemplateView.js"; -import {SessionLoadStatusView} from "./SessionLoadStatusView.js"; export class PasswordLoginView extends TemplateView { render(t, vm) { @@ -43,11 +42,11 @@ export class PasswordLoginView extends TemplateView { }, [ t.div({ className: "form-row" }, [t.label({ for: "username" }, vm.i18n`Username`), username]), t.div({ className: "form-row" }, [t.label({ for: "password" }, vm.i18n`Password`), password]), - t.mapView(vm => vm.loadViewModel, loadViewModel => loadViewModel ? new SessionLoadStatusView(loadViewModel) : null), t.div({ className: "button-row" }, [ t.button({ className: "button-action primary", - type: "submit" + type: "submit", + disabled }, vm.i18n`Log In`), ]), ]) From dadeb7f3e53a850bd806f3058e7729f0d9501f7e Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 20 Aug 2021 15:42:43 +0530 Subject: [PATCH 55/81] Do not override childOptions Signed-off-by: RMidhunSuresh --- src/domain/login/CompleteSSOLoginViewModel.js | 4 +- src/domain/login/LoginViewModel.js | 54 ++++++++++++------- src/domain/login/PasswordLoginViewModel.js | 3 +- 3 files changed, 36 insertions(+), 25 deletions(-) diff --git a/src/domain/login/CompleteSSOLoginViewModel.js b/src/domain/login/CompleteSSOLoginViewModel.js index 90d25fda..9434f84a 100644 --- a/src/domain/login/CompleteSSOLoginViewModel.js +++ b/src/domain/login/CompleteSSOLoginViewModel.js @@ -23,12 +23,10 @@ export class CompleteSSOLoginViewModel extends ViewModel { const { loginToken, sessionContainer, - ready, attemptLogin, showError, } = options; this._loginToken = loginToken; - this._ready = ready; this._sessionContainer = sessionContainer; this._attemptLogin = attemptLogin; this._showError = showError; @@ -53,7 +51,7 @@ export class CompleteSSOLoginViewModel extends ViewModel { error = `Your login-token is invalid.`; break; case LoginFailure.Connection: - error = `Can't connect to ${this._homeserver}.`; + error = `Can't connect to ${homeserver}.`; break; case LoginFailure.Unknown: error = `Something went wrong while checking your login-token.`; diff --git a/src/domain/login/LoginViewModel.js b/src/domain/login/LoginViewModel.js index 63c4d253..df779ec0 100644 --- a/src/domain/login/LoginViewModel.js +++ b/src/domain/login/LoginViewModel.js @@ -55,7 +55,14 @@ export class LoginViewModel extends ViewModel { async _createViewModels(homeserver) { if (this._loginToken) { this._hideHomeserver = true; - this._completeSSOLoginViewModel = this.track(new CompleteSSOLoginViewModel(this.childOptions({loginToken: this._loginToken}))); + this._completeSSOLoginViewModel = this.track(new CompleteSSOLoginViewModel( + this.childOptions( + { + sessionContainer: this._sessionContainer, + attemptLogin: loginMethod => this.attemptLogin(loginMethod), + showError: message => this.showError(message), + loginToken: this._loginToken + }))); this.emitChange("completeSSOLoginViewModel"); } else { @@ -80,12 +87,23 @@ export class LoginViewModel extends ViewModel { } _showPasswordLogin() { - this._passwordLoginViewModel = this.track(new PasswordLoginViewModel(this.childOptions())); + this._passwordLoginViewModel = this.track(new PasswordLoginViewModel( + this.childOptions({ + sessionContainer: this._sessionContainer, + loginOptions: this._loginOptions, + homeserver: this._homeserver, + attemptLogin: loginMethod => this.attemptLogin(loginMethod), + showError: message => this.showError(message) + }))); this.emitChange("passwordLoginViewModel"); } _showSSOLogin() { - this._startSSOLoginViewModel = this.track(new StartSSOLoginViewModel(this.childOptions())); + this._startSSOLoginViewModel = this.track( + new StartSSOLoginViewModel( + this.childOptions({ loginOptions: this._loginOptions, homeserver: this._homeserver }) + ) + ); this.emitChange("startSSOLoginViewModel"); } @@ -122,7 +140,19 @@ export class LoginViewModel extends ViewModel { if (this._loadViewModel) { this._loadViewModel = this.disposeTracked(this._loadViewModel); } - this._loadViewModel = this.track(new SessionLoadViewModel(this.childOptions())); + this._loadViewModel = this.track( + new SessionLoadViewModel( + this.childOptions({ + ready: (sessionContainer) => { + // make sure we don't delete the session in dispose when navigating away + this._sessionContainer = null; + this._ready(sessionContainer); + }, + sessionContainer: this._sessionContainer, + homeserver: this._homeserver + }) + ) + ); this._loadViewModel.start(); this.emitChange("loadViewModel"); this._loadViewModelSubscription = this.track( @@ -148,22 +178,6 @@ export class LoginViewModel extends ViewModel { this._createViewModels(newHomeserver); } - childOptions(options = {}) { - return { - ...super.childOptions(options), - ready: sessionContainer => { - // make sure we don't delete the session in dispose when navigating away - this._sessionContainer = null; - this._ready(sessionContainer); - }, - sessionContainer: this._sessionContainer, - loginOptions: this._loginOptions, - homeserver: this._homeserver, - attemptLogin: loginMethod => this.attemptLogin(loginMethod), - showError: message => this.showError(message) - } - } - dispose() { super.dispose(); if (this._sessionContainer) { diff --git a/src/domain/login/PasswordLoginViewModel.js b/src/domain/login/PasswordLoginViewModel.js index 99f54826..d08b8312 100644 --- a/src/domain/login/PasswordLoginViewModel.js +++ b/src/domain/login/PasswordLoginViewModel.js @@ -20,8 +20,7 @@ import {LoginFailure} from "../../matrix/SessionContainer.js"; export class PasswordLoginViewModel extends ViewModel { constructor(options) { super(options); - const {ready, loginOptions, sessionContainer, homeserver, attemptLogin, showError} = options; - this._ready = ready; + const {loginOptions, sessionContainer, homeserver, attemptLogin, showError} = options; this._sessionContainer = sessionContainer; this._loginOptions = loginOptions; this._attemptLogin = attemptLogin; From 2468bc3e9fa2d2e1ae5444544e86e996acd0db09 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 20 Aug 2021 15:47:30 +0530 Subject: [PATCH 56/81] Remove homeserver prop Signed-off-by: RMidhunSuresh --- src/domain/login/LoginViewModel.js | 4 +--- src/domain/login/StartSSOLoginViewModel.js | 6 ++---- src/matrix/login/SSOLoginHelper.js | 2 ++ 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/domain/login/LoginViewModel.js b/src/domain/login/LoginViewModel.js index df779ec0..78457be0 100644 --- a/src/domain/login/LoginViewModel.js +++ b/src/domain/login/LoginViewModel.js @@ -100,9 +100,7 @@ export class LoginViewModel extends ViewModel { _showSSOLogin() { this._startSSOLoginViewModel = this.track( - new StartSSOLoginViewModel( - this.childOptions({ loginOptions: this._loginOptions, homeserver: this._homeserver }) - ) + new StartSSOLoginViewModel(this.childOptions({loginOptions: this._loginOptions})) ); this.emitChange("startSSOLoginViewModel"); } diff --git a/src/domain/login/StartSSOLoginViewModel.js b/src/domain/login/StartSSOLoginViewModel.js index 9a54d2c4..41f6dbeb 100644 --- a/src/domain/login/StartSSOLoginViewModel.js +++ b/src/domain/login/StartSSOLoginViewModel.js @@ -19,13 +19,11 @@ import {ViewModel} from "../ViewModel.js"; export class StartSSOLoginViewModel extends ViewModel{ constructor(options) { super(options); - const {loginOptions, homeserver} = options; - this._sso = loginOptions.sso; - this._homeserver = homeserver; + this._sso = options.loginOptions.sso; } async startSSOLogin() { - await this.platform.settingsStorage.setString("sso_ongoing_login_homeserver", this._homeserver); + await this.platform.settingsStorage.setString("sso_ongoing_login_homeserver", this._sso.homeserver); const link = this._sso.createSSORedirectURL(this.urlCreator.createSSOCallbackURL()); this.platform.openUrl(link); } diff --git a/src/matrix/login/SSOLoginHelper.js b/src/matrix/login/SSOLoginHelper.js index 376e81ba..a15c8ef9 100644 --- a/src/matrix/login/SSOLoginHelper.js +++ b/src/matrix/login/SSOLoginHelper.js @@ -19,6 +19,8 @@ export class SSOLoginHelper{ this._homeserver = homeserver; } + get homeserver() { return this._homeserver; } + createSSORedirectURL(returnURL) { return `${this._homeserver}/_matrix/client/r0/login/sso/redirect?redirectUrl=${returnURL}`; } From 9482998b15c0533abb636541a9e0f49ba0811c83 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 20 Aug 2021 19:08:47 +0530 Subject: [PATCH 57/81] Internationalize and remove dash Signed-off-by: RMidhunSuresh --- src/domain/login/CompleteSSOLoginViewModel.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/domain/login/CompleteSSOLoginViewModel.js b/src/domain/login/CompleteSSOLoginViewModel.js index 9434f84a..2ec2f371 100644 --- a/src/domain/login/CompleteSSOLoginViewModel.js +++ b/src/domain/login/CompleteSSOLoginViewModel.js @@ -48,13 +48,13 @@ export class CompleteSSOLoginViewModel extends ViewModel { let error = ""; switch (status) { case LoginFailure.Credentials: - error = `Your login-token is invalid.`; + error = this.i18n`Your logintoken is invalid.`; break; case LoginFailure.Connection: - error = `Can't connect to ${homeserver}.`; + error = this.i18n`Can't connect to ${homeserver}.`; break; case LoginFailure.Unknown: - error = `Something went wrong while checking your login-token.`; + error = this.i18n`Something went wrong while checking your logintoken.`; break; } if (error) { From c650b358319e38d5f9ce6021f6fd44ee13297af7 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 20 Aug 2021 20:05:36 +0530 Subject: [PATCH 58/81] resetStatus from within startLogin Signed-off-by: RMidhunSuresh --- src/domain/login/CompleteSSOLoginViewModel.js | 1 - src/domain/login/LoginViewModel.js | 1 - src/domain/login/PasswordLoginViewModel.js | 4 +--- src/matrix/SessionContainer.js | 6 ++++-- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/domain/login/CompleteSSOLoginViewModel.js b/src/domain/login/CompleteSSOLoginViewModel.js index 2ec2f371..1fed0b41 100644 --- a/src/domain/login/CompleteSSOLoginViewModel.js +++ b/src/domain/login/CompleteSSOLoginViewModel.js @@ -59,7 +59,6 @@ export class CompleteSSOLoginViewModel extends ViewModel { } if (error) { this._showError(error); - this._sessionContainer.resetStatus(); } } } diff --git a/src/domain/login/LoginViewModel.js b/src/domain/login/LoginViewModel.js index 78457be0..9e8ed146 100644 --- a/src/domain/login/LoginViewModel.js +++ b/src/domain/login/LoginViewModel.js @@ -89,7 +89,6 @@ export class LoginViewModel extends ViewModel { _showPasswordLogin() { this._passwordLoginViewModel = this.track(new PasswordLoginViewModel( this.childOptions({ - sessionContainer: this._sessionContainer, loginOptions: this._loginOptions, homeserver: this._homeserver, attemptLogin: loginMethod => this.attemptLogin(loginMethod), diff --git a/src/domain/login/PasswordLoginViewModel.js b/src/domain/login/PasswordLoginViewModel.js index d08b8312..76814806 100644 --- a/src/domain/login/PasswordLoginViewModel.js +++ b/src/domain/login/PasswordLoginViewModel.js @@ -20,8 +20,7 @@ import {LoginFailure} from "../../matrix/SessionContainer.js"; export class PasswordLoginViewModel extends ViewModel { constructor(options) { super(options); - const {loginOptions, sessionContainer, homeserver, attemptLogin, showError} = options; - this._sessionContainer = sessionContainer; + const {loginOptions, homeserver, attemptLogin, showError} = options; this._loginOptions = loginOptions; this._attemptLogin = attemptLogin; this._showError = showError; @@ -54,7 +53,6 @@ export class PasswordLoginViewModel extends ViewModel { } if (error) { this._showError(error); - this._sessionContainer.resetStatus(); } } } diff --git a/src/matrix/SessionContainer.js b/src/matrix/SessionContainer.js index c078f95f..ca8379bc 100644 --- a/src/matrix/SessionContainer.js +++ b/src/matrix/SessionContainer.js @@ -129,9 +129,11 @@ export class SessionContainer { } async startWithLogin(loginMethod) { - if (this._status.get() !== LoadStatus.NotLoading) { + const currentStatus = this._status.get(); + if (currentStatus !== LoadStatus.LoginFailed && currentStatus !== LoadStatus.NotLoading) { return; } + this._resetStatus(); await this._platform.logger.run("login", async log => { this._status.set(LoadStatus.Login); const clock = this._platform.clock; @@ -354,7 +356,7 @@ export class SessionContainer { } } - resetStatus() { + _resetStatus() { this._status.set(LoadStatus.NotLoading); this._error = null; this._loginFailure = null; From 5ca732341a0e2756d0c75e2fb1ad7b9863065df6 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 20 Aug 2021 20:09:12 +0530 Subject: [PATCH 59/81] Rename defaultHomeserver to homeserver Signed-off-by: RMidhunSuresh --- src/domain/login/LoginViewModel.js | 2 +- src/platform/web/ui/login/LoginView.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/domain/login/LoginViewModel.js b/src/domain/login/LoginViewModel.js index 9e8ed146..61fd3967 100644 --- a/src/domain/login/LoginViewModel.js +++ b/src/domain/login/LoginViewModel.js @@ -45,7 +45,7 @@ export class LoginViewModel extends ViewModel { get passwordLoginViewModel() { return this._passwordLoginViewModel; } get startSSOLoginViewModel() { return this._startSSOLoginViewModel; } get completeSSOLoginViewModel(){ return this._completeSSOLoginViewModel; } - get defaultHomeServer() { return this._homeserver; } + get homeserver() { return this._homeserver; } get errorMessage() { return this._errorMessage; } get showHomeserver() { return !this._hideHomeserver; } get cancelUrl() { return this.urlCreator.urlForSegment("session"); } diff --git a/src/platform/web/ui/login/LoginView.js b/src/platform/web/ui/login/LoginView.js index ad7f72a4..1a146051 100644 --- a/src/platform/web/ui/login/LoginView.js +++ b/src/platform/web/ui/login/LoginView.js @@ -34,7 +34,7 @@ export class LoginView extends TemplateView { id: "homeserver", type: "text", placeholder: vm.i18n`Your matrix homeserver`, - value: vm.defaultHomeServer, + value: vm.homeserver, disabled: vm => vm.isBusy, onChange: event => vm.updateHomeServer(event.target.value), }) From 0e7a9e224c0cd3e0925faecbc4487796ae843863 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 20 Aug 2021 20:10:31 +0530 Subject: [PATCH 60/81] Remove unwanted if Signed-off-by: RMidhunSuresh --- src/domain/login/LoginViewModel.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/domain/login/LoginViewModel.js b/src/domain/login/LoginViewModel.js index 61fd3967..e7d7bf49 100644 --- a/src/domain/login/LoginViewModel.js +++ b/src/domain/login/LoginViewModel.js @@ -134,9 +134,7 @@ export class LoginViewModel extends ViewModel { _createLoadViewModel() { this._loadViewModelSubscription = this.disposeTracked(this._loadViewModelSubscription); - if (this._loadViewModel) { - this._loadViewModel = this.disposeTracked(this._loadViewModel); - } + this._loadViewModel = this.disposeTracked(this._loadViewModel); this._loadViewModel = this.track( new SessionLoadViewModel( this.childOptions({ From 784b06d5009e368bdc6def2a3051fb7a275c041d Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 20 Aug 2021 20:12:25 +0530 Subject: [PATCH 61/81] Fix emit for isBusy Signed-off-by: RMidhunSuresh --- src/domain/login/LoginViewModel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/domain/login/LoginViewModel.js b/src/domain/login/LoginViewModel.js index e7d7bf49..26d0ca88 100644 --- a/src/domain/login/LoginViewModel.js +++ b/src/domain/login/LoginViewModel.js @@ -155,7 +155,7 @@ export class LoginViewModel extends ViewModel { if (!this._loadViewModel.loading) { this._loadViewModelSubscription = this.disposeTracked(this._loadViewModelSubscription); } - this.emitChange("isBusy"); + this._toggleBusy(false); }) ); } From ff8417dfe250c5cc0914c1ef1044d14def777ea4 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 20 Aug 2021 20:31:22 +0530 Subject: [PATCH 62/81] Set busy state from login vm Signed-off-by: RMidhunSuresh --- src/domain/login/LoginViewModel.js | 4 ++++ src/domain/login/PasswordLoginViewModel.js | 4 +--- src/domain/login/StartSSOLoginViewModel.js | 8 ++++++++ src/platform/web/ui/login/LoginView.js | 3 ++- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/domain/login/LoginViewModel.js b/src/domain/login/LoginViewModel.js index 26d0ca88..381dc260 100644 --- a/src/domain/login/LoginViewModel.js +++ b/src/domain/login/LoginViewModel.js @@ -117,11 +117,15 @@ export class LoginViewModel extends ViewModel { async attemptLogin(loginMethod) { this._toggleBusy(true); + this._passwordLoginViewModel?.toggleBusy(true); + this._startSSOLoginViewModel?.toggleBusy(true); this._sessionContainer.startWithLogin(loginMethod); const loadStatus = this._sessionContainer.loadStatus; const handle = loadStatus.waitFor(status => status !== LoadStatus.Login); await handle.promise; this._toggleBusy(false); + this._passwordLoginViewModel?.toggleBusy(false); + this._startSSOLoginViewModel?.toggleBusy(false); const status = loadStatus.get(); if (status === LoadStatus.LoginFailed) { return this._sessionContainer.loginFailure; diff --git a/src/domain/login/PasswordLoginViewModel.js b/src/domain/login/PasswordLoginViewModel.js index 76814806..64a48f37 100644 --- a/src/domain/login/PasswordLoginViewModel.js +++ b/src/domain/login/PasswordLoginViewModel.js @@ -30,15 +30,13 @@ export class PasswordLoginViewModel extends ViewModel { get isBusy() { return this._isBusy; } - _toggleBusy(state) { + toggleBusy(state) { this._isBusy = state; this.emitChange("isBusy"); } async login(username, password) { - this._toggleBusy(true); const status = await this._attemptLogin(this._loginOptions.password(username, password)); - this._toggleBusy(false); let error = ""; switch (status) { case LoginFailure.Credentials: diff --git a/src/domain/login/StartSSOLoginViewModel.js b/src/domain/login/StartSSOLoginViewModel.js index 41f6dbeb..e4c0cf92 100644 --- a/src/domain/login/StartSSOLoginViewModel.js +++ b/src/domain/login/StartSSOLoginViewModel.js @@ -20,6 +20,14 @@ export class StartSSOLoginViewModel extends ViewModel{ constructor(options) { super(options); this._sso = options.loginOptions.sso; + this._isBusy = false; + } + + get isBusy() { return this._isBusy; } + + toggleBusy(state) { + this._isBusy = state; + this.emitChange("isBusy"); } async startSSOLogin() { diff --git a/src/platform/web/ui/login/LoginView.js b/src/platform/web/ui/login/LoginView.js index 1a146051..7672a719 100644 --- a/src/platform/web/ui/login/LoginView.js +++ b/src/platform/web/ui/login/LoginView.js @@ -57,7 +57,8 @@ class StartSSOLoginView extends TemplateView { t.button({ className: "StartSSOLoginView_button button-action secondary", type: "button", - onClick: () => vm.startSSOLogin() + onClick: () => vm.startSSOLogin(), + disabled: vm => vm.isBusy }, vm.i18n`Log in with SSO`) ); } From 355468b6372d662f04beeb5e6a1c8cbfbd5ab351 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 20 Aug 2021 20:36:33 +0530 Subject: [PATCH 63/81] Internationalize + add back old message Signed-off-by: RMidhunSuresh --- src/domain/login/PasswordLoginViewModel.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/domain/login/PasswordLoginViewModel.js b/src/domain/login/PasswordLoginViewModel.js index 64a48f37..d23cda4a 100644 --- a/src/domain/login/PasswordLoginViewModel.js +++ b/src/domain/login/PasswordLoginViewModel.js @@ -40,13 +40,13 @@ export class PasswordLoginViewModel extends ViewModel { let error = ""; switch (status) { case LoginFailure.Credentials: - error = `Your credentials don't seem to be correct.`; + error = this.i18n`Your username and/or password don't seem to be correct.`; break; case LoginFailure.Connection: - error = `Can't connect to ${this._homeserver}.`; + error = this.i18n`Can't connect to ${this._homeserver}.`; break; case LoginFailure.Unknown: - error = `Something went wrong while checking your credentials.`; + error = this.i18n`Something went wrong while checking your login and password.`; break; } if (error) { From a2677a64006c52dab0a18efe110d73274a5547a5 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 20 Aug 2021 21:22:12 +0530 Subject: [PATCH 64/81] Separate errors for each vm Signed-off-by: RMidhunSuresh --- src/domain/login/CompleteSSOLoginViewModel.js | 11 +++++++++-- src/domain/login/PasswordLoginViewModel.js | 11 +++++++++-- src/platform/web/ui/login/CompleteSSOView.js | 3 ++- src/platform/web/ui/login/LoginView.js | 2 +- src/platform/web/ui/login/PasswordLoginView.js | 1 + 5 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/domain/login/CompleteSSOLoginViewModel.js b/src/domain/login/CompleteSSOLoginViewModel.js index 1fed0b41..9bdb0509 100644 --- a/src/domain/login/CompleteSSOLoginViewModel.js +++ b/src/domain/login/CompleteSSOLoginViewModel.js @@ -24,15 +24,22 @@ export class CompleteSSOLoginViewModel extends ViewModel { loginToken, sessionContainer, attemptLogin, - showError, } = options; this._loginToken = loginToken; this._sessionContainer = sessionContainer; this._attemptLogin = attemptLogin; - this._showError = showError; + this._errorMessage = ""; this.performSSOLoginCompletion(); } + get errorMessage() { return this._errorMessage; } + + _showError(message) { + this._errorMessage = message; + this.emitChange("errorMessage"); + this._errorMessage = ""; + } + async performSSOLoginCompletion() { if (!this._loginToken) { return; diff --git a/src/domain/login/PasswordLoginViewModel.js b/src/domain/login/PasswordLoginViewModel.js index d23cda4a..3524779e 100644 --- a/src/domain/login/PasswordLoginViewModel.js +++ b/src/domain/login/PasswordLoginViewModel.js @@ -20,21 +20,28 @@ import {LoginFailure} from "../../matrix/SessionContainer.js"; export class PasswordLoginViewModel extends ViewModel { constructor(options) { super(options); - const {loginOptions, homeserver, attemptLogin, showError} = options; + const {loginOptions, homeserver, attemptLogin} = options; this._loginOptions = loginOptions; this._attemptLogin = attemptLogin; - this._showError = showError; this._homeserver = homeserver; this._isBusy = false; + this._errorMessage = ""; } get isBusy() { return this._isBusy; } + get errorMessage() { return this._errorMessage; } toggleBusy(state) { this._isBusy = state; this.emitChange("isBusy"); } + _showError(message) { + this._errorMessage = message; + this.emitChange("errorMessage"); + this._errorMessage = ""; + } + async login(username, password) { const status = await this._attemptLogin(this._loginOptions.password(username, password)); let error = ""; diff --git a/src/platform/web/ui/login/CompleteSSOView.js b/src/platform/web/ui/login/CompleteSSOView.js index cece7184..8b0654c6 100644 --- a/src/platform/web/ui/login/CompleteSSOView.js +++ b/src/platform/web/ui/login/CompleteSSOView.js @@ -22,7 +22,8 @@ export class CompleteSSOView extends TemplateView { return t.div({ className: "CompleteSSOView" }, [ t.p({ className: "CompleteSSOView_title" }, "Finishing up your SSO Login"), - t.mapView(vm => vm.loadViewModel, loadViewModel => loadViewModel ? new SessionLoadStatusView(loadViewModel) : null) + t.if(vm => vm.errorMessage, (t, vm) => t.p({className: "CompleteSSOView_error"}, vm.i18n(vm.errorMessage))), + t.mapView(vm => vm.loadViewModel, loadViewModel => loadViewModel ? new SessionLoadStatusView(loadViewModel) : null), ] ); } diff --git a/src/platform/web/ui/login/LoginView.js b/src/platform/web/ui/login/LoginView.js index 7672a719..1dcbde3d 100644 --- a/src/platform/web/ui/login/LoginView.js +++ b/src/platform/web/ui/login/LoginView.js @@ -29,6 +29,7 @@ export class LoginView extends TemplateView { t.mapView(vm => vm.completeSSOLoginViewModel, vm => vm ? new CompleteSSOView(vm) : null), t.if(vm => vm.showHomeserver, (t, vm) => t.div({ className: "LoginView_sso form form-row" }, [ + t.if(vm => vm.errorMessage, (t, vm) => t.p({className: "LoginView_error"}, vm.i18n(vm.errorMessage))), t.label({for: "homeserver"}, vm.i18n`Homeserver`), t.input({ id: "homeserver", @@ -43,7 +44,6 @@ export class LoginView extends TemplateView { t.mapView(vm => vm.passwordLoginViewModel, vm => vm ? new PasswordLoginView(vm): null), t.if(vm => vm.passwordLoginViewModel && vm.startSSOLoginViewModel, t => t.p({className: "LoginView_separator"}, vm.i18n`or`)), t.mapView(vm => vm.startSSOLoginViewModel, vm => vm ? new StartSSOLoginView(vm) : null), - t.if(vm => vm.errorMessage, (t, vm) => t.p({className: "LoginView_error"}, vm.i18n(vm.errorMessage))), t.mapView(vm => vm.loadViewModel, loadViewModel => loadViewModel ? new SessionLoadStatusView(loadViewModel) : null), // use t.mapView rather than t.if to create a new view when the view model changes too t.p(hydrogenGithubLink(t)) diff --git a/src/platform/web/ui/login/PasswordLoginView.js b/src/platform/web/ui/login/PasswordLoginView.js index ca26d1cc..7fd7d534 100644 --- a/src/platform/web/ui/login/PasswordLoginView.js +++ b/src/platform/web/ui/login/PasswordLoginView.js @@ -40,6 +40,7 @@ export class PasswordLoginView extends TemplateView { vm.login(username.value, password.value); } }, [ + t.if(vm => vm.errorMessage, (t, vm) => t.p({className: "PasswordLoginView_error"}, vm.i18n(vm.errorMessage))), t.div({ className: "form-row" }, [t.label({ for: "username" }, vm.i18n`Username`), username]), t.div({ className: "form-row" }, [t.label({ for: "password" }, vm.i18n`Password`), password]), t.div({ className: "button-row" }, [ From 55da58893b86f7771e494bf8480d0d32aa6fa654 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 20 Aug 2021 21:41:42 +0530 Subject: [PATCH 65/81] Red + bold error Signed-off-by: RMidhunSuresh --- src/platform/web/ui/css/login.css | 4 ---- src/platform/web/ui/css/themes/element/theme.css | 5 +++++ src/platform/web/ui/login/CompleteSSOView.js | 2 +- src/platform/web/ui/login/LoginView.js | 2 +- src/platform/web/ui/login/PasswordLoginView.js | 2 +- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/platform/web/ui/css/login.css b/src/platform/web/ui/css/login.css index 01b6bc6e..59a25799 100644 --- a/src/platform/web/ui/css/login.css +++ b/src/platform/web/ui/css/login.css @@ -96,7 +96,3 @@ limitations under the License. .LoginView_sso { padding: 0.4em 0.4em 0; } - -.LoginView_error { - padding: 0.4em -} diff --git a/src/platform/web/ui/css/themes/element/theme.css b/src/platform/web/ui/css/themes/element/theme.css index 08f1df06..4b7dab4d 100644 --- a/src/platform/web/ui/css/themes/element/theme.css +++ b/src/platform/web/ui/css/themes/element/theme.css @@ -161,6 +161,11 @@ a.button-action { background-image: url('icons/disable-grid.svg'); } +.error { + color: #f00; + font-weight: 600; +} + .FilterField { background-image: url('icons/search.svg'); background-repeat: no-repeat; diff --git a/src/platform/web/ui/login/CompleteSSOView.js b/src/platform/web/ui/login/CompleteSSOView.js index 8b0654c6..63614acf 100644 --- a/src/platform/web/ui/login/CompleteSSOView.js +++ b/src/platform/web/ui/login/CompleteSSOView.js @@ -22,7 +22,7 @@ export class CompleteSSOView extends TemplateView { return t.div({ className: "CompleteSSOView" }, [ t.p({ className: "CompleteSSOView_title" }, "Finishing up your SSO Login"), - t.if(vm => vm.errorMessage, (t, vm) => t.p({className: "CompleteSSOView_error"}, vm.i18n(vm.errorMessage))), + t.if(vm => vm.errorMessage, (t, vm) => t.p({className: "error"}, vm.i18n(vm.errorMessage))), t.mapView(vm => vm.loadViewModel, loadViewModel => loadViewModel ? new SessionLoadStatusView(loadViewModel) : null), ] ); diff --git a/src/platform/web/ui/login/LoginView.js b/src/platform/web/ui/login/LoginView.js index 1dcbde3d..b44a67df 100644 --- a/src/platform/web/ui/login/LoginView.js +++ b/src/platform/web/ui/login/LoginView.js @@ -29,7 +29,7 @@ export class LoginView extends TemplateView { t.mapView(vm => vm.completeSSOLoginViewModel, vm => vm ? new CompleteSSOView(vm) : null), t.if(vm => vm.showHomeserver, (t, vm) => t.div({ className: "LoginView_sso form form-row" }, [ - t.if(vm => vm.errorMessage, (t, vm) => t.p({className: "LoginView_error"}, vm.i18n(vm.errorMessage))), + t.if(vm => vm.errorMessage, (t, vm) => t.p({className: "error"}, vm.i18n(vm.errorMessage))), t.label({for: "homeserver"}, vm.i18n`Homeserver`), t.input({ id: "homeserver", diff --git a/src/platform/web/ui/login/PasswordLoginView.js b/src/platform/web/ui/login/PasswordLoginView.js index 7fd7d534..130f30ae 100644 --- a/src/platform/web/ui/login/PasswordLoginView.js +++ b/src/platform/web/ui/login/PasswordLoginView.js @@ -40,7 +40,7 @@ export class PasswordLoginView extends TemplateView { vm.login(username.value, password.value); } }, [ - t.if(vm => vm.errorMessage, (t, vm) => t.p({className: "PasswordLoginView_error"}, vm.i18n(vm.errorMessage))), + t.if(vm => vm.errorMessage, (t, vm) => t.p({className: "error"}, vm.i18n(vm.errorMessage))), t.div({ className: "form-row" }, [t.label({ for: "username" }, vm.i18n`Username`), username]), t.div({ className: "form-row" }, [t.label({ for: "password" }, vm.i18n`Password`), password]), t.div({ className: "button-row" }, [ From 82067ca6f57f568aaff9f450cbe5ca650cc17ba5 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 20 Aug 2021 21:43:42 +0530 Subject: [PATCH 66/81] No need to pass showError Signed-off-by: RMidhunSuresh --- src/domain/login/LoginViewModel.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/domain/login/LoginViewModel.js b/src/domain/login/LoginViewModel.js index 381dc260..74a8d67d 100644 --- a/src/domain/login/LoginViewModel.js +++ b/src/domain/login/LoginViewModel.js @@ -60,7 +60,6 @@ export class LoginViewModel extends ViewModel { { sessionContainer: this._sessionContainer, attemptLogin: loginMethod => this.attemptLogin(loginMethod), - showError: message => this.showError(message), loginToken: this._loginToken }))); this.emitChange("completeSSOLoginViewModel"); @@ -77,11 +76,11 @@ export class LoginViewModel extends ViewModel { if (this._loginOptions.sso) { this._showSSOLogin(); } if (this._loginOptions.password) { this._showPasswordLogin(); } if (!this._loginOptions.sso && !this._loginOptions.password) { - this.showError("This homeserver neither supports SSO nor Password based login flows"); + this._showError("This homeserver neither supports SSO nor Password based login flows"); } } else { - this.showError("Could not query login methods supported by the homeserver"); + this._showError("Could not query login methods supported by the homeserver"); } } } @@ -92,7 +91,6 @@ export class LoginViewModel extends ViewModel { loginOptions: this._loginOptions, homeserver: this._homeserver, attemptLogin: loginMethod => this.attemptLogin(loginMethod), - showError: message => this.showError(message) }))); this.emitChange("passwordLoginViewModel"); } @@ -104,7 +102,7 @@ export class LoginViewModel extends ViewModel { this.emitChange("startSSOLoginViewModel"); } - showError(message) { + _showError(message) { this._errorMessage = message; this.emitChange("errorMessage"); this._errorMessage = ""; From a5985cba2a15106ed6d7e3a9130a8f44ce606229 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 20 Aug 2021 22:18:24 +0530 Subject: [PATCH 67/81] Add spinner Signed-off-by: RMidhunSuresh --- src/domain/login/LoginViewModel.js | 6 ++++++ src/platform/web/ui/css/login.css | 6 +++--- src/platform/web/ui/login/LoginView.js | 2 ++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/domain/login/LoginViewModel.js b/src/domain/login/LoginViewModel.js index 74a8d67d..9a1b75df 100644 --- a/src/domain/login/LoginViewModel.js +++ b/src/domain/login/LoginViewModel.js @@ -39,6 +39,7 @@ export class LoginViewModel extends ViewModel { this._errorMessage = ""; this._hideHomeserver = false; this._isBusy = false; + this._isFetchingLoginOptions = false; this._createViewModels(this._homeserver); } @@ -51,6 +52,7 @@ export class LoginViewModel extends ViewModel { get cancelUrl() { return this.urlCreator.urlForSegment("session"); } get loadViewModel() {return this._loadViewModel; } get isBusy() { return this._isBusy; } + get isFetchingLoginOptions() { return this._isFetchingLoginOptions; } async _createViewModels(homeserver) { if (this._loginToken) { @@ -67,11 +69,15 @@ export class LoginViewModel extends ViewModel { else { this._errorMessage = ""; try { + this._isFetchingLoginOptions = true; + this.emitChange("isFetchingLoginOptions"); this._loginOptions = await this._sessionContainer.queryLogin(homeserver); } catch (e) { this._loginOptions = null; } + this._isFetchingLoginOptions = false; + this.emitChange("isFetchingLoginOptions"); if (this._loginOptions) { if (this._loginOptions.sso) { this._showSSOLogin(); } if (this._loginOptions.password) { this._showPasswordLogin(); } diff --git a/src/platform/web/ui/css/login.css b/src/platform/web/ui/css/login.css index 59a25799..ca376dee 100644 --- a/src/platform/web/ui/css/login.css +++ b/src/platform/web/ui/css/login.css @@ -54,15 +54,15 @@ limitations under the License. padding: 0 0.4em 0.4em; } -.SessionLoadStatusView { +.SessionLoadStatusView, .LoginView_query-spinner { display: flex; } -.SessionLoadStatusView> :not(:first-child) { +.SessionLoadStatusView> :not(:first-child), .LoginView_query-spinner> :not(:first-child) { margin-left: 12px; } -.SessionLoadStatusView p { +.SessionLoadStatusView p, .LoginView_query-spinner p { flex: 1; margin: 0; } diff --git a/src/platform/web/ui/login/LoginView.js b/src/platform/web/ui/login/LoginView.js index b44a67df..4b507567 100644 --- a/src/platform/web/ui/login/LoginView.js +++ b/src/platform/web/ui/login/LoginView.js @@ -19,6 +19,7 @@ import {hydrogenGithubLink} from "./common.js"; import {PasswordLoginView} from "./PasswordLoginView.js"; import {CompleteSSOView} from "./CompleteSSOView.js"; import {SessionLoadStatusView} from "./SessionLoadStatusView.js"; +import {spinner} from "../common.js"; export class LoginView extends TemplateView { render(t, vm) { @@ -41,6 +42,7 @@ export class LoginView extends TemplateView { }) ] )), + t.if(vm => vm.isFetchingLoginOptions, t => t.div({className: "LoginView_query-spinner"}, [spinner(t), t.p("Fetching available login options...")])), t.mapView(vm => vm.passwordLoginViewModel, vm => vm ? new PasswordLoginView(vm): null), t.if(vm => vm.passwordLoginViewModel && vm.startSSOLoginViewModel, t => t.p({className: "LoginView_separator"}, vm.i18n`or`)), t.mapView(vm => vm.startSSOLoginViewModel, vm => vm ? new StartSSOLoginView(vm) : null), From 0630452571c507b8215d8e059fcdb4013f809aa2 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 20 Aug 2021 22:53:15 +0530 Subject: [PATCH 68/81] No need to observe Signed-off-by: RMidhunSuresh --- src/domain/RootViewModel.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/domain/RootViewModel.js b/src/domain/RootViewModel.js index 4a0a969e..d6fdcfa0 100644 --- a/src/domain/RootViewModel.js +++ b/src/domain/RootViewModel.js @@ -40,10 +40,9 @@ export class RootViewModel extends ViewModel { } async _applyNavigation(shouldRestoreLastUrl) { - const isLogin = this.navigation.observe("login").get(); - const sessionId = this.navigation.observe("session").get(); - // TODO: why not observe? - const ssoSegment = this.navigation.path.get("sso"); + const isLogin = this.navigation.path.get("login") + const sessionId = this.navigation.path.get("session")?.value; + const loginToken = this.navigation.path.get("sso")?.value; if (isLogin) { if (this.activeSection !== "login") { this._showLogin(); @@ -68,10 +67,10 @@ export class RootViewModel extends ViewModel { this._showSessionLoader(sessionId); } } - } else if (ssoSegment) { + } else if (loginToken) { this.urlCreator.normalizeUrl(); if (this.activeSection !== "login") { - this._showLogin({loginToken: ssoSegment.value}); + this._showLogin(loginToken); } } else { @@ -120,7 +119,7 @@ export class RootViewModel extends ViewModel { this._pendingSessionContainer = sessionContainer; this.navigation.push("session", sessionContainer.sessionId); }, - ...loginToken + loginToken })); }); } From 94ba93acb5272cee7c4bb94e6c9653c4b2e5055a Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 20 Aug 2021 22:57:13 +0530 Subject: [PATCH 69/81] Add explaining comment Signed-off-by: RMidhunSuresh --- src/domain/SessionLoadViewModel.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/domain/SessionLoadViewModel.js b/src/domain/SessionLoadViewModel.js index 956ba3a4..10cbb851 100644 --- a/src/domain/SessionLoadViewModel.js +++ b/src/domain/SessionLoadViewModel.js @@ -108,6 +108,7 @@ export class SessionLoadViewModel extends ViewModel { return `Something went wrong: ${error && error.message}.`; } + // Statuses related to login are handled by respective login view models if (sc) { switch (sc.loadStatus.get()) { case LoadStatus.SessionSetup: From 0e6139d5e3e453261d9ece6b4162a96281f1942f Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 20 Aug 2021 23:02:06 +0530 Subject: [PATCH 70/81] Use homeserver from login method Signed-off-by: RMidhunSuresh --- src/domain/login/LoginViewModel.js | 3 +-- src/domain/login/PasswordLoginViewModel.js | 8 ++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/domain/login/LoginViewModel.js b/src/domain/login/LoginViewModel.js index 9a1b75df..129b5913 100644 --- a/src/domain/login/LoginViewModel.js +++ b/src/domain/login/LoginViewModel.js @@ -95,8 +95,7 @@ export class LoginViewModel extends ViewModel { this._passwordLoginViewModel = this.track(new PasswordLoginViewModel( this.childOptions({ loginOptions: this._loginOptions, - homeserver: this._homeserver, - attemptLogin: loginMethod => this.attemptLogin(loginMethod), + attemptLogin: loginMethod => this.attemptLogin(loginMethod) }))); this.emitChange("passwordLoginViewModel"); } diff --git a/src/domain/login/PasswordLoginViewModel.js b/src/domain/login/PasswordLoginViewModel.js index 3524779e..8cdef3c5 100644 --- a/src/domain/login/PasswordLoginViewModel.js +++ b/src/domain/login/PasswordLoginViewModel.js @@ -20,10 +20,9 @@ import {LoginFailure} from "../../matrix/SessionContainer.js"; export class PasswordLoginViewModel extends ViewModel { constructor(options) { super(options); - const {loginOptions, homeserver, attemptLogin} = options; + const {loginOptions, attemptLogin} = options; this._loginOptions = loginOptions; this._attemptLogin = attemptLogin; - this._homeserver = homeserver; this._isBusy = false; this._errorMessage = ""; } @@ -43,14 +42,15 @@ export class PasswordLoginViewModel extends ViewModel { } async login(username, password) { - const status = await this._attemptLogin(this._loginOptions.password(username, password)); + const loginMethod = this._loginOptions.password(username, password); + const status = await this._attemptLogin(loginMethod); let error = ""; switch (status) { case LoginFailure.Credentials: error = this.i18n`Your username and/or password don't seem to be correct.`; break; case LoginFailure.Connection: - error = this.i18n`Can't connect to ${this._homeserver}.`; + error = this.i18n`Can't connect to ${loginMethod.homeServer}.`; break; case LoginFailure.Unknown: error = this.i18n`Something went wrong while checking your login and password.`; From d47e126370683cda318cabc49d209d6a2abbb842 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 20 Aug 2021 23:03:51 +0530 Subject: [PATCH 71/81] add missing emit Signed-off-by: RMidhunSuresh --- src/domain/login/LoginViewModel.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/domain/login/LoginViewModel.js b/src/domain/login/LoginViewModel.js index 129b5913..b080c76e 100644 --- a/src/domain/login/LoginViewModel.js +++ b/src/domain/login/LoginViewModel.js @@ -134,6 +134,7 @@ export class LoginViewModel extends ViewModel { return this._sessionContainer.loginFailure; } this._hideHomeserver = true; + this.emitChange("hideHomeserver"); this._disposeViewModels(); this._createLoadViewModel(); return null; From 04806a1425e67847d38402a7e36610467ef612a6 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 20 Aug 2021 23:16:03 +0530 Subject: [PATCH 72/81] Convert link to button Signed-off-by: RMidhunSuresh --- src/domain/login/LoginViewModel.js | 6 +++++- src/platform/web/ui/login/LoginView.js | 10 ++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/domain/login/LoginViewModel.js b/src/domain/login/LoginViewModel.js index b080c76e..27e45d1f 100644 --- a/src/domain/login/LoginViewModel.js +++ b/src/domain/login/LoginViewModel.js @@ -49,11 +49,15 @@ export class LoginViewModel extends ViewModel { get homeserver() { return this._homeserver; } get errorMessage() { return this._errorMessage; } get showHomeserver() { return !this._hideHomeserver; } - get cancelUrl() { return this.urlCreator.urlForSegment("session"); } get loadViewModel() {return this._loadViewModel; } get isBusy() { return this._isBusy; } get isFetchingLoginOptions() { return this._isFetchingLoginOptions; } + goBack() { + const path = this.navigation.pathFrom([this.navigation.segment("session")]); + this.navigation.applyPath(path); + } + async _createViewModels(homeserver) { if (this._loginToken) { this._hideHomeserver = true; diff --git a/src/platform/web/ui/login/LoginView.js b/src/platform/web/ui/login/LoginView.js index 4b507567..3d8e8470 100644 --- a/src/platform/web/ui/login/LoginView.js +++ b/src/platform/web/ui/login/LoginView.js @@ -23,8 +23,14 @@ import {spinner} from "../common.js"; export class LoginView extends TemplateView { render(t, vm) { + const disabled = vm => vm.isBusy; + return t.div({className: "PreSessionScreen"}, [ - t.a({className: "button-utility LoginView_back", href: vm.cancelUrl}), + t.button({ + className: "button-utility LoginView_back", + onClick: () => vm.goBack(), + disabled + }), t.div({className: "logo"}), t.h1([vm.i18n`Sign In`]), t.mapView(vm => vm.completeSSOLoginViewModel, vm => vm ? new CompleteSSOView(vm) : null), @@ -37,7 +43,7 @@ export class LoginView extends TemplateView { type: "text", placeholder: vm.i18n`Your matrix homeserver`, value: vm.homeserver, - disabled: vm => vm.isBusy, + disabled, onChange: event => vm.updateHomeServer(event.target.value), }) ] From 84fd2861402c5dbfe3caf0725ba8175ac9dbd64c Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 23 Aug 2021 15:06:16 +0530 Subject: [PATCH 73/81] Split logintoken into two words Signed-off-by: RMidhunSuresh --- src/domain/login/CompleteSSOLoginViewModel.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/domain/login/CompleteSSOLoginViewModel.js b/src/domain/login/CompleteSSOLoginViewModel.js index 9bdb0509..ded09cd9 100644 --- a/src/domain/login/CompleteSSOLoginViewModel.js +++ b/src/domain/login/CompleteSSOLoginViewModel.js @@ -55,13 +55,13 @@ export class CompleteSSOLoginViewModel extends ViewModel { let error = ""; switch (status) { case LoginFailure.Credentials: - error = this.i18n`Your logintoken is invalid.`; + error = this.i18n`Your login token is invalid.`; break; case LoginFailure.Connection: error = this.i18n`Can't connect to ${homeserver}.`; break; case LoginFailure.Unknown: - error = this.i18n`Something went wrong while checking your logintoken.`; + error = this.i18n`Something went wrong while checking your login token.`; break; } if (error) { From c9fbafb909fd64b81cbe2f4a26227c18d20376f1 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 23 Aug 2021 15:12:40 +0530 Subject: [PATCH 74/81] Also check LoadStatus.Error Signed-off-by: RMidhunSuresh --- src/matrix/SessionContainer.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/matrix/SessionContainer.js b/src/matrix/SessionContainer.js index ca8379bc..27776d37 100644 --- a/src/matrix/SessionContainer.js +++ b/src/matrix/SessionContainer.js @@ -130,7 +130,9 @@ export class SessionContainer { async startWithLogin(loginMethod) { const currentStatus = this._status.get(); - if (currentStatus !== LoadStatus.LoginFailed && currentStatus !== LoadStatus.NotLoading) { + if (currentStatus !== LoadStatus.LoginFailed && + currentStatus !== LoadStatus.NotLoading && + currentStatus !== LoadStatus.Error) { return; } this._resetStatus(); From ecfdc314d5a8b9b0c4bee1c1538176c32414ad3b Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 23 Aug 2021 15:28:44 +0530 Subject: [PATCH 75/81] Do not set error message to empty string Signed-off-by: RMidhunSuresh --- src/domain/login/CompleteSSOLoginViewModel.js | 1 - src/domain/login/LoginViewModel.js | 3 ++- src/domain/login/PasswordLoginViewModel.js | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/domain/login/CompleteSSOLoginViewModel.js b/src/domain/login/CompleteSSOLoginViewModel.js index ded09cd9..c06b3ffd 100644 --- a/src/domain/login/CompleteSSOLoginViewModel.js +++ b/src/domain/login/CompleteSSOLoginViewModel.js @@ -37,7 +37,6 @@ export class CompleteSSOLoginViewModel extends ViewModel { _showError(message) { this._errorMessage = message; this.emitChange("errorMessage"); - this._errorMessage = ""; } async performSSOLoginCompletion() { diff --git a/src/domain/login/LoginViewModel.js b/src/domain/login/LoginViewModel.js index 27e45d1f..13087581 100644 --- a/src/domain/login/LoginViewModel.js +++ b/src/domain/login/LoginViewModel.js @@ -114,7 +114,6 @@ export class LoginViewModel extends ViewModel { _showError(message) { this._errorMessage = message; this.emitChange("errorMessage"); - this._errorMessage = ""; } _toggleBusy(status) { @@ -180,6 +179,8 @@ export class LoginViewModel extends ViewModel { } updateHomeServer(newHomeserver) { + this._errorMessage = ""; + this.emitChange("errorMessage"); this._homeserver = newHomeserver; this._disposeViewModels(); this._createViewModels(newHomeserver); diff --git a/src/domain/login/PasswordLoginViewModel.js b/src/domain/login/PasswordLoginViewModel.js index 8cdef3c5..0d534b90 100644 --- a/src/domain/login/PasswordLoginViewModel.js +++ b/src/domain/login/PasswordLoginViewModel.js @@ -38,10 +38,11 @@ export class PasswordLoginViewModel extends ViewModel { _showError(message) { this._errorMessage = message; this.emitChange("errorMessage"); - this._errorMessage = ""; } async login(username, password) { + this._errorMessage = ""; + this.emitChange("errorMessage"); const loginMethod = this._loginOptions.password(username, password); const status = await this._attemptLogin(loginMethod); let error = ""; From 69478b81b2c45689d7e16d794801d32ddace6e64 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 23 Aug 2021 15:48:29 +0530 Subject: [PATCH 76/81] Fix toggleBusy Signed-off-by: RMidhunSuresh --- src/domain/login/LoginViewModel.js | 14 ++++++-------- src/domain/login/PasswordLoginViewModel.js | 4 ++-- src/domain/login/StartSSOLoginViewModel.js | 4 ++-- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/domain/login/LoginViewModel.js b/src/domain/login/LoginViewModel.js index 13087581..97534a56 100644 --- a/src/domain/login/LoginViewModel.js +++ b/src/domain/login/LoginViewModel.js @@ -116,22 +116,20 @@ export class LoginViewModel extends ViewModel { this.emitChange("errorMessage"); } - _toggleBusy(status) { - this._isBusy = status; + _toggleBusy() { + this._isBusy = !this._isBusy; + this._passwordLoginViewModel?.toggleBusy(); + this._startSSOLoginViewModel?.toggleBusy(); this.emitChange("isBusy"); } async attemptLogin(loginMethod) { - this._toggleBusy(true); - this._passwordLoginViewModel?.toggleBusy(true); - this._startSSOLoginViewModel?.toggleBusy(true); + this._toggleBusy(); this._sessionContainer.startWithLogin(loginMethod); const loadStatus = this._sessionContainer.loadStatus; const handle = loadStatus.waitFor(status => status !== LoadStatus.Login); await handle.promise; - this._toggleBusy(false); - this._passwordLoginViewModel?.toggleBusy(false); - this._startSSOLoginViewModel?.toggleBusy(false); + this._toggleBusy(); const status = loadStatus.get(); if (status === LoadStatus.LoginFailed) { return this._sessionContainer.loginFailure; diff --git a/src/domain/login/PasswordLoginViewModel.js b/src/domain/login/PasswordLoginViewModel.js index 0d534b90..7ba75c18 100644 --- a/src/domain/login/PasswordLoginViewModel.js +++ b/src/domain/login/PasswordLoginViewModel.js @@ -30,8 +30,8 @@ export class PasswordLoginViewModel extends ViewModel { get isBusy() { return this._isBusy; } get errorMessage() { return this._errorMessage; } - toggleBusy(state) { - this._isBusy = state; + toggleBusy() { + this._isBusy = !this._isBusy; this.emitChange("isBusy"); } diff --git a/src/domain/login/StartSSOLoginViewModel.js b/src/domain/login/StartSSOLoginViewModel.js index e4c0cf92..bd236788 100644 --- a/src/domain/login/StartSSOLoginViewModel.js +++ b/src/domain/login/StartSSOLoginViewModel.js @@ -25,8 +25,8 @@ export class StartSSOLoginViewModel extends ViewModel{ get isBusy() { return this._isBusy; } - toggleBusy(state) { - this._isBusy = state; + toggleBusy() { + this._isBusy = !this._isBusy; this.emitChange("isBusy"); } From e80667c93547eaa944991448ca4e232c21214c64 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 23 Aug 2021 15:50:22 +0530 Subject: [PATCH 77/81] Remove duplicate style Signed-off-by: RMidhunSuresh --- src/platform/web/ui/css/themes/element/theme.css | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/platform/web/ui/css/themes/element/theme.css b/src/platform/web/ui/css/themes/element/theme.css index 4b7dab4d..08f1df06 100644 --- a/src/platform/web/ui/css/themes/element/theme.css +++ b/src/platform/web/ui/css/themes/element/theme.css @@ -161,11 +161,6 @@ a.button-action { background-image: url('icons/disable-grid.svg'); } -.error { - color: #f00; - font-weight: 600; -} - .FilterField { background-image: url('icons/search.svg'); background-repeat: no-repeat; From b0db7e03441edf851f977329ebc3a1be6e3c4f1b Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 23 Aug 2021 15:55:07 +0530 Subject: [PATCH 78/81] More simpler navigation Signed-off-by: RMidhunSuresh --- src/domain/login/LoginViewModel.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/domain/login/LoginViewModel.js b/src/domain/login/LoginViewModel.js index 97534a56..832d2916 100644 --- a/src/domain/login/LoginViewModel.js +++ b/src/domain/login/LoginViewModel.js @@ -54,8 +54,7 @@ export class LoginViewModel extends ViewModel { get isFetchingLoginOptions() { return this._isFetchingLoginOptions; } goBack() { - const path = this.navigation.pathFrom([this.navigation.segment("session")]); - this.navigation.applyPath(path); + this.navigation.push("session"); } async _createViewModels(homeserver) { From fc169af10f0b50f6de02892eb87140c7d889de2a Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 23 Aug 2021 16:09:40 +0530 Subject: [PATCH 79/81] Rename toggle to set Signed-off-by: RMidhunSuresh --- src/domain/login/LoginViewModel.js | 14 +++++++------- src/domain/login/PasswordLoginViewModel.js | 4 ++-- src/domain/login/StartSSOLoginViewModel.js | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/domain/login/LoginViewModel.js b/src/domain/login/LoginViewModel.js index 832d2916..85dddb79 100644 --- a/src/domain/login/LoginViewModel.js +++ b/src/domain/login/LoginViewModel.js @@ -115,20 +115,20 @@ export class LoginViewModel extends ViewModel { this.emitChange("errorMessage"); } - _toggleBusy() { - this._isBusy = !this._isBusy; - this._passwordLoginViewModel?.toggleBusy(); - this._startSSOLoginViewModel?.toggleBusy(); + _setBusy(status) { + this._isBusy = status; + this._passwordLoginViewModel?.setBusy(status); + this._startSSOLoginViewModel?.setBusy(status); this.emitChange("isBusy"); } async attemptLogin(loginMethod) { - this._toggleBusy(); + this._setBusy(true); this._sessionContainer.startWithLogin(loginMethod); const loadStatus = this._sessionContainer.loadStatus; const handle = loadStatus.waitFor(status => status !== LoadStatus.Login); await handle.promise; - this._toggleBusy(); + this._setBusy(false); const status = loadStatus.get(); if (status === LoadStatus.LoginFailed) { return this._sessionContainer.loginFailure; @@ -163,7 +163,7 @@ export class LoginViewModel extends ViewModel { if (!this._loadViewModel.loading) { this._loadViewModelSubscription = this.disposeTracked(this._loadViewModelSubscription); } - this._toggleBusy(false); + this._setBusy(false); }) ); } diff --git a/src/domain/login/PasswordLoginViewModel.js b/src/domain/login/PasswordLoginViewModel.js index 7ba75c18..f8d354a4 100644 --- a/src/domain/login/PasswordLoginViewModel.js +++ b/src/domain/login/PasswordLoginViewModel.js @@ -30,8 +30,8 @@ export class PasswordLoginViewModel extends ViewModel { get isBusy() { return this._isBusy; } get errorMessage() { return this._errorMessage; } - toggleBusy() { - this._isBusy = !this._isBusy; + setBusy(status) { + this._isBusy = status; this.emitChange("isBusy"); } diff --git a/src/domain/login/StartSSOLoginViewModel.js b/src/domain/login/StartSSOLoginViewModel.js index bd236788..54218d22 100644 --- a/src/domain/login/StartSSOLoginViewModel.js +++ b/src/domain/login/StartSSOLoginViewModel.js @@ -25,8 +25,8 @@ export class StartSSOLoginViewModel extends ViewModel{ get isBusy() { return this._isBusy; } - toggleBusy() { - this._isBusy = !this._isBusy; + setBusy(status) { + this._isBusy = status; this.emitChange("isBusy"); } From c9319c7c3880abae669ffd6e657d2ea9a09891db Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 23 Aug 2021 16:58:54 +0530 Subject: [PATCH 80/81] Catch any error from queryLogin Signed-off-by: RMidhunSuresh --- src/domain/login/CompleteSSOLoginViewModel.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/domain/login/CompleteSSOLoginViewModel.js b/src/domain/login/CompleteSSOLoginViewModel.js index c06b3ffd..c1f6223c 100644 --- a/src/domain/login/CompleteSSOLoginViewModel.js +++ b/src/domain/login/CompleteSSOLoginViewModel.js @@ -44,7 +44,14 @@ export class CompleteSSOLoginViewModel extends ViewModel { return; } const homeserver = await this.platform.settingsStorage.getString("sso_ongoing_login_homeserver"); - const loginOptions = await this._sessionContainer.queryLogin(homeserver); + let loginOptions; + try { + loginOptions = await this._sessionContainer.queryLogin(homeserver); + } + catch (err) { + this._showError(err.message); + return; + } if (!loginOptions.token) { const path = this.navigation.pathFrom([this.navigation.segment("session")]); this.navigation.applyPath(path); From ef4db4ababb9db97c7d06ebbf3f10cb835a238e1 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 23 Aug 2021 17:00:41 +0530 Subject: [PATCH 81/81] Make navigation simpler Signed-off-by: RMidhunSuresh --- src/domain/login/CompleteSSOLoginViewModel.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/domain/login/CompleteSSOLoginViewModel.js b/src/domain/login/CompleteSSOLoginViewModel.js index c1f6223c..b7e04cc8 100644 --- a/src/domain/login/CompleteSSOLoginViewModel.js +++ b/src/domain/login/CompleteSSOLoginViewModel.js @@ -53,8 +53,7 @@ export class CompleteSSOLoginViewModel extends ViewModel { return; } if (!loginOptions.token) { - const path = this.navigation.pathFrom([this.navigation.segment("session")]); - this.navigation.applyPath(path); + this.navigation.push("session"); return; } const status = await this._attemptLogin(loginOptions.token(this._loginToken));