Merge pull request #5296 from matrix-org/jryans/sso-4s-integration

Convert auth lifecycle to TS, remove dead ILAG code
pull/21833/head
J. Ryan Stinnett 2020-10-07 15:42:42 +01:00 committed by GitHub
commit 779e43b8dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 247 additions and 1057 deletions

View File

@ -1,50 +1,31 @@
# autogenerated file: run scripts/generate-eslint-error-ignore-file to update.
src/components/structures/RoomDirectory.js
src/components/structures/RoomStatusBar.js
src/components/structures/ScrollPanel.js
src/components/structures/SearchBox.js
src/components/structures/UploadBar.js
src/components/views/avatars/MemberAvatar.js
src/components/views/create_room/RoomAlias.js
src/components/views/dialogs/SetPasswordDialog.js
src/components/views/elements/AddressSelector.js
src/components/views/elements/DirectorySearchBox.js
src/components/views/elements/MemberEventListSummary.js
src/components/views/elements/UserSelector.js
src/components/views/globals/NewVersionBar.js
src/components/views/messages/MFileBody.js
src/components/views/messages/TextualBody.js
src/components/views/room_settings/ColorSettings.js
src/components/views/rooms/Autocomplete.js
src/components/views/rooms/AuxPanel.js
src/components/views/rooms/LinkPreviewWidget.js
src/components/views/rooms/MemberInfo.js
src/components/views/rooms/MemberList.js
src/components/views/rooms/RoomList.js
src/components/views/rooms/RoomPreviewBar.js
src/components/views/rooms/SearchResultTile.js
src/components/views/settings/ChangeAvatar.js
src/components/views/settings/ChangePassword.js
src/components/views/settings/DevicesPanel.js
src/components/views/settings/Notifications.js
src/HtmlUtils.js
src/ImageUtils.js
src/Markdown.js
src/notifications/ContentRules.js
src/notifications/PushRuleVectorState.js
src/PlatformPeg.js
src/rageshake/rageshake.js
src/ratelimitedfunc.js
src/Rooms.js
src/Unread.js
src/Velociraptor.js
src/components/structures/RoomDirectory.js
src/components/structures/ScrollPanel.js
src/components/structures/UploadBar.js
src/components/views/elements/AddressSelector.js
src/components/views/elements/DirectorySearchBox.js
src/components/views/messages/MFileBody.js
src/components/views/messages/TextualBody.js
src/components/views/rooms/AuxPanel.js
src/components/views/rooms/LinkPreviewWidget.js
src/components/views/rooms/MemberList.js
src/components/views/rooms/RoomPreviewBar.js
src/components/views/settings/ChangeAvatar.js
src/components/views/settings/DevicesPanel.js
src/components/views/settings/Notifications.js
src/rageshake/rageshake.js
src/ratelimitedfunc.js
src/utils/DMRoomMap.js
src/utils/DecryptFile.js
src/utils/DirectoryUtils.js
src/utils/DMRoomMap.js
src/utils/FormattingUtils.js
src/utils/MultiInviter.js
src/utils/Receipt.js
src/Velociraptor.js
test/components/structures/MessagePanel-test.js
test/components/views/dialogs/InteractiveAuthDialog-test.js
test/mock-clock.js

View File

@ -81,8 +81,6 @@
@import "./views/dialogs/_RoomUpgradeWarningDialog.scss";
@import "./views/dialogs/_ServerOfflineDialog.scss";
@import "./views/dialogs/_SetEmailDialog.scss";
@import "./views/dialogs/_SetMxIdDialog.scss";
@import "./views/dialogs/_SetPasswordDialog.scss";
@import "./views/dialogs/_SettingsDialog.scss";
@import "./views/dialogs/_ShareDialog.scss";
@import "./views/dialogs/_SlashCommandHelpDialog.scss";

View File

@ -1,50 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
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.
*/
.mx_SetMxIdDialog .mx_Dialog_title {
padding-right: 40px;
}
.mx_SetMxIdDialog_input_group {
display: flex;
}
.mx_SetMxIdDialog_input {
border-radius: 3px;
border: 1px solid $input-border-color;
padding: 9px;
color: $primary-fg-color;
background-color: $primary-bg-color;
font-size: $font-15px;
width: 100%;
max-width: 280px;
}
.mx_SetMxIdDialog_input.error,
.mx_SetMxIdDialog_input.error:focus {
border: 1px solid $warning-color;
}
.mx_SetMxIdDialog_input_group .mx_Spinner {
height: 37px;
padding-left: 10px;
justify-content: flex-start;
}
.mx_SetMxIdDialog .success {
color: $accent-color;
}

View File

@ -1,34 +0,0 @@
/*
Copyright 2017 Vector Creations Ltd
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.
*/
.mx_SetPasswordDialog_change_password input {
border-radius: 3px;
border: 1px solid $input-border-color;
padding: 9px;
color: $primary-fg-color;
background-color: $primary-bg-color;
font-size: $font-15px;
max-width: 280px;
margin-bottom: 10px;
}
.mx_SetPasswordDialog_change_password_button {
margin-top: 68px;
}
.mx_SetPasswordDialog .mx_Dialog_content {
margin-bottom: 0px;
}

View File

