2017-02-13 19:03:21 +03:00
|
|
|
/*
|
|
|
|
Copyright 2017 Vector Creations Ltd.
|
2020-03-31 05:03:46 +03:00
|
|
|
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
|
2017-02-13 19:03:21 +03:00
|
|
|
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
2019-12-21 00:41:07 +03:00
|
|
|
import {InteractiveAuth} from "matrix-js-sdk";
|
2019-12-08 15:16:17 +03:00
|
|
|
import React, {createRef} from 'react';
|
2017-12-26 04:03:18 +03:00
|
|
|
import PropTypes from 'prop-types';
|
2017-02-13 19:03:21 +03:00
|
|
|
|
2020-01-25 01:26:28 +03:00
|
|
|
import getEntryComponentForLoginType from '../views/auth/InteractiveAuthEntryComponents';
|
2017-02-13 19:03:21 +03:00
|
|
|
|
2019-12-20 04:19:56 +03:00
|
|
|
import * as sdk from '../../index';
|
2019-06-10 17:22:53 +03:00
|
|
|
|
2020-03-31 05:03:46 +03:00
|
|
|
export const ERROR_USER_CANCELLED = new Error("User cancelled auth session");
|
|
|
|
|
2020-08-29 14:14:16 +03:00
|
|
|
export default class InteractiveAuthComponent extends React.Component {
|
|
|
|
static propTypes = {
|
2017-02-24 14:41:23 +03:00
|
|
|
// matrix client to use for UI auth requests
|
2017-12-26 04:03:18 +03:00
|
|
|
matrixClient: PropTypes.object.isRequired,
|
2017-02-24 14:41:23 +03:00
|
|
|
|
2017-02-13 19:03:21 +03:00
|
|
|
// response from initial request. If not supplied, will do a request on
|
|
|
|
// mount.
|
2017-12-26 04:03:18 +03:00
|
|
|
authData: PropTypes.shape({
|
|
|
|
flows: PropTypes.array,
|
|
|
|
params: PropTypes.object,
|
|
|
|
session: PropTypes.string,
|
2017-02-13 19:03:21 +03:00
|
|
|
}),
|
|
|
|
|
|
|
|
// callback
|
2017-12-26 04:03:18 +03:00
|
|
|
makeRequest: PropTypes.func.isRequired,
|
2017-02-13 19:03:21 +03:00
|
|
|
|
2017-03-03 15:08:26 +03:00
|
|
|
// callback called when the auth process has finished,
|
|
|
|
// successfully or unsuccessfully.
|
2017-02-13 22:09:43 +03:00
|
|
|
// @param {bool} status True if the operation requiring
|
|
|
|
// auth was completed sucessfully, false if canceled.
|
2017-03-06 20:31:21 +03:00
|
|
|
// @param {object} result The result of the authenticated call
|
2020-03-31 05:03:46 +03:00
|
|
|
// if successful, otherwise the error object.
|
2017-03-06 20:31:21 +03:00
|
|
|
// @param {object} extra Additional information about the UI Auth
|
|
|
|
// process:
|
|
|
|
// * emailSid {string} If email auth was performed, the sid of
|
|
|
|
// the auth session.
|
|
|
|
// * clientSecret {string} The client secret used in auth
|
|
|
|
// sessions with the ID server.
|
2017-12-26 04:03:18 +03:00
|
|
|
onAuthFinished: PropTypes.func.isRequired,
|
2017-02-24 14:41:23 +03:00
|
|
|
|
|
|
|
// Inputs provided by the user to the auth process
|
|
|
|
// and used by various stages. As passed to js-sdk
|
|
|
|
// interactive-auth
|
2017-12-26 04:03:18 +03:00
|
|
|
inputs: PropTypes.object,
|
2017-02-24 14:41:23 +03:00
|
|
|
|
|
|
|
// As js-sdk interactive-auth
|
2019-05-22 13:51:26 +03:00
|
|
|
requestEmailToken: PropTypes.func,
|
2017-12-26 04:03:18 +03:00
|
|
|
sessionId: PropTypes.string,
|
|
|
|
clientSecret: PropTypes.string,
|
|
|
|
emailSid: PropTypes.string,
|
2017-02-24 20:24:10 +03:00
|
|
|
|
|
|
|
// If true, poll to see if the auth flow has been completed
|
|
|
|
// out-of-band
|
2017-12-26 04:03:18 +03:00
|
|
|
poll: PropTypes.bool,
|
2018-11-15 04:26:08 +03:00
|
|
|
|
|
|
|
// If true, components will be told that the 'Continue' button
|
|
|
|
// is managed by some other party and should not be managed by
|
|
|
|
// the component itself.
|
|
|
|
continueIsManaged: PropTypes.bool,
|
2020-03-31 05:03:46 +03:00
|
|
|
|
|
|
|
// Called when the stage changes, or the stage's phase changes. First
|
|
|
|
// argument is the stage, second is the phase. Some stages do not have
|
|
|
|
// phases and will be counted as 0 (numeric).
|
|
|
|
onStagePhaseChange: PropTypes.func,
|
|
|
|
|
|
|
|
// continueText and continueKind are passed straight through to the AuthEntryComponent.
|
|
|
|
continueText: PropTypes.string,
|
|
|
|
continueKind: PropTypes.string,
|
2020-08-29 14:14:16 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
2017-02-13 19:03:21 +03:00
|
|
|
|
2020-08-29 14:14:16 +03:00
|
|
|
this.state = {
|
2017-02-13 19:03:21 +03:00
|
|
|
authStage: null,
|
|
|
|
busy: false,
|
|
|
|
errorText: null,
|
|
|
|
stageErrorText: null,
|
|
|
|
submitButtonEnabled: false,
|
|
|
|
};
|
|
|
|
|
|
|
|
this._unmounted = false;
|
|
|
|
this._authLogic = new InteractiveAuth({
|
|
|
|
authData: this.props.authData,
|
2019-06-10 18:18:58 +03:00
|
|
|
doRequest: this._requestCallback,
|
2019-06-10 18:27:27 +03:00
|
|
|
busyChanged: this._onBusyChanged,
|
2017-02-24 14:41:23 +03:00
|
|
|
inputs: this.props.inputs,
|
|
|
|
stateUpdated: this._authStateUpdated,
|
|
|
|
matrixClient: this.props.matrixClient,
|
|
|
|
sessionId: this.props.sessionId,
|
|
|
|
clientSecret: this.props.clientSecret,
|
|
|
|
emailSid: this.props.emailSid,
|
2019-06-06 21:41:57 +03:00
|
|
|
requestEmailToken: this._requestEmailToken,
|
2017-02-13 19:03:21 +03:00
|
|
|
});
|
|
|
|
|
|
|
|
this._authLogic.attemptAuth().then((result) => {
|
2017-03-06 20:31:21 +03:00
|
|
|
const extra = {
|
|
|
|
emailSid: this._authLogic.getEmailSid(),
|
|
|
|
clientSecret: this._authLogic.getClientSecret(),
|
|
|
|
};
|
|
|
|
this.props.onAuthFinished(true, result, extra);
|
2017-02-13 19:03:21 +03:00
|
|
|
}).catch((error) => {
|
2017-03-03 15:08:26 +03:00
|
|
|
this.props.onAuthFinished(false, error);
|
2017-02-13 19:03:21 +03:00
|
|
|
console.error("Error during user-interactive auth:", error);
|
|
|
|
if (this._unmounted) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const msg = error.message || error.toString();
|
|
|
|
this.setState({
|
2017-10-11 19:56:17 +03:00
|
|
|
errorText: msg,
|
2017-02-13 19:03:21 +03:00
|
|
|
});
|
2019-11-18 13:03:05 +03:00
|
|
|
});
|
2017-02-24 20:24:10 +03:00
|
|
|
|
|
|
|
this._intervalId = null;
|
|
|
|
if (this.props.poll) {
|
|
|
|
this._intervalId = setInterval(() => {
|
|
|
|
this._authLogic.poll();
|
|
|
|
}, 2000);
|
|
|
|
}
|
2019-12-08 15:16:17 +03:00
|
|
|
|
|
|
|
this._stageComponent = createRef();
|
2020-08-29 14:14:16 +03:00
|
|
|
}
|
2017-02-13 19:03:21 +03:00
|
|
|
|
2020-08-29 14:14:16 +03:00
|
|
|
componentWillUnmount() {
|
2017-02-13 19:03:21 +03:00
|
|
|
this._unmounted = true;
|
2017-02-24 20:24:10 +03:00
|
|
|
|
|
|
|
if (this._intervalId !== null) {
|
|
|
|
clearInterval(this._intervalId);
|
|
|
|
}
|
2020-08-29 14:14:16 +03:00
|
|
|
}
|
2019-06-06 21:41:57 +03:00
|
|
|
|
2020-08-29 14:14:16 +03:00
|
|
|
_requestEmailToken = async (...args) => {
|
2019-06-06 21:41:57 +03:00
|
|
|
this.setState({
|
|
|
|
busy: true,
|
|
|
|
});
|
|
|
|
try {
|
|
|
|
return await this.props.requestEmailToken(...args);
|
|
|
|
} finally {
|
|
|
|
this.setState({
|
|
|
|
busy: false,
|
|
|
|
});
|
|
|
|
}
|
2020-08-29 14:14:16 +03:00
|
|
|
};
|
2017-02-13 19:03:21 +03:00
|
|
|
|
2020-08-29 14:14:16 +03:00
|
|
|
tryContinue = () => {
|
2019-12-08 15:16:17 +03:00
|
|
|
if (this._stageComponent.current && this._stageComponent.current.tryContinue) {
|
|
|
|
this._stageComponent.current.tryContinue();
|
2018-11-15 04:26:08 +03:00
|
|
|
}
|
2020-08-29 14:14:16 +03:00
|
|
|
};
|
2018-11-15 04:26:08 +03:00
|
|
|
|
2020-08-29 14:14:16 +03:00
|
|
|
_authStateUpdated = (stageType, stageState) => {
|
2017-02-24 14:41:23 +03:00
|
|
|
const oldStage = this.state.authStage;
|
2017-02-13 19:03:21 +03:00
|
|
|
this.setState({
|
2020-03-03 17:40:21 +03:00
|
|
|
busy: false,
|
2017-02-13 19:03:21 +03:00
|
|
|
authStage: stageType,
|
2017-02-24 14:41:23 +03:00
|
|
|
stageState: stageState,
|
2017-03-01 19:59:25 +03:00
|
|
|
errorText: stageState.error,
|
2017-02-24 14:41:23 +03:00
|
|
|
}, () => {
|
|
|
|
if (oldStage != stageType) this._setFocus();
|
|
|
|
});
|
2020-08-29 14:14:16 +03:00
|
|
|
};
|
2017-02-13 19:03:21 +03:00
|
|
|
|
2020-08-29 14:14:16 +03:00
|
|
|
_requestCallback = (auth) => {
|
2019-06-10 18:18:58 +03:00
|
|
|
// This wrapper just exists because the js-sdk passes a second
|
|
|
|
// 'busy' param for backwards compat. This throws the tests off
|
|
|
|
// so discard it here.
|
|
|
|
return this.props.makeRequest(auth);
|
2020-08-29 14:14:16 +03:00
|
|
|
};
|
2019-06-10 18:18:58 +03:00
|
|
|
|
2020-08-29 14:14:16 +03:00
|
|
|
_onBusyChanged = (busy) => {
|
2019-06-10 17:22:53 +03:00
|
|
|
// if we've started doing stuff, reset the error messages
|
|
|
|
if (busy) {
|
|
|
|
this.setState({
|
|
|
|
busy: true,
|
|
|
|
errorText: null,
|
|
|
|
stageErrorText: null,
|
|
|
|
});
|
|
|
|
}
|
2020-03-03 20:41:28 +03:00
|
|
|
// The JS SDK eagerly reports itself as "not busy" right after any
|
|
|
|
// immediate work has completed, but that's not really what we want at
|
|
|
|
// the UI layer, so we ignore this signal and show a spinner until
|
|
|
|
// there's a new screen to show the user. This is implemented by setting
|
|
|
|
// `busy: false` in `_authStateUpdated`.
|
2020-08-03 18:02:26 +03:00
|
|
|
// See also https://github.com/vector-im/element-web/issues/12546
|
2020-08-29 14:14:16 +03:00
|
|
|
};
|
2017-02-13 19:03:21 +03:00
|
|
|
|
2020-08-29 14:14:16 +03:00
|
|
|
_setFocus() {
|
2019-12-08 15:16:17 +03:00
|
|
|
if (this._stageComponent.current && this._stageComponent.current.focus) {
|
|
|
|
this._stageComponent.current.focus();
|
2017-02-13 19:03:21 +03:00
|
|
|
}
|
2020-08-29 14:14:16 +03:00
|
|
|
}
|
2017-02-13 19:03:21 +03:00
|
|
|
|
2020-08-29 14:14:16 +03:00
|
|
|
_submitAuthDict = authData => {
|
2017-02-13 19:03:21 +03:00
|
|
|
this._authLogic.submitAuthDict(authData);
|
2020-08-29 14:14:16 +03:00
|
|
|
};
|
2017-02-13 19:03:21 +03:00
|
|
|
|
2020-08-29 14:14:16 +03:00
|
|
|
_onPhaseChange = newPhase => {
|
2020-03-31 05:03:46 +03:00
|
|
|
if (this.props.onStagePhaseChange) {
|
|
|
|
this.props.onStagePhaseChange(this.state.authStage, newPhase || 0);
|
|
|
|
}
|
2020-08-29 14:14:16 +03:00
|
|
|
};
|
2020-03-31 05:03:46 +03:00
|
|
|
|
2020-08-29 14:14:16 +03:00
|
|
|
_onStageCancel = () => {
|
2020-03-31 05:03:46 +03:00
|
|
|
this.props.onAuthFinished(false, ERROR_USER_CANCELLED);
|
2020-08-29 14:14:16 +03:00
|
|
|
};
|
2020-03-31 05:03:46 +03:00
|
|
|
|
2020-08-29 14:14:16 +03:00
|
|
|
_renderCurrentStage() {
|
2017-02-13 19:03:21 +03:00
|
|
|
const stage = this.state.authStage;
|
2019-06-10 17:22:53 +03:00
|
|
|
if (!stage) {
|
|
|
|
if (this.state.busy) {
|
|
|
|
const Loader = sdk.getComponent("elements.Spinner");
|
|
|
|
return <Loader />;
|
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
2017-02-24 14:41:23 +03:00
|
|
|
|
|
|
|
const StageComponent = getEntryComponentForLoginType(stage);
|
2017-02-13 19:03:21 +03:00
|
|
|
return (
|
2019-12-08 15:16:17 +03:00
|
|
|
<StageComponent
|
|
|
|
ref={this._stageComponent}
|
2017-02-13 19:03:21 +03:00
|
|
|
loginType={stage}
|
2017-02-24 14:41:23 +03:00
|
|
|
matrixClient={this.props.matrixClient}
|
2017-02-13 19:03:21 +03:00
|
|
|
authSessionId={this._authLogic.getSessionId()}
|
2017-03-01 19:04:15 +03:00
|
|
|
clientSecret={this._authLogic.getClientSecret()}
|
2017-02-13 19:03:21 +03:00
|
|
|
stageParams={this._authLogic.getStageParams(stage)}
|
|
|
|
submitAuthDict={this._submitAuthDict}
|
|
|
|
errorText={this.state.stageErrorText}
|
2017-02-13 21:52:33 +03:00
|
|
|
busy={this.state.busy}
|
2017-02-24 14:41:23 +03:00
|
|
|
inputs={this.props.inputs}
|
|
|
|
stageState={this.state.stageState}
|
2017-03-01 19:04:15 +03:00
|
|
|
fail={this._onAuthStageFailed}
|
|
|
|
setEmailSid={this._setEmailSid}
|
2018-11-16 22:15:44 +03:00
|
|
|
showContinue={!this.props.continueIsManaged}
|
2020-03-31 05:03:46 +03:00
|
|
|
onPhaseChange={this._onPhaseChange}
|
|
|
|
continueText={this.props.continueText}
|
|
|
|
continueKind={this.props.continueKind}
|
|
|
|
onCancel={this._onStageCancel}
|
2017-02-13 19:03:21 +03:00
|
|
|
/>
|
|
|
|
);
|
2020-08-29 14:14:16 +03:00
|
|
|
}
|
2017-02-13 19:03:21 +03:00
|
|
|
|
2020-08-29 14:14:16 +03:00
|
|
|
_onAuthStageFailed = e => {
|
2017-03-03 15:08:26 +03:00
|
|
|
this.props.onAuthFinished(false, e);
|
2020-08-29 14:14:16 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
_setEmailSid = sid => {
|
2017-03-01 19:04:15 +03:00
|
|
|
this._authLogic.setEmailSid(sid);
|
2020-08-29 14:14:16 +03:00
|
|
|
};
|
2017-03-01 19:04:15 +03:00
|
|
|
|
2020-08-29 14:14:16 +03:00
|
|
|
render() {
|
2017-02-13 19:03:21 +03:00
|
|
|
let error = null;
|
|
|
|
if (this.state.errorText) {
|
|
|
|
error = (
|
|
|
|
<div className="error">
|
2017-10-11 19:56:17 +03:00
|
|
|
{ this.state.errorText }
|
2017-02-13 19:03:21 +03:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div>
|
|
|
|
<div>
|
2017-10-11 19:56:17 +03:00
|
|
|
{ this._renderCurrentStage() }
|
|
|
|
{ error }
|
2017-02-13 19:03:21 +03:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
2020-08-29 14:14:16 +03:00
|
|
|
}
|
|
|
|
}
|