Split login view into password and sso components
Signed-off-by: RMidhunSuresh <rmidhunsuresh@gmail.com>
This commit is contained in:
parent
cabffd5e3f
commit
b8f0361157
10 changed files with 361 additions and 185 deletions
|
@ -1,120 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -16,7 +16,7 @@ limitations under the License.
|
||||||
|
|
||||||
import {SessionViewModel} from "./session/SessionViewModel.js";
|
import {SessionViewModel} from "./session/SessionViewModel.js";
|
||||||
import {SessionLoadViewModel} from "./SessionLoadViewModel.js";
|
import {SessionLoadViewModel} from "./SessionLoadViewModel.js";
|
||||||
import {LoginViewModel} from "./LoginViewModel.js";
|
import {LoginViewModel} from "./login/LoginViewModel.js";
|
||||||
import {SessionPickerViewModel} from "./SessionPickerViewModel.js";
|
import {SessionPickerViewModel} from "./SessionPickerViewModel.js";
|
||||||
import {ViewModel} from "./ViewModel.js";
|
import {ViewModel} from "./ViewModel.js";
|
||||||
|
|
||||||
|
@ -42,7 +42,8 @@ export class RootViewModel extends ViewModel {
|
||||||
async _applyNavigation(shouldRestoreLastUrl) {
|
async _applyNavigation(shouldRestoreLastUrl) {
|
||||||
const isLogin = this.navigation.observe("login").get();
|
const isLogin = this.navigation.observe("login").get();
|
||||||
const sessionId = this.navigation.observe("session").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 (isLogin) {
|
||||||
if (this.activeSection !== "login") {
|
if (this.activeSection !== "login") {
|
||||||
this._showLogin();
|
this._showLogin();
|
||||||
|
@ -67,8 +68,11 @@ export class RootViewModel extends ViewModel {
|
||||||
this._showSessionLoader(sessionId);
|
this._showSessionLoader(sessionId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (SSOSegment) {
|
} else if (ssoSegment) {
|
||||||
this._setSection(() => this.showCompletionView = true);
|
this.urlCreator.normalizeUrl();
|
||||||
|
if (this.activeSection !== "login") {
|
||||||
|
this._showLogin({loginToken: ssoSegment.value});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
try {
|
try {
|
||||||
|
@ -99,7 +103,7 @@ export class RootViewModel extends ViewModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_showLogin() {
|
_showLogin(options) {
|
||||||
this._setSection(() => {
|
this._setSection(() => {
|
||||||
this._loginViewModel = new LoginViewModel(this.childOptions({
|
this._loginViewModel = new LoginViewModel(this.childOptions({
|
||||||
defaultHomeServer: this.platform.config["defaultHomeServer"],
|
defaultHomeServer: this.platform.config["defaultHomeServer"],
|
||||||
|
@ -116,6 +120,7 @@ export class RootViewModel extends ViewModel {
|
||||||
this._pendingSessionContainer = sessionContainer;
|
this._pendingSessionContainer = sessionContainer;
|
||||||
this.navigation.push("session", sessionContainer.sessionId);
|
this.navigation.push("session", sessionContainer.sessionId);
|
||||||
},
|
},
|
||||||
|
...options
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -152,8 +157,6 @@ export class RootViewModel extends ViewModel {
|
||||||
return "picker";
|
return "picker";
|
||||||
} else if (this._sessionLoadViewModel) {
|
} else if (this._sessionLoadViewModel) {
|
||||||
return "loading";
|
return "loading";
|
||||||
} else if (this.showCompletionView) {
|
|
||||||
return "sso";
|
|
||||||
} else {
|
} else {
|
||||||
return "redirecting";
|
return "redirecting";
|
||||||
}
|
}
|
||||||
|
|
105
src/domain/login/LoginViewModel.js
Normal file
105
src/domain/login/LoginViewModel.js
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
77
src/domain/login/PasswordLoginViewModel.js
Normal file
77
src/domain/login/PasswordLoginViewModel.js
Normal file
|
@ -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");
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
46
src/domain/login/SSOLoginViewModel.js
Normal file
46
src/domain/login/SSOLoginViewModel.js
Normal file
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
23
src/domain/login/common.js
Normal file
23
src/domain/login/common.js
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,7 +20,6 @@ import {SessionLoadView} from "./login/SessionLoadView.js";
|
||||||
import {SessionPickerView} from "./login/SessionPickerView.js";
|
import {SessionPickerView} from "./login/SessionPickerView.js";
|
||||||
import {TemplateView} from "./general/TemplateView.js";
|
import {TemplateView} from "./general/TemplateView.js";
|
||||||
import {StaticView} from "./general/StaticView.js";
|
import {StaticView} from "./general/StaticView.js";
|
||||||
import {CompleteSSOView} from "./login/CompleteSSOView.js";
|
|
||||||
|
|
||||||
export class RootView extends TemplateView {
|
export class RootView extends TemplateView {
|
||||||
render(t, vm) {
|
render(t, vm) {
|
||||||
|
@ -43,8 +42,6 @@ export class RootView extends TemplateView {
|
||||||
return new StaticView(t => t.p("Redirecting..."));
|
return new StaticView(t => t.p("Redirecting..."));
|
||||||
case "loading":
|
case "loading":
|
||||||
return new SessionLoadView(vm.sessionLoadViewModel);
|
return new SessionLoadView(vm.sessionLoadViewModel);
|
||||||
case "sso":
|
|
||||||
return new CompleteSSOView();
|
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unknown section: ${vm.activeSection}`);
|
throw new Error(`Unknown section: ${vm.activeSection}`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,9 +15,15 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {TemplateView} from "../general/TemplateView.js";
|
import {TemplateView} from "../general/TemplateView.js";
|
||||||
|
import {SessionLoadStatusView} from "./SessionLoadStatusView.js";
|
||||||
|
|
||||||
export class CompleteSSOView extends TemplateView {
|
export class CompleteSSOView extends TemplateView {
|
||||||
render(t) {
|
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)
|
||||||
|
]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,63 +16,31 @@ limitations under the License.
|
||||||
|
|
||||||
import {TemplateView} from "../general/TemplateView.js";
|
import {TemplateView} from "../general/TemplateView.js";
|
||||||
import {hydrogenGithubLink} from "./common.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 {
|
export class LoginView extends TemplateView {
|
||||||
render(t, vm) {
|
render(t) {
|
||||||
const disabled = vm => !!vm.isBusy;
|
return t.div({ className: "PreSessionScreen" }, [
|
||||||
const username = t.input({
|
t.div({ className: "logo" }),
|
||||||
id: "username",
|
t.mapView(vm => vm.passwordLoginViewModel, vm => vm? new PasswordLoginView(vm): null),
|
||||||
type: "text",
|
t.mapView(vm => vm.ssoLoginViewModel, vm => {
|
||||||
placeholder: vm.i18n`Username`,
|
if (vm?.isSSOCompletion) {
|
||||||
disabled
|
return new CompleteSSOView(vm);
|
||||||
});
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}, [
|
else if (vm) {
|
||||||
t.div({className: "form-row"}, [t.label({for: "username"}, vm.i18n`Username`), username]),
|
return new SSOLoginView(vm);
|
||||||
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]),
|
return null;
|
||||||
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
|
// use t.mapView rather than t.if to create a new view when the view model changes too
|
||||||
t.p(hydrogenGithubLink(t))
|
t.p(hydrogenGithubLink(t))
|
||||||
])
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class SSOLoginView extends TemplateView {
|
||||||
|
render(t, vm) {
|
||||||
|
return t.button({className: "SSO", type: "button", onClick: () => vm.startSSOLogin()}, "Login with SSO");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
71
src/platform/web/ui/login/PasswordLoginView.js
Normal file
71
src/platform/web/ui/login/PasswordLoginView.js
Normal file
|
@ -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`),
|
||||||
|
]),
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Reference in a new issue