@ -18,8 +18,10 @@ limitations under the License.
*/
import Matrix from 'matrix-js-sdk';
import { InvalidStoreError } from "matrix-js-sdk/src/errors";
import { MatrixClient } from "matrix-js-sdk/src/client";
import {MatrixClientPeg} from './MatrixClientPeg';
import {IMatrixClientCreds, MatrixClientPeg} from './MatrixClientPeg';
import EventIndexPeg from './indexing/EventIndexPeg';
import createMatrixClient from './utils/createMatrixClient';
import Analytics from './Analytics';
@ -47,44 +49,46 @@ import ThreepidInviteStore from "./stores/ThreepidInviteStore";
const HOMESERVER_URL_KEY = "mx_hs_url";
const ID_SERVER_URL_KEY = "mx_is_url";
interface ILoadSessionOpts {
enableGuest?: boolean;
guestHsUrl?: string;
guestIsUrl?: string;
ignoreGuest?: boolean;
defaultDeviceDisplayName?: string;
fragmentQueryParams?: Record<string, string>;
}
/**
* Called at startup, to attempt to build a logged-in Matrix session. It tries
* a number of things:
*
*
* 1. if we have a guest access token in the fragment query params, it uses
* that.
*
* 2. if an access token is stored in local storage (from a previous session),
* it uses that.
*
* 3. it attempts to auto-register as a guest user.
*
* If any of steps 1-4 are successful, it will call {_doSetLoggedIn}, which in
* turn will raise on_logged_in and will_start_client events.
*
* @param {object} opts
*
* @param {object} opts.fragmentQueryParams: string->string map of the
* @param {object} [opts]
* @param {object} [opts.fragmentQueryParams]: string->string map of the
* query-parameters extracted from the #-fragment of the starting URI.
*
* @param {boolean} opts.enableGuest: set to true to enable guest access tokens
* and auto-guest registrations.
*
* @params {string} opts.guestHsUrl: homeserver URL. Only used if enableGuest is
* true; defines the HS to register against.
*
* @params {string} opts.guestIsUrl: homeserver URL. Only used if enableGuest is
* true; defines the IS to use.
*
* @params {bool} opts.ignoreGuest: If the stored session is a guest account, ignore
* it and don't load it.
*
* @param {boolean} [opts.enableGuest]: set to true to enable guest access
* tokens and auto-guest registrations.
* @param {string} [opts.guestHsUrl]: homeserver URL. Only used if enableGuest
* is true; defines the HS to register against.
* @param {string} [opts.guestIsUrl]: homeserver URL. Only used if enableGuest
* is true; defines the IS to use.
* @param {bool} [opts.ignoreGuest]: If the stored session is a guest account,
* ignore it and don't load it.
* @param {string} [opts.defaultDeviceDisplayName]: Default display name to use
* when registering as a guest.
* @returns {Promise} a promise which resolves when the above process completes.
* Resolves to `true` if we ended up starting a session, or `false` if we
* failed.
*/
export async function loadSession(opts) {
export async function loadSession(opts: ILoadSessionOpts = {}): Promise<boolean> {
try {
let enableGuest = opts.enableGuest || false;
const guestHsUrl = opts.guestHsUrl;
@ -97,12 +101,13 @@ export async function loadSession(opts) {
enableGuest = false;
}
if (enableGuest &&
if (
enableGuest &&
fragmentQueryParams.guest_user_id &&
fragmentQueryParams.guest_access_token
) {
) {
console.log("Using guest access credentials");
return _doSetLoggedIn({
return doSetLoggedIn({
userId: fragmentQueryParams.guest_user_id,
accessToken: fragmentQueryParams.guest_access_token,
homeserverUrl: guestHsUrl,
@ -110,7 +115,7 @@ export async function loadSession(opts) {
guest: true,
}, true).then(() => true);
}
const success = await _restoreFromLocalStorage({
const success = await restoreFromLocalStorage({
ignoreGuest: Boolean(opts.ignoreGuest),
});
if (success) {
@ -118,7 +123,7 @@ export async function loadSession(opts) {
}
if (enableGuest) {
return _registerAsGuest(guestHsUrl, guestIsUrl, defaultDeviceDisplayName);
return registerAsGuest(guestHsUrl, guestIsUrl, defaultDeviceDisplayName);
}
// fall back to welcome screen
@ -129,7 +134,7 @@ export async function loadSession(opts) {
// need to show the general failure dialog. Instead, just go back to welcome.
return false;
}
return _handleLoadSessionFailure(e);
return handleLoadSessionFailure(e);
}
}
@ -139,7 +144,7 @@ export async function loadSession(opts) {
* is associated with them. The session is not loaded.
* @returns {String} The persisted session's owner, if an owner exists. Null otherwise.
*/
export function getStoredSessionOwner() {
export function getStoredSessionOwner(): string {
const {hsUrl, userId, accessToken} = getLocalStorageSessionVars();
return hsUrl && userId && accessToken ? userId : null;
}
@ -148,7 +153,7 @@ export function getStoredSessionOwner() {
* @returns {bool} True if the stored session is for a guest user or false if it is
* for a real user. If there is no stored session, return null.
*/
export function getStoredSessionIsGuest() {
export function getStoredSessionIsGuest(): boolean {
const sessVars = getLocalStorageSessionVars();
return sessVars.hsUrl && sessVars.userId && sessVars.accessToken ? sessVars.isGuest : null;
}
@ -163,7 +168,10 @@ export function getStoredSessionIsGuest() {
* @returns {Promise} promise which resolves to true if we completed the token
* login, else false
*/
export function attemptTokenLogin(queryParams, defaultDeviceDisplayName) {
export function attemptTokenLogin(
queryParams: Record<string, string>,
defaultDeviceDisplayName?: string,
): Promise<boolean> {
if (!queryParams.loginToken) {
return Promise.resolve(false);
}
@ -184,10 +192,10 @@ export function attemptTokenLogin(queryParams, defaultDeviceDisplayName) {
},
).then(function(creds) {
console.log("Logged in with token");
return _clearStorage().then(() => {
_persistCredentialsToLocalStorage(creds);
return clearStorage().then(() => {
persistCredentialsToLocalStorage(creds);
// remember that we just logged in
sessionStorage.setItem("mx_fresh_login", true);
sessionStorage.setItem("mx_fresh_login", String(true));
return true;
});
}).catch((err) => {
@ -197,8 +205,8 @@ export function attemptTokenLogin(queryParams, defaultDeviceDisplayName) {
});
}
export function handleInvalidStoreError(e) {
if (e.reason === Matrix.InvalidStoreError.TOGGLED_LAZY_LOADING) {
export function handleInvalidStoreError(e: InvalidStoreError): Promise<void> {
if (e.reason === InvalidStoreError.TOGGLED_LAZY_LOADING) {
return Promise.resolve().then(() => {
const lazyLoadEnabled = e.value;
if (lazyLoadEnabled) {
@ -231,7 +239,11 @@ export function handleInvalidStoreError(e) {
}
}
function _registerAsGuest(hsUrl, isUrl, defaultDeviceDisplayName) {
function registerAsGuest(
hsUrl: string,
isUrl: string,
defaultDeviceDisplayName: string,
): Promise<boolean> {
console.log(`Doing guest login on ${hsUrl}`);
// create a temporary MatrixClient to do the login
@ -245,7 +257,7 @@ function _registerAsGuest(hsUrl, isUrl, defaultDeviceDisplayName) {
},
}).then((creds) => {
console.log(`Registered as guest: ${creds.user_id}`);
return _doSetLoggedIn({
return doSetLoggedIn({
userId: creds.user_id,
deviceId: creds.device_id,
accessToken: creds.access_token,
@ -259,12 +271,21 @@ function _registerAsGuest(hsUrl, isUrl, defaultDeviceDisplayName) {
});
}
export interface ILocalStorageSession {
hsUrl: string;
isUrl: string;
accessToken: string;
userId: string;
deviceId: string;
isGuest: boolean;
}
/**
* Retrieves information about the stored session in localstorage. The session
* may not be valid, as it is not tested for consistency here.
* @returns {Object} Information about the session - see implementation for variables.
*/
export function getLocalStorageSessionVars() {
export function getLocalStorageSessionVars(): ILocalStorageSession {
const hsUrl = localStorage.getItem(HOMESERVER_URL_KEY);
const isUrl = localStorage.getItem(ID_SERVER_URL_KEY);
const accessToken = localStorage.getItem("mx_access_token");
@ -292,8 +313,8 @@ export function getLocalStorageSessionVars() {
// The plan is to gradually move the localStorage access done here into
// SessionStore to avoid bugs where the view becomes out-of-sync with
// localStorage (e.g. isGuest etc.)
async function _restoreFromLocalStorage(opts) {
const ignoreGuest = opts.ignoreGuest;
async function restoreFromLocalStorage(opts?: { ignoreGuest?: boolean }): Promise<boolean> {
const ignoreGuest = opts?.ignoreGuest;
if (!localStorage) {
return false;
@ -314,11 +335,11 @@ async function _restoreFromLocalStorage(opts) {
console.log("No pickle key available");
}
const freshLogin = sessionStorage.getItem("mx_fresh_login");
const freshLogin = sessionStorage.getItem("mx_fresh_login") === "true";
sessionStorage.removeItem("mx_fresh_login");
console.log(`Restoring session for ${userId}`);
await _doSetLoggedIn({
await doSetLoggedIn({
userId: userId,
deviceId: deviceId,
accessToken: accessToken,
@ -335,7 +356,7 @@ async function _restoreFromLocalStorage(opts) {
}
}
async function _handleLoadSessionFailure(e) {
async function handleLoadSessionFailure(e: Error): Promise<boolean> {
console.error("Unable to load session", e);
const SessionRestoreErrorDialog =
@ -348,7 +369,7 @@ async function _handleLoadSessionFailure(e) {
const [success] = await modal.finished;
if (success) {
// user clicked continue.
await _clearStorage();
await clearStorage();
return false;
}
@ -369,12 +390,12 @@ async function _handleLoadSessionFailure(e) {
*
* @returns {Promise} promise which resolves to the new MatrixClient once it has been started
*/
export async function setLoggedIn(credentials) {
export async function setLoggedIn(credentials: IMatrixClientCreds): Promise<MatrixClient> {
credentials.freshLogin = true;
stopMatrixClient();
const pickleKey = credentials.userId && credentials.deviceId
? await PlatformPeg.get().createPickleKey(credentials.userId, credentials.deviceId)
: null;
? await PlatformPeg.get().createPickleKey(credentials.userId, credentials.deviceId)
: null;
if (pickleKey) {
console.log("Created pickle key");
@ -382,7 +403,7 @@ export async function setLoggedIn(credentials) {
console.log("Pickle key not created");
}
return _doSetLoggedIn(Object.assign({}, credentials, {pickleKey}), true);
return doSetLoggedIn(Object.assign({}, credentials, {pickleKey}), true);
}
/**
@ -400,7 +421,7 @@ export async function setLoggedIn(credentials) {
*
* @returns {Promise} promise which resolves to the new MatrixClient once it has been started
*/
export function hydrateSession(credentials) {
export function hydrateSession(credentials: IMatrixClientCreds): Promise<MatrixClient> {
const oldUserId = MatrixClientPeg.get().getUserId();
const oldDeviceId = MatrixClientPeg.get().getDeviceId();
@ -413,7 +434,7 @@ export function hydrateSession(credentials) {
console.warn("Clearing all data: Old session belongs to a different user/session");
}
return _doSetLoggedIn(credentials, overwrite);
return doSetLoggedIn(credentials, overwrite);
}
/**
@ -425,7 +446,10 @@ export function hydrateSession(credentials) {
*
* @returns {Promise} promise which resolves to the new MatrixClient once it has been started
*/
async function _doSetLoggedIn(credentials, clearStorage) {
async function doSetLoggedIn(
credentials: IMatrixClientCreds,
clearStorageEnabled: boolean,
): Promise<MatrixClient> {
credentials.guest = Boolean(credentials.guest);
const softLogout = isSoftLogout();
@ -448,8 +472,8 @@ async function _doSetLoggedIn(credentials, clearStorage) {
// (dis.dispatch uses `setTimeout`, which does not guarantee ordering.)
dis.dispatch({action: 'on_logging_in'}, true);
if (clearStorage) {
await _clearStorage();
if (clearStorageEnabled) {
await clearStorage();
}
const results = await StorageManager.checkConsistency();
@ -457,9 +481,9 @@ async function _doSetLoggedIn(credentials, clearStorage) {
// crypto store, we'll be generally confused when handling encrypted data.
// Show a modal recommending a full reset of storage.
if (results.dataInLocalStorage && results.cryptoInited && !results.dataInCryptoStore) {
const signOut = await _showStorageEvictedDialog();
const signOut = await showStorageEvictedDialog();
if (signOut) {
await _clearStorage();
await clearStorage();
// This error feels a bit clunky, but we want to make sure we don't go any
// further and instead head back to sign in.
throw new AbortLoginAndRebuildStorage(
@ -487,20 +511,9 @@ async function _doSetLoggedIn(credentials, clearStorage) {
if (localStorage) {
try {
_persistCredentialsToLocalStorage(credentials);
persistCredentialsToLocalStorage(credentials);
// make sure we don't think that it's a fresh login any more
sessionStorage.removeItem("mx_fresh_login");
// The user registered as a PWLU (PassWord-Less User), the generated password
// is cached here such that the user can change it at a later time.
if (credentials.password) {
// Update SessionStore
dis.dispatch({
action: 'cached_password',
cachedPassword: credentials.password,
});
}
} catch (e) {
console.warn("Error using local storage: can't persist session!", e);
}
@ -514,7 +527,7 @@ async function _doSetLoggedIn(credentials, clearStorage) {
return client;
}
function _showStorageEvictedDialog() {
function showStorageEvictedDialog(): Promise<boolean> {
const StorageEvictedDialog = sdk.getComponent('views.dialogs.StorageEvictedDialog');
return new Promise(resolve => {
Modal.createTrackedDialog('Storage evicted', '', StorageEvictedDialog, {
@ -527,7 +540,7 @@ function _showStorageEvictedDialog() {
// `instanceof`. Babel 7 supports this natively in their class handling.
class AbortLoginAndRebuildStorage extends Error { }
function _persistCredentialsToLocalStorage(credentials) {
function persistCredentialsToLocalStorage(credentials: IMatrixClientCreds): void {
localStorage.setItem(HOMESERVER_URL_KEY, credentials.homeserverUrl);
if (credentials.identityServerUrl) {
localStorage.setItem(ID_SERVER_URL_KEY, credentials.identityServerUrl);
@ -537,7 +550,7 @@ function _persistCredentialsToLocalStorage(credentials) {
localStorage.setItem("mx_is_guest", JSON.stringify(credentials.guest));
if (credentials.pickleKey) {
localStorage.setItem("mx_has_pickle_key", true);
localStorage.setItem("mx_has_pickle_key", String(true));
} else {
if (localStorage.getItem("mx_has_pickle_key")) {
console.error("Expected a pickle key, but none provided. Encryption may not work.");
@ -561,7 +574,7 @@ let _isLoggingOut = false;
/**
* Logs the current session out and transitions to the logged-out state
*/
export function logout() {
export function logout(): void {
if (!MatrixClientPeg.get()) return;
if (MatrixClientPeg.get().isGuest()) {
@ -590,7 +603,7 @@ export function logout() {
);
}
export function softLogout() {
export function softLogout(): void {
if (!MatrixClientPeg.get()) return;
// Track that we've detected and trapped a soft logout. This helps prevent other
@ -611,11 +624,11 @@ export function softLogout() {
// DO NOT CALL LOGOUT. A soft logout preserves data, logout does not.
}
export function isSoftLogout() {
export function isSoftLogout(): boolean {
return localStorage.getItem("mx_soft_logout") === "true";
}
export function isLoggingOut() {
export function isLoggingOut(): boolean {
return _isLoggingOut;
}
@ -625,7 +638,7 @@ export function isLoggingOut() {
* @param {boolean} startSyncing True (default) to actually start
* syncing the client.
*/
async function startMatrixClient(startSyncing=true) {
async function startMatrixClient(startSyncing = true): Promise<void> {
console.log(`Lifecycle: Starting MatrixClient`);
// dispatch this before starting the matrix client: it's used
@ -684,21 +697,21 @@ async function startMatrixClient(startSyncing=true) {
* Stops a running client and all related services, and clears persistent
* storage. Used after a session has been logged out.
*/
export async function onLoggedOut() {
export async function onLoggedOut(): Promise<void> {
_isLoggingOut = false;
// Ensure that we dispatch a view change **before** stopping the client so
// so that React components unmount first. This avoids React soft crashes
// that can occur when components try to use a null client.
dis.dispatch({action: 'on_logged_out'}, true);
stopMatrixClient();
await _clearStorage({deleteEverything: true});
await clearStorage({deleteEverything: true});
}
/**
* @param {object} opts Options for how to clear storage.
* @returns {Promise} promise which resolves once the stores have been cleared
*/
async function _clearStorage(opts: {deleteEverything: boolean}) {
async function clearStorage(opts?: { deleteEverything?: boolean }): Promise<void> {
Analytics.disable();
if (window.localStorage) {
@ -736,7 +749,7 @@ async function _clearStorage(opts: {deleteEverything: boolean}) {
* @param {boolean} unsetClient True (default) to abandon the client
* on MatrixClientPeg after stopping.
*/
export function stopMatrixClient(unsetClient=true) {
export function stopMatrixClient(unsetClient = true): void {
Notifier.stop();
UserActivity.sharedInstance().stop();
TypingStore.sharedInstance().reset();

View File

@ -19,34 +19,70 @@ limitations under the License.
*/
import Matrix from "matrix-js-sdk";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { IMatrixClientCreds } from "./MatrixClientPeg";
interface ILoginOptions {
defaultDeviceDisplayName?: string;
}
// TODO: Move this to JS SDK
interface ILoginFlow {
type: string;
}
// TODO: Move this to JS SDK
/* eslint-disable camelcase */
interface ILoginParams {
identifier?: string;
password?: string;
token?: string;
device_id?: string;
initial_device_display_name?: string;
}
/* eslint-enable camelcase */
export default class Login {
constructor(hsUrl, isUrl, fallbackHsUrl, opts) {
this._hsUrl = hsUrl;
this._isUrl = isUrl;
this._fallbackHsUrl = fallbackHsUrl;
this._currentFlowIndex = 0;
this._flows = [];
this._defaultDeviceDisplayName = opts.defaultDeviceDisplayName;
this._tempClient = null; // memoize
private hsUrl: string;
private isUrl: string;
private fallbackHsUrl: string;
private currentFlowIndex: number;
// TODO: Flows need a type in JS SDK
private flows: Array<ILoginFlow>;
private defaultDeviceDisplayName: string;
private tempClient: MatrixClient;
constructor(
hsUrl: string,
isUrl: string,
fallbackHsUrl?: string,
opts?: ILoginOptions,
) {
this.hsUrl = hsUrl;
this.isUrl = isUrl;
this.fallbackHsUrl = fallbackHsUrl;
this.currentFlowIndex = 0;
this.flows = [];
this.defaultDeviceDisplayName = opts.defaultDeviceDisplayName;
this.tempClient = null; // memoize
}
getHomeserverUrl() {
return this._hsUrl;
public getHomeserverUrl(): string {
return this.hsUrl;
}
getIdentityServerUrl() {
return this._isUrl;
public getIdentityServerUrl(): string {
return this.isUrl;
}
setHomeserverUrl(hsUrl) {
this._tempClient = null; // clear memoization
this._hsUrl = hsUrl;
public setHomeserverUrl(hsUrl: string): void {
this.tempClient = null; // clear memoization
this.hsUrl = hsUrl;
}
setIdentityServerUrl(isUrl) {
this._tempClient = null; // clear memoization
this._isUrl = isUrl;
public setIdentityServerUrl(isUrl: string): void {
this.tempClient = null; // clear memoization
this.isUrl = isUrl;
}
/**
@ -54,40 +90,41 @@ export default class Login {
* requests.
* @returns {MatrixClient}
*/
createTemporaryClient() {
if (this._tempClient) return this._tempClient; // use memoization
return this._tempClient = Matrix.createClient({
baseUrl: this._hsUrl,
idBaseUrl: this._isUrl,
public createTemporaryClient(): MatrixClient {
if (this.tempClient) return this.tempClient; // use memoization
return this.tempClient = Matrix.createClient({
baseUrl: this.hsUrl,
idBaseUrl: this.isUrl,
});
}
getFlows() {
const self = this;
public async getFlows(): Promise<Array<ILoginFlow>> {
const client = this.createTemporaryClient();
return client.loginFlows().then(function(result) {
self._flows = result.flows;
self._currentFlowIndex = 0;
// technically the UI should display options for all flows for the
// user to then choose one, so return all the flows here.
return self._flows;
});
const { flows } = await client.loginFlows();
this.flows = flows;
this.currentFlowIndex = 0;
// technically the UI should display options for all flows for the
// user to then choose one, so return all the flows here.
return this.flows;
}
chooseFlow(flowIndex) {
this._currentFlowIndex = flowIndex;
public chooseFlow(flowIndex): void {
this.currentFlowIndex = flowIndex;
}
getCurrentFlowStep() {
public getCurrentFlowStep(): string {
// technically the flow can have multiple steps, but no one does this
// for login so we can ignore it.
const flowStep = this._flows[this._currentFlowIndex];
const flowStep = this.flows[this.currentFlowIndex];
return flowStep ? flowStep.type : null;
}
loginViaPassword(username, phoneCountry, phoneNumber, pass) {
const self = this;
public loginViaPassword(
username: string,
phoneCountry: string,
phoneNumber: string,
password: string,
): Promise<IMatrixClientCreds> {
const isEmail = username.indexOf("@") > 0;
let identifier;
@ -113,14 +150,14 @@ export default class Login {
}
const loginParams = {
password: pass,
identifier: identifier,
initial_device_display_name: this._defaultDeviceDisplayName,
password,
identifier,
initial_device_display_name: this.defaultDeviceDisplayName,
};
const tryFallbackHs = (originalError) => {
return sendLoginRequest(
self._fallbackHsUrl, this._isUrl, 'm.login.password', loginParams,
this.fallbackHsUrl, this.isUrl, 'm.login.password', loginParams,
).catch((fallbackError) => {
console.log("fallback HS login failed", fallbackError);
// throw the original error
@ -130,11 +167,11 @@ export default class Login {
let originalLoginError = null;
return sendLoginRequest(
self._hsUrl, self._isUrl, 'm.login.password', loginParams,
this.hsUrl, this.isUrl, 'm.login.password', loginParams,
).catch((error) => {
originalLoginError = error;
if (error.httpStatus === 403) {
if (self._fallbackHsUrl) {
if (this.fallbackHsUrl) {
return tryFallbackHs(originalLoginError);
}
}
@ -154,11 +191,16 @@ export default class Login {
* @param {string} hsUrl the base url of the Homeserver used to log in.
* @param {string} isUrl the base url of the default identity server
* @param {string} loginType the type of login to do
* @param {object} loginParams the parameters for the login
* @param {ILoginParams} loginParams the parameters for the login
*
* @returns {MatrixClientCreds}
*/
export async function sendLoginRequest(hsUrl, isUrl, loginType, loginParams) {
export async function sendLoginRequest(
hsUrl: string,
isUrl: string,
loginType: string,
loginParams: ILoginParams,
): Promise<IMatrixClientCreds> {
const client = Matrix.createClient({
baseUrl: hsUrl,
idBaseUrl: isUrl,

View File

@ -38,9 +38,9 @@ export interface IMatrixClientCreds {
homeserverUrl: string;
identityServerUrl: string;
userId: string;
deviceId: string;
deviceId?: string;
accessToken: string;
guest: boolean;
guest?: boolean;
pickleKey?: string;
freshLogin?: boolean;
}

View File

@ -24,7 +24,6 @@ import dis from './dispatcher/dispatcher';
import * as sdk from './index';
import Modal from './Modal';
import { _t } from './languageHandler';
// import {MatrixClientPeg} from './MatrixClientPeg';
// Regex for what a "safe" or "Matrix-looking" localpart would be.
// TODO: Update as needed for https://github.com/matrix-org/matrix-doc/issues/1514
@ -44,70 +43,27 @@ export const SAFE_LOCALPART_REGEX = /^[a-z0-9=_\-./]+$/;
*/
export async function startAnyRegistrationFlow(options) {
if (options === undefined) options = {};
// look for an ILAG compatible flow. We define this as one
// which has only dummy or recaptcha flows. In practice it
// would support any stage InteractiveAuth supports, just not
// ones like email & msisdn which require the user to supply
// the relevant details in advance. We err on the side of
// caution though.
// XXX: ILAG is disabled for now,
// see https://github.com/vector-im/element-web/issues/8222
// const flows = await _getRegistrationFlows();
// const hasIlagFlow = flows.some((flow) => {
// return flow.stages.every((stage) => {
// return ['m.login.dummy', 'm.login.recaptcha', 'm.login.terms'].includes(stage);
// });
// });
// if (hasIlagFlow) {
// dis.dispatch({
// action: 'view_set_mxid',
// go_home_on_cancel: options.go_home_on_cancel,
// });
//} else {
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
const modal = Modal.createTrackedDialog('Registration required', '', QuestionDialog, {
hasCancelButton: true,
quitOnly: true,
title: _t("Sign In or Create Account"),
description: _t("Use your account or create a new one to continue."),
button: _t("Create Account"),
extraButtons: [
<button key="start_login" onClick={() => {
modal.close();
dis.dispatch({action: 'start_login', screenAfterLogin: options.screen_after});
}}>{ _t('Sign In') }</button>,
],
onFinished: (proceed) => {
if (proceed) {
dis.dispatch({action: 'start_registration', screenAfterLogin: options.screen_after});
} else if (options.go_home_on_cancel) {
dis.dispatch({action: 'view_home_page'});
} else if (options.go_welcome_on_cancel) {
dis.dispatch({action: 'view_welcome_page'});
}
},
});
//}
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
const modal = Modal.createTrackedDialog('Registration required', '', QuestionDialog, {
hasCancelButton: true,
quitOnly: true,
title: _t("Sign In or Create Account"),
description: _t("Use your account or create a new one to continue."),
button: _t("Create Account"),
extraButtons: [
<button key="start_login" onClick={() => {
modal.close();
dis.dispatch({action: 'start_login', screenAfterLogin: options.screen_after});
}}>{ _t('Sign In') }</button>,
],
onFinished: (proceed) => {
if (proceed) {
dis.dispatch({action: 'start_registration', screenAfterLogin: options.screen_after});
} else if (options.go_home_on_cancel) {
dis.dispatch({action: 'view_home_page'});
} else if (options.go_welcome_on_cancel) {
dis.dispatch({action: 'view_welcome_page'});
}
},
});
}
// async function _getRegistrationFlows() {
// try {
// await MatrixClientPeg.get().register(
// null,
// null,
// undefined,
// {},
// {},
// );
// console.log("Register request succeeded when it should have returned 401!");
// } catch (e) {
// if (e.httpStatus === 401) {
// return e.data.flows;
// }
// throw e;
// }
// throw new Error("Register request succeeded when it should have returned 401!");
// }

View File

@ -27,7 +27,6 @@ import CallMediaHandler from '../../CallMediaHandler';
import { fixupColorFonts } from '../../utils/FontManager';
import * as sdk from '../../index';
import dis from '../../dispatcher/dispatcher';
import sessionStore from '../../stores/SessionStore';
import {MatrixClientPeg, IMatrixClientCreds} from '../../MatrixClientPeg';
import SettingsStore from "../../settings/SettingsStore";
@ -41,10 +40,6 @@ import HomePage from "./HomePage";
import ResizeNotifier from "../../utils/ResizeNotifier";
import PlatformPeg from "../../PlatformPeg";
import { DefaultTagID } from "../../stores/room-list/models";
import {
showToast as showSetPasswordToast,
hideToast as hideSetPasswordToast,
} from "../../toasts/SetPasswordToast";
import {
showToast as showServerLimitToast,
hideToast as hideServerLimitToast,
@ -149,8 +144,6 @@ class LoggedInView extends React.Component<IProps, IState> {
protected readonly _matrixClient: MatrixClient;
protected readonly _roomView: React.RefObject<any>;
protected readonly _resizeContainer: React.RefObject<ResizeHandle>;
protected readonly _sessionStore: sessionStore;
protected readonly _sessionStoreToken: { remove: () => void };
protected readonly _compactLayoutWatcherRef: string;
protected resizer: Resizer;
@ -171,12 +164,6 @@ class LoggedInView extends React.Component<IProps, IState> {
document.addEventListener('keydown', this._onNativeKeyDown, false);
this._sessionStore = sessionStore;
this._sessionStoreToken = this._sessionStore.addListener(
this._setStateFromSessionStore,
);
this._setStateFromSessionStore();
this._updateServerNoticeEvents();
this._matrixClient.on("accountData", this.onAccountData);
@ -205,9 +192,6 @@ class LoggedInView extends React.Component<IProps, IState> {
this._matrixClient.removeListener("sync", this.onSync);
this._matrixClient.removeListener("RoomState.events", this.onRoomStateEvents);
SettingsStore.unwatchSetting(this._compactLayoutWatcherRef);
if (this._sessionStoreToken) {
this._sessionStoreToken.remove();
}
this.resizer.detach();
}
@ -228,14 +212,6 @@ class LoggedInView extends React.Component<IProps, IState> {
return this._roomView.current.canResetTimeline();
};
_setStateFromSessionStore = () => {
if (this._sessionStore.getCachedPassword()) {
showSetPasswordToast();
} else {
hideSetPasswordToast();
}
};
_createResizer() {
const classNames = {
handle: "mx_ResizeHandle",

View File

@ -30,7 +30,7 @@ import 'what-input';
import Analytics from "../../Analytics";
import { DecryptionFailureTracker } from "../../DecryptionFailureTracker";
import { MatrixClientPeg } from "../../MatrixClientPeg";
import { MatrixClientPeg, IMatrixClientCreds } from "../../MatrixClientPeg";
import PlatformPeg from "../../PlatformPeg";
import SdkConfig from "../../SdkConfig";
import * as RoomListSorter from "../../RoomListSorter";
@ -290,7 +290,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
// When the session loads it'll be detected as soft logged out and a dispatch
// will be sent out to say that, triggering this MatrixChat to show the soft
// logout page.
Lifecycle.loadSession({});
Lifecycle.loadSession();
}
this.accountPassword = null;
@ -670,9 +670,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
case 'view_home_page':
this.viewHome();
break;
case 'view_set_mxid':
this.setMxId(payload);
break;
case 'view_start_chat_or_reuse':
this.chatCreateOrReuse(payload.user_id);
break;
@ -985,36 +982,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
});
}
private setMxId(payload) {
const SetMxIdDialog = sdk.getComponent('views.dialogs.SetMxIdDialog');
const close = Modal.createTrackedDialog('Set MXID', '', SetMxIdDialog, {
homeserverUrl: MatrixClientPeg.get().getHomeserverUrl(),
onFinished: (submitted, credentials) => {
if (!submitted) {
dis.dispatch({
action: 'cancel_after_sync_prepared',
});
if (payload.go_home_on_cancel) {
dis.dispatch({
action: 'view_home_page',
});
}
return;
}
MatrixClientPeg.setJustRegisteredUserId(credentials.user_id);
this.onRegistered(credentials);
},
onDifferentServerClicked: (ev) => {
dis.dispatch({action: 'start_registration'});
close();
},
onLoginClick: (ev) => {
dis.dispatch({action: 'start_login'});
close();
},
}).close;
}
private async createRoom(defaultPublic = false) {
const communityId = CommunityPrototypeStore.instance.getSelectedCommunityId();
if (communityId) {
@ -1814,12 +1781,12 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
this.showScreen("forgot_password");
};
onRegisterFlowComplete = (credentials: object, password: string) => {
onRegisterFlowComplete = (credentials: IMatrixClientCreds, password: string) => {
return this.onUserCompletedLoginFlow(credentials, password);
};
// returns a promise which resolves to the new MatrixClient
onRegistered(credentials: object) {
onRegistered(credentials: IMatrixClientCreds) {
return Lifecycle.setLoggedIn(credentials);
}
@ -1905,7 +1872,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
* Note: SSO users (and any others using token login) currently do not pass through
* this, as they instead jump straight into the app after `attemptTokenLogin`.
*/
onUserCompletedLoginFlow = async (credentials: object, password: string) => {
onUserCompletedLoginFlow = async (credentials: IMatrixClientCreds, password: string) => {
this.accountPassword = password;
// self-destruct the password after 5mins
if (this.accountPasswordTimer !== null) clearTimeout(this.accountPasswordTimer);

View File

@ -1090,42 +1090,7 @@ export default class RoomView extends React.Component<IProps, IState> {
room_id: this.getRoomId(),
},
});
// Don't peek whilst registering otherwise getPendingEventList complains
// Do this by indicating our intention to join
// XXX: ILAG is disabled for now,
// see https://github.com/vector-im/element-web/issues/8222
dis.dispatch({action: 'require_registration'});
// dis.dispatch({
// action: 'will_join',
// });
// const SetMxIdDialog = sdk.getComponent('views.dialogs.SetMxIdDialog');
// const close = Modal.createTrackedDialog('Set MXID', '', SetMxIdDialog, {
// homeserverUrl: cli.getHomeserverUrl(),
// onFinished: (submitted, credentials) => {
// if (submitted) {
// this.props.onRegistered(credentials);
// } else {
// dis.dispatch({
// action: 'cancel_after_sync_prepared',
// });
// dis.dispatch({
// action: 'cancel_join',
// });
// }
// },
// onDifferentServerClicked: (ev) => {
// dis.dispatch({action: 'start_registration'});
// close();
// },
// onLoginClick: (ev) => {
// dis.dispatch({action: 'start_login'});
// close();
// },
// }).close;
// return;
} else {
Promise.resolve().then(() => {
const signUrl = this.props.threepidInvite?.signUrl;

View File

@ -1,304 +0,0 @@
/*
Copyright 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
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 React, {createRef} from 'react';
import PropTypes from 'prop-types';
import * as sdk from '../../../index';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import classnames from 'classnames';
import { Key } from '../../../Keyboard';
import { _t } from '../../../languageHandler';
import { SAFE_LOCALPART_REGEX } from '../../../Registration';
// The amount of time to wait for further changes to the input username before
// sending a request to the server
const USERNAME_CHECK_DEBOUNCE_MS = 250;
/*
* Prompt the user to set a display name.
*
* On success, `onFinished(true, newDisplayName)` is called.
*/
export default class SetMxIdDialog extends React.Component {
static propTypes = {
onFinished: PropTypes.func.isRequired,
// Called when the user requests to register with a different homeserver
onDifferentServerClicked: PropTypes.func.isRequired,
// Called if the user wants to switch to login instead
onLoginClick: PropTypes.func.isRequired,
};
constructor(props) {
super(props);
this._input_value = createRef();
this._uiAuth = createRef();
this.state = {
// The entered username
username: '',
// Indicate ongoing work on the username
usernameBusy: false,
// Indicate error with username
usernameError: '',
// Assume the homeserver supports username checking until "M_UNRECOGNIZED"
usernameCheckSupport: true,
// Whether the auth UI is currently being used
doingUIAuth: false,
// Indicate error with auth
authError: '',
};
}
componentDidMount() {
this._input_value.current.select();
this._matrixClient = MatrixClientPeg.get();
}
onValueChange = ev => {
this.setState({
username: ev.target.value,
usernameBusy: true,
usernameError: '',
}, () => {
if (!this.state.username || !this.state.usernameCheckSupport) {
this.setState({
usernameBusy: false,
});
return;
}
// Debounce the username check to limit number of requests sent
if (this._usernameCheckTimeout) {
clearTimeout(this._usernameCheckTimeout);
}
this._usernameCheckTimeout = setTimeout(() => {
this._doUsernameCheck().finally(() => {
this.setState({
usernameBusy: false,
});
});
}, USERNAME_CHECK_DEBOUNCE_MS);
});
};
onKeyUp = ev => {
if (ev.key === Key.ENTER) {
this.onSubmit();
}
};
onSubmit = ev => {
if (this._uiAuth.current) {
this._uiAuth.current.tryContinue();
}
this.setState({
doingUIAuth: true,
});
};
_doUsernameCheck() {
// We do a quick check ahead of the username availability API to ensure the
// user ID roughly looks okay from a Matrix perspective.
if (!SAFE_LOCALPART_REGEX.test(this.state.username)) {
this.setState({
usernameError: _t("A username can only contain lower case letters, numbers and '=_-./'"),
});
return Promise.reject();
}
// Check if username is available
return this._matrixClient.isUsernameAvailable(this.state.username).then(
(isAvailable) => {
if (isAvailable) {
this.setState({usernameError: ''});
}
},
(err) => {
// Indicate whether the homeserver supports username checking
const newState = {
usernameCheckSupport: err.errcode !== "M_UNRECOGNIZED",
};
console.error('Error whilst checking username availability: ', err);
switch (err.errcode) {
case "M_USER_IN_USE":
newState.usernameError = _t('Username not available');
break;
case "M_INVALID_USERNAME":
newState.usernameError = _t(
'Username invalid: %(errMessage)s',
{ errMessage: err.message},
);
break;
case "M_UNRECOGNIZED":
// This homeserver doesn't support username checking, assume it's
// fine and rely on the error appearing in registration step.
newState.usernameError = '';
break;
case undefined:
newState.usernameError = _t('Something went wrong!');
break;
default:
newState.usernameError = _t(
'An error occurred: %(error_string)s',
{ error_string: err.message },
);
break;
}
this.setState(newState);
},
);
}
_generatePassword() {
return Math.random().toString(36).slice(2);
}
_makeRegisterRequest = auth => {
// Not upgrading - changing mxids
const guestAccessToken = null;
if (!this._generatedPassword) {
this._generatedPassword = this._generatePassword();
}
return this._matrixClient.register(
this.state.username,
this._generatedPassword,
undefined, // session id: included in the auth dict already
auth,
{},
guestAccessToken,
);
};
_onUIAuthFinished = (success, response) => {
this.setState({
doingUIAuth: false,
});
if (!success) {
this.setState({ authError: response.message });
return;
}
this.props.onFinished(true, {
userId: response.user_id,
deviceId: response.device_id,
homeserverUrl: this._matrixClient.getHomeserverUrl(),
identityServerUrl: this._matrixClient.getIdentityServerUrl(),
accessToken: response.access_token,
password: this._generatedPassword,
});
};
render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const InteractiveAuth = sdk.getComponent('structures.InteractiveAuth');
let auth;
if (this.state.doingUIAuth) {
auth = <InteractiveAuth
matrixClient={this._matrixClient}
makeRequest={this._makeRegisterRequest}
onAuthFinished={this._onUIAuthFinished}
inputs={{}}
poll={true}
ref={this._uiAuth}
continueIsManaged={true}
/>;
}
const inputClasses = classnames({
"mx_SetMxIdDialog_input": true,
"error": Boolean(this.state.usernameError),
});
let usernameIndicator = null;
if (this.state.usernameBusy) {
usernameIndicator = <div>{_t("Checking...")}</div>;
} else {
const usernameAvailable = this.state.username &&
this.state.usernameCheckSupport && !this.state.usernameError;
const usernameIndicatorClasses = classnames({
"error": Boolean(this.state.usernameError),
"success": usernameAvailable,
});
usernameIndicator = <div className={usernameIndicatorClasses} role="alert">
{ usernameAvailable ? _t('Username available') : this.state.usernameError }
</div>;
}
let authErrorIndicator = null;
if (this.state.authError) {
authErrorIndicator = <div className="error" role="alert">
{ this.state.authError }
</div>;
}
const canContinue = this.state.username &&
!this.state.usernameError &&
!this.state.usernameBusy;
return (
<BaseDialog className="mx_SetMxIdDialog"
onFinished={this.props.onFinished}
title={_t('To get started, please pick a username!')}
contentId='mx_Dialog_content'
>
<div className="mx_Dialog_content" id='mx_Dialog_content'>
<div className="mx_SetMxIdDialog_input_group">
<input type="text" ref={this._input_value} value={this.state.username}
autoFocus={true}
onChange={this.onValueChange}
onKeyUp={this.onKeyUp}
size="30"
className={inputClasses}
/>
</div>
{ usernameIndicator }
<p>
{ _t(
'This will be your account name on the <span></span> ' +
'homeserver, or you can pick a <a>different server</a>.',
{},
{
'span': <span>{ this.props.homeserverUrl }</span>,
'a': (sub) => <a href="#" onClick={this.props.onDifferentServerClicked}>{ sub }</a>,
},
) }
</p>
<p>
{ _t(
'If you already have a Matrix account you can <a>log in</a> instead.',
{},
{ 'a': (sub) => <a href="#" onClick={this.props.onLoginClick}>{ sub }</a> },
) }
</p>
{ auth }
{ authErrorIndicator }
</div>
<div className="mx_Dialog_buttons">
<input className="mx_Dialog_primary"
type="submit"
value={_t("Continue")}
onClick={this.onSubmit}
disabled={!canContinue}
/>
</div>
</BaseDialog>
);
}
}

View File

@ -1,130 +0,0 @@
/*
Copyright 2017 Vector Creations Ltd
Copyright 2018 New Vector Ltd
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
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 React from 'react';
import PropTypes from 'prop-types';
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
import Modal from '../../../Modal';
const WarmFuzzy = function(props) {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
let title = _t('You have successfully set a password!');
if (props.didSetEmail) {
title = _t('You have successfully set a password and an email address!');
}
const advice = _t('You can now return to your account after signing out, and sign in on other devices.');
let extraAdvice = null;
if (!props.didSetEmail) {
extraAdvice = _t('Remember, you can always set an email address in user settings if you change your mind.');
}
return <BaseDialog className="mx_SetPasswordDialog"
onFinished={props.onFinished}
title={ title }
>
<div className="mx_Dialog_content">
<p>
{ advice }
</p>
<p>
{ extraAdvice }
</p>
</div>
<div className="mx_Dialog_buttons">
<button
className="mx_Dialog_primary"
autoFocus={true}
onClick={props.onFinished}>
{ _t('Continue') }
</button>
</div>
</BaseDialog>;
};
/**
* Prompt the user to set a password
*
* On success, `onFinished()` when finished
*/
export default class SetPasswordDialog extends React.Component {
static propTypes = {
onFinished: PropTypes.func.isRequired,
};
state = {
error: null,
};
_onPasswordChanged = res => {
Modal.createDialog(WarmFuzzy, {
didSetEmail: res.didSetEmail,
onFinished: () => {
this.props.onFinished();
},
});
};
_onPasswordChangeError = err => {
let errMsg = err.error || "";
if (err.httpStatus === 403) {
errMsg = _t('Failed to change password. Is your password correct?');
} else if (err.httpStatus) {
errMsg += ' ' + _t(
'(HTTP status %(httpStatus)s)',
{ httpStatus: err.httpStatus },
);
}
this.setState({
error: errMsg,
});
};
render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const ChangePassword = sdk.getComponent('views.settings.ChangePassword');
return (
<BaseDialog className="mx_SetPasswordDialog"
onFinished={this.props.onFinished}
title={ _t('Please set a password!') }
>
<div className="mx_Dialog_content">
<p>
{ _t('This will allow you to return to your account after signing out, and sign in on other sessions.') }
</p>
<ChangePassword
className="mx_SetPasswordDialog_change_password"
rowClassName=""
buttonClassNames="mx_Dialog_primary mx_SetPasswordDialog_change_password_button"
buttonKind="primary"
confirm={false}
autoFocusNewPasswordInput={true}
shouldAskForEmail={true}
onError={this._onPasswordChangeError}
onFinished={this._onPasswordChanged}
buttonLabel={_t("Set Password")}
/>
<div className="error">
{ this.state.error }
</div>
</div>
</BaseDialog>
);
}
}

View File

@ -19,14 +19,12 @@ import Field from "../elements/Field";
import React from 'react';
import PropTypes from 'prop-types';
import {MatrixClientPeg} from "../../../MatrixClientPeg";
import dis from "../../../dispatcher/dispatcher";
import AccessibleButton from '../elements/AccessibleButton';
import Spinner from '../elements/Spinner';
import { _t } from '../../../languageHandler';
import * as sdk from "../../../index";
import Modal from "../../../Modal";
import sessionStore from '../../../stores/SessionStore';
export default class ChangePassword extends React.Component {
static propTypes = {
onFinished: PropTypes.func,
@ -66,33 +64,11 @@ export default class ChangePassword extends React.Component {
state = {
phase: ChangePassword.Phases.Edit,
cachedPassword: null,
oldPassword: "",
newPassword: "",
newPasswordConfirm: "",
};
componentDidMount() {
this._sessionStore = sessionStore;
this._sessionStoreToken = this._sessionStore.addListener(
this._setStateFromSessionStore,
);
this._setStateFromSessionStore();
}
componentWillUnmount() {
if (this._sessionStoreToken) {
this._sessionStoreToken.remove();
}
}
_setStateFromSessionStore = () => {
this.setState({
cachedPassword: this._sessionStore.getCachedPassword(),
});
};
changePassword(oldPassword, newPassword) {
const cli = MatrixClientPeg.get();
@ -119,8 +95,11 @@ export default class ChangePassword extends React.Component {
</div>,
button: _t("Continue"),
extraButtons: [
<button className="mx_Dialog_primary"
onClick={this._onExportE2eKeysClicked}>
<button
key="exportRoomKeys"
className="mx_Dialog_primary"
onClick={this._onExportE2eKeysClicked}
>
{ _t('Export E2E room keys') }
</button>,
],
@ -150,9 +129,6 @@ export default class ChangePassword extends React.Component {
});
cli.setPassword(authDict, newPassword).then(() => {
// Notify SessionStore that the user's password was changed
dis.dispatch({action: 'password_changed'});
if (this.props.shouldAskForEmail) {
return this._optionallySetEmail().then((confirmed) => {
this.props.onFinished({
@ -212,7 +188,7 @@ export default class ChangePassword extends React.Component {
onClickChange = (ev) => {
ev.preventDefault();
const oldPassword = this.state.cachedPassword || this.state.oldPassword;
const oldPassword = this.state.oldPassword;
const newPassword = this.state.newPassword;
const confirmPassword = this.state.newPasswordConfirm;
const err = this.props.onCheckPassword(
@ -231,31 +207,22 @@ export default class ChangePassword extends React.Component {
const rowClassName = this.props.rowClassName;
const buttonClassName = this.props.buttonClassName;
let currentPassword = null;
if (!this.state.cachedPassword) {
currentPassword = (
<div className={rowClassName}>
<Field
type="password"
label={_t('Current password')}
value={this.state.oldPassword}
onChange={this.onChangeOldPassword}
/>
</div>
);
}
switch (this.state.phase) {
case ChangePassword.Phases.Edit:
const passwordLabel = this.state.cachedPassword ?
_t('Password') : _t('New Password');
return (
<form className={this.props.className} onSubmit={this.onClickChange}>
{ currentPassword }
<div className={rowClassName}>
<Field
type="password"
label={passwordLabel}
label={_t('Current password')}
value={this.state.oldPassword}
onChange={this.onChangeOldPassword}
/>
</div>
<div className={rowClassName}>
<Field
type="password"
label={_t('New Password')}
value={this.state.newPassword}
autoFocus={this.props.autoFocusNewPasswordInput}
onChange={this.onChangeNewPassword}
@ -277,10 +244,9 @@ export default class ChangePassword extends React.Component {
</form>
);
case ChangePassword.Phases.Uploading:
var Loader = sdk.getComponent("elements.Spinner");
return (
<div className="mx_Dialog_content">
<Loader />
<Spinner />
</div>
);
}

View File

@ -401,9 +401,6 @@
"Contact your <a>server admin</a>.": "Contact your <a>server admin</a>.",
"Warning": "Warning",
"Ok": "Ok",
"Set password": "Set password",
"To return to your account in future you need to set a password": "To return to your account in future you need to set a password",
"Set Password": "Set Password",
"Set up Secure Backup": "Set up Secure Backup",
"Encryption upgrade available": "Encryption upgrade available",
"Verify this session": "Verify this session",
@ -636,7 +633,6 @@
"Export E2E room keys": "Export E2E room keys",
"Do you want to set an email address?": "Do you want to set an email address?",
"Current password": "Current password",
"Password": "Password",
"New Password": "New Password",
"Confirm password": "Confirm password",
"Change Password": "Change Password",
@ -1819,22 +1815,6 @@
"Verification Pending": "Verification Pending",
"Please check your email and click on the link it contains. Once this is done, click continue.": "Please check your email and click on the link it contains. Once this is done, click continue.",
"This will allow you to reset your password and receive notifications.": "This will allow you to reset your password and receive notifications.",
"A username can only contain lower case letters, numbers and '=_-./'": "A username can only contain lower case letters, numbers and '=_-./'",
"Username not available": "Username not available",
"Username invalid: %(errMessage)s": "Username invalid: %(errMessage)s",
"An error occurred: %(error_string)s": "An error occurred: %(error_string)s",
"Checking...": "Checking...",
"Username available": "Username available",
"To get started, please pick a username!": "To get started, please pick a username!",
"This will be your account name on the <span></span> homeserver, or you can pick a <a>different server</a>.": "This will be your account name on the <span></span> homeserver, or you can pick a <a>different server</a>.",
"If you already have a Matrix account you can <a>log in</a> instead.": "If you already have a Matrix account you can <a>log in</a> instead.",
"You have successfully set a password!": "You have successfully set a password!",
"You have successfully set a password and an email address!": "You have successfully set a password and an email address!",
"You can now return to your account after signing out, and sign in on other devices.": "You can now return to your account after signing out, and sign in on other devices.",
"Remember, you can always set an email address in user settings if you change your mind.": "Remember, you can always set an email address in user settings if you change your mind.",
"(HTTP status %(httpStatus)s)": "(HTTP status %(httpStatus)s)",
"Please set a password!": "Please set a password!",
"This will allow you to return to your account after signing out, and sign in on other sessions.": "This will allow you to return to your account after signing out, and sign in on other sessions.",
"Share Room": "Share Room",
"Link to most recent message": "Link to most recent message",
"Share User": "Share User",
@ -1946,6 +1926,7 @@
"Custom Server Options": "Custom Server Options",
"You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use %(brand)s with an existing Matrix account on a different homeserver.": "You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use %(brand)s with an existing Matrix account on a different homeserver.",
"Confirm your identity by entering your account password below.": "Confirm your identity by entering your account password below.",
"Password": "Password",
"Missing captcha public key in homeserver configuration. Please report this to your homeserver administrator.": "Missing captcha public key in homeserver configuration. Please report this to your homeserver administrator.",
"Please review and accept all of the homeserver's policies": "Please review and accept all of the homeserver's policies",
"Please review and accept the policies of this homeserver:": "Please review and accept the policies of this homeserver:",

View File

@ -1,90 +0,0 @@
/*
Copyright 2017 Vector Creations Ltd
Copyright 2019 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 dis from '../dispatcher/dispatcher';
import {Store} from 'flux/utils';
const INITIAL_STATE = {
cachedPassword: localStorage.getItem('mx_pass'),
};
/**
* A class for storing application state to do with the session. This is a simple flux
* store that listens for actions and updates its state accordingly, informing any
* listeners (views) of state changes.
*
* Usage:
* ```
* sessionStore.addListener(() => {
* this.setState({ cachedPassword: sessionStore.getCachedPassword() })
* })
* ```
*/
class SessionStore extends Store {
constructor() {
super(dis);
// Initialise state
this._state = INITIAL_STATE;
}
_update() {
// Persist state to localStorage
if (this._state.cachedPassword) {
localStorage.setItem('mx_pass', this._state.cachedPassword);
} else {
localStorage.removeItem('mx_pass', this._state.cachedPassword);
}
this.__emitChange();
}
_setState(newState) {
this._state = Object.assign(this._state, newState);
this._update();
}
__onDispatch(payload) {
switch (payload.action) {
case 'cached_password':
this._setState({
cachedPassword: payload.cachedPassword,
});
break;
case 'password_changed':
this._setState({
cachedPassword: null,
});
break;
case 'on_client_not_viable':
case 'on_logged_out':
this._setState({
cachedPassword: null,
});
break;
}
}
getCachedPassword() {
return this._state.cachedPassword;
}
}
let singletonSessionStore = null;
if (!singletonSessionStore) {
singletonSessionStore = new SessionStore();
}
export default singletonSessionStore;

View File

@ -1,47 +0,0 @@
/*
Copyright 2020 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 { _t } from "../languageHandler";
import Modal from "../Modal";
import SetPasswordDialog from "../components/views/dialogs/SetPasswordDialog";
import GenericToast from "../components/views/toasts/GenericToast";
import ToastStore from "../stores/ToastStore";
const onAccept = () => {
Modal.createTrackedDialog('Set Password Dialog', 'Password Nag Bar', SetPasswordDialog);
};
const TOAST_KEY = "setpassword";
export const showToast = () => {
ToastStore.sharedInstance().addOrReplaceToast({
key: TOAST_KEY,
title: _t("Set password"),
props: {
description: _t("To return to your account in future you need to set a password"),
acceptLabel: _t("Set Password"),
onAccept,
rejectLabel: _t("Later"),
onReject: hideToast, // it'll return on reload
},
component: GenericToast,
priority: 60,
});
};
export const hideToast = () => {
ToastStore.sharedInstance().dismissToast(TOAST_KEY);
};