mirror of
https://github.com/element-hq/element-web
synced 2024-11-24 02:05:45 +03:00
Merge pull request #4292 from matrix-org/travis/sso-uia
Support SSO for interactive authentication
This commit is contained in:
commit
094d006928
8 changed files with 323 additions and 14 deletions
|
@ -60,3 +60,14 @@ limitations under the License.
|
||||||
.mx_InteractiveAuthEntryComponents_passwordSection {
|
.mx_InteractiveAuthEntryComponents_passwordSection {
|
||||||
width: 300px;
|
width: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_InteractiveAuthEntryComponents_sso_buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: 20px;
|
||||||
|
|
||||||
|
.mx_AccessibleButton {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -36,12 +36,20 @@ limitations under the License.
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_AccessibleButton_kind_primary_outline {
|
||||||
|
color: $button-primary-bg-color;
|
||||||
|
background-color: $button-secondary-bg-color;
|
||||||
|
border: 1px solid $button-primary-bg-color;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_AccessibleButton_kind_secondary {
|
.mx_AccessibleButton_kind_secondary {
|
||||||
color: $accent-color;
|
color: $accent-color;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_AccessibleButton_kind_primary.mx_AccessibleButton_disabled {
|
.mx_AccessibleButton_kind_primary.mx_AccessibleButton_disabled,
|
||||||
|
.mx_AccessibleButton_kind_primary_outline.mx_AccessibleButton_disabled {
|
||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,7 +68,14 @@ limitations under the License.
|
||||||
background-color: $button-danger-bg-color;
|
background-color: $button-danger-bg-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_AccessibleButton_kind_danger.mx_AccessibleButton_disabled {
|
.mx_AccessibleButton_kind_danger_outline {
|
||||||
|
color: $button-danger-bg-color;
|
||||||
|
background-color: $button-secondary-bg-color;
|
||||||
|
border: 1px solid $button-danger-bg-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AccessibleButton_kind_danger.mx_AccessibleButton_disabled,
|
||||||
|
.mx_AccessibleButton_kind_danger_outline.mx_AccessibleButton_disabled {
|
||||||
color: $button-danger-disabled-fg-color;
|
color: $button-danger-disabled-fg-color;
|
||||||
background-color: $button-danger-disabled-bg-color;
|
background-color: $button-danger-disabled-bg-color;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import * as sdk from './index';
|
||||||
import Modal from './Modal';
|
import Modal from './Modal';
|
||||||
import { _t } from './languageHandler';
|
import { _t } from './languageHandler';
|
||||||
import IdentityAuthClient from './IdentityAuthClient';
|
import IdentityAuthClient from './IdentityAuthClient';
|
||||||
|
import {SSOAuthEntry} from "./components/views/auth/InteractiveAuthEntryComponents";
|
||||||
|
|
||||||
function getIdServerDomain() {
|
function getIdServerDomain() {
|
||||||
return MatrixClientPeg.get().idBaseUrl.split("://")[1];
|
return MatrixClientPeg.get().idBaseUrl.split("://")[1];
|
||||||
|
@ -188,11 +189,31 @@ export default class AddThreepid {
|
||||||
// pop up an interactive auth dialog
|
// pop up an interactive auth dialog
|
||||||
const InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog");
|
const InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog");
|
||||||
|
|
||||||
|
|
||||||
|
const dialogAesthetics = {
|
||||||
|
[SSOAuthEntry.PHASE_PREAUTH]: {
|
||||||
|
title: _t("Use Single Sign On to continue"),
|
||||||
|
body: _t("Confirm adding this email address by using " +
|
||||||
|
"Single Sign On to prove your identity."),
|
||||||
|
continueText: _t("Single Sign On"),
|
||||||
|
continueKind: "primary",
|
||||||
|
},
|
||||||
|
[SSOAuthEntry.PHASE_POSTAUTH]: {
|
||||||
|
title: _t("Confirm adding email"),
|
||||||
|
body: _t("Click the button below to confirm adding this email address."),
|
||||||
|
continueText: _t("Confirm"),
|
||||||
|
continueKind: "primary",
|
||||||
|
},
|
||||||
|
};
|
||||||
const { finished } = Modal.createTrackedDialog('Add Email', '', InteractiveAuthDialog, {
|
const { finished } = Modal.createTrackedDialog('Add Email', '', InteractiveAuthDialog, {
|
||||||
title: _t("Add Email Address"),
|
title: _t("Add Email Address"),
|
||||||
matrixClient: MatrixClientPeg.get(),
|
matrixClient: MatrixClientPeg.get(),
|
||||||
authData: e.data,
|
authData: e.data,
|
||||||
makeRequest: this._makeAddThreepidOnlyRequest,
|
makeRequest: this._makeAddThreepidOnlyRequest,
|
||||||
|
aestheticsForStagePhases: {
|
||||||
|
[SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics,
|
||||||
|
[SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
return finished;
|
return finished;
|
||||||
}
|
}
|
||||||
|
@ -285,11 +306,30 @@ export default class AddThreepid {
|
||||||
// pop up an interactive auth dialog
|
// pop up an interactive auth dialog
|
||||||
const InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog");
|
const InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog");
|
||||||
|
|
||||||
|
const dialogAesthetics = {
|
||||||
|
[SSOAuthEntry.PHASE_PREAUTH]: {
|
||||||
|
title: _t("Use Single Sign On to continue"),
|
||||||
|
body: _t("Confirm adding this phone number by using " +
|
||||||
|
"Single Sign On to prove your identity."),
|
||||||
|
continueText: _t("Single Sign On"),
|
||||||
|
continueKind: "primary",
|
||||||
|
},
|
||||||
|
[SSOAuthEntry.PHASE_POSTAUTH]: {
|
||||||
|
title: _t("Confirm adding phone number"),
|
||||||
|
body: _t("Click the button below to confirm adding this phone number."),
|
||||||
|
continueText: _t("Confirm"),
|
||||||
|
continueKind: "primary",
|
||||||
|
},
|
||||||
|
};
|
||||||
const { finished } = Modal.createTrackedDialog('Add MSISDN', '', InteractiveAuthDialog, {
|
const { finished } = Modal.createTrackedDialog('Add MSISDN', '', InteractiveAuthDialog, {
|
||||||
title: _t("Add Phone Number"),
|
title: _t("Add Phone Number"),
|
||||||
matrixClient: MatrixClientPeg.get(),
|
matrixClient: MatrixClientPeg.get(),
|
||||||
authData: e.data,
|
authData: e.data,
|
||||||
makeRequest: this._makeAddThreepidOnlyRequest,
|
makeRequest: this._makeAddThreepidOnlyRequest,
|
||||||
|
aestheticsForStagePhases: {
|
||||||
|
[SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics,
|
||||||
|
[SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
return finished;
|
return finished;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2017 Vector Creations Ltd.
|
Copyright 2017 Vector Creations Ltd.
|
||||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -24,6 +24,8 @@ import getEntryComponentForLoginType from '../views/auth/InteractiveAuthEntryCom
|
||||||
|
|
||||||
import * as sdk from '../../index';
|
import * as sdk from '../../index';
|
||||||
|
|
||||||
|
export const ERROR_USER_CANCELLED = new Error("User cancelled auth session");
|
||||||
|
|
||||||
export default createReactClass({
|
export default createReactClass({
|
||||||
displayName: 'InteractiveAuth',
|
displayName: 'InteractiveAuth',
|
||||||
|
|
||||||
|
@ -47,7 +49,7 @@ export default createReactClass({
|
||||||
// @param {bool} status True if the operation requiring
|
// @param {bool} status True if the operation requiring
|
||||||
// auth was completed sucessfully, false if canceled.
|
// auth was completed sucessfully, false if canceled.
|
||||||
// @param {object} result The result of the authenticated call
|
// @param {object} result The result of the authenticated call
|
||||||
// if successful, otherwise the error object
|
// if successful, otherwise the error object.
|
||||||
// @param {object} extra Additional information about the UI Auth
|
// @param {object} extra Additional information about the UI Auth
|
||||||
// process:
|
// process:
|
||||||
// * emailSid {string} If email auth was performed, the sid of
|
// * emailSid {string} If email auth was performed, the sid of
|
||||||
|
@ -75,6 +77,15 @@ export default createReactClass({
|
||||||
// is managed by some other party and should not be managed by
|
// is managed by some other party and should not be managed by
|
||||||
// the component itself.
|
// the component itself.
|
||||||
continueIsManaged: PropTypes.bool,
|
continueIsManaged: PropTypes.bool,
|
||||||
|
|
||||||
|
// 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,
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
|
@ -204,6 +215,16 @@ export default createReactClass({
|
||||||
this._authLogic.submitAuthDict(authData);
|
this._authLogic.submitAuthDict(authData);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_onPhaseChange: function(newPhase) {
|
||||||
|
if (this.props.onStagePhaseChange) {
|
||||||
|
this.props.onStagePhaseChange(this.state.authStage, newPhase || 0);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_onStageCancel: function() {
|
||||||
|
this.props.onAuthFinished(false, ERROR_USER_CANCELLED);
|
||||||
|
},
|
||||||
|
|
||||||
_renderCurrentStage: function() {
|
_renderCurrentStage: function() {
|
||||||
const stage = this.state.authStage;
|
const stage = this.state.authStage;
|
||||||
if (!stage) {
|
if (!stage) {
|
||||||
|
@ -232,6 +253,10 @@ export default createReactClass({
|
||||||
fail={this._onAuthStageFailed}
|
fail={this._onAuthStageFailed}
|
||||||
setEmailSid={this._setEmailSid}
|
setEmailSid={this._setEmailSid}
|
||||||
showContinue={!this.props.continueIsManaged}
|
showContinue={!this.props.continueIsManaged}
|
||||||
|
onPhaseChange={this._onPhaseChange}
|
||||||
|
continueText={this.props.continueText}
|
||||||
|
continueKind={this.props.continueKind}
|
||||||
|
onCancel={this._onStageCancel}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2016 OpenMarket Ltd
|
Copyright 2016 OpenMarket Ltd
|
||||||
Copyright 2017 Vector Creations Ltd
|
Copyright 2017 Vector Creations Ltd
|
||||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -25,6 +25,7 @@ import classnames from 'classnames';
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
|
|
||||||
/* This file contains a collection of components which are used by the
|
/* This file contains a collection of components which are used by the
|
||||||
* InteractiveAuth to prompt the user to enter the information needed
|
* InteractiveAuth to prompt the user to enter the information needed
|
||||||
|
@ -59,11 +60,21 @@ import SettingsStore from "../../../settings/SettingsStore";
|
||||||
* session to be failed and the process to go back to the start.
|
* session to be failed and the process to go back to the start.
|
||||||
* setEmailSid: m.login.email.identity only: a function to be called with the
|
* setEmailSid: m.login.email.identity only: a function to be called with the
|
||||||
* email sid after a token is requested.
|
* email sid after a token is requested.
|
||||||
|
* onPhaseChange: A function which is called when the stage's phase changes. If
|
||||||
|
* the stage has no phases, call this with DEFAULT_PHASE. Takes
|
||||||
|
* one argument, the phase, and is always defined/required.
|
||||||
|
* continueText: For stages which have a continue button, the text to use.
|
||||||
|
* continueKind: For stages which have a continue button, the style of button to
|
||||||
|
* use. For example, 'danger' or 'primary'.
|
||||||
|
* onCancel A function with no arguments which is called by the stage if the
|
||||||
|
* user knowingly cancelled/dismissed the authentication attempt.
|
||||||
*
|
*
|
||||||
* Each component may also provide the following functions (beyond the standard React ones):
|
* Each component may also provide the following functions (beyond the standard React ones):
|
||||||
* focus: set the input focus appropriately in the form.
|
* focus: set the input focus appropriately in the form.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
export const DEFAULT_PHASE = 0;
|
||||||
|
|
||||||
export const PasswordAuthEntry = createReactClass({
|
export const PasswordAuthEntry = createReactClass({
|
||||||
displayName: 'PasswordAuthEntry',
|
displayName: 'PasswordAuthEntry',
|
||||||
|
|
||||||
|
@ -78,6 +89,11 @@ export const PasswordAuthEntry = createReactClass({
|
||||||
// is the auth logic currently waiting for something to
|
// is the auth logic currently waiting for something to
|
||||||
// happen?
|
// happen?
|
||||||
busy: PropTypes.bool,
|
busy: PropTypes.bool,
|
||||||
|
onPhaseChange: PropTypes.func.isRequired,
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidMount: function() {
|
||||||
|
this.props.onPhaseChange(DEFAULT_PHASE);
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
|
@ -175,6 +191,11 @@ export const RecaptchaAuthEntry = createReactClass({
|
||||||
stageParams: PropTypes.object.isRequired,
|
stageParams: PropTypes.object.isRequired,
|
||||||
errorText: PropTypes.string,
|
errorText: PropTypes.string,
|
||||||
busy: PropTypes.bool,
|
busy: PropTypes.bool,
|
||||||
|
onPhaseChange: PropTypes.func.isRequired,
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidMount: function() {
|
||||||
|
this.props.onPhaseChange(DEFAULT_PHASE);
|
||||||
},
|
},
|
||||||
|
|
||||||
_onCaptchaResponse: function(response) {
|
_onCaptchaResponse: function(response) {
|
||||||
|
@ -236,6 +257,11 @@ export const TermsAuthEntry = createReactClass({
|
||||||
errorText: PropTypes.string,
|
errorText: PropTypes.string,
|
||||||
busy: PropTypes.bool,
|
busy: PropTypes.bool,
|
||||||
showContinue: PropTypes.bool,
|
showContinue: PropTypes.bool,
|
||||||
|
onPhaseChange: PropTypes.func.isRequired,
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidMount: function() {
|
||||||
|
this.props.onPhaseChange(DEFAULT_PHASE);
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
|
@ -378,6 +404,11 @@ export const EmailIdentityAuthEntry = createReactClass({
|
||||||
stageState: PropTypes.object.isRequired,
|
stageState: PropTypes.object.isRequired,
|
||||||
fail: PropTypes.func.isRequired,
|
fail: PropTypes.func.isRequired,
|
||||||
setEmailSid: PropTypes.func.isRequired,
|
setEmailSid: PropTypes.func.isRequired,
|
||||||
|
onPhaseChange: PropTypes.func.isRequired,
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidMount: function() {
|
||||||
|
this.props.onPhaseChange(DEFAULT_PHASE);
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
|
@ -420,6 +451,11 @@ export const MsisdnAuthEntry = createReactClass({
|
||||||
clientSecret: PropTypes.func,
|
clientSecret: PropTypes.func,
|
||||||
submitAuthDict: PropTypes.func.isRequired,
|
submitAuthDict: PropTypes.func.isRequired,
|
||||||
matrixClient: PropTypes.object,
|
matrixClient: PropTypes.object,
|
||||||
|
onPhaseChange: PropTypes.func.isRequired,
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidMount: function() {
|
||||||
|
this.props.onPhaseChange(DEFAULT_PHASE);
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
|
@ -564,6 +600,91 @@ export const MsisdnAuthEntry = createReactClass({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export class SSOAuthEntry extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
|
matrixClient: PropTypes.object.isRequired,
|
||||||
|
authSessionId: PropTypes.string.isRequired,
|
||||||
|
loginType: PropTypes.string.isRequired,
|
||||||
|
submitAuthDict: PropTypes.func.isRequired,
|
||||||
|
errorText: PropTypes.string,
|
||||||
|
onPhaseChange: PropTypes.func.isRequired,
|
||||||
|
continueText: PropTypes.string,
|
||||||
|
continueKind: PropTypes.string,
|
||||||
|
onCancel: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
static LOGIN_TYPE = "m.login.sso";
|
||||||
|
static UNSTABLE_LOGIN_TYPE = "org.matrix.login.sso";
|
||||||
|
|
||||||
|
static PHASE_PREAUTH = 1; // button to start SSO
|
||||||
|
static PHASE_POSTAUTH = 2; // button to confirm SSO completed
|
||||||
|
|
||||||
|
_ssoUrl: string;
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
// We actually send the user through fallback auth so we don't have to
|
||||||
|
// deal with a redirect back to us, losing application context.
|
||||||
|
this._ssoUrl = props.matrixClient.getFallbackAuthUrl(
|
||||||
|
this.props.loginType,
|
||||||
|
this.props.authSessionId,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
phase: SSOAuthEntry.PHASE_PREAUTH,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount(): void {
|
||||||
|
this.props.onPhaseChange(SSOAuthEntry.PHASE_PREAUTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
onStartAuthClick = () => {
|
||||||
|
// Note: We don't use PlatformPeg's startSsoAuth functions because we almost
|
||||||
|
// certainly will need to open the thing in a new tab to avoid losing application
|
||||||
|
// context.
|
||||||
|
|
||||||
|
window.open(this._ssoUrl, '_blank');
|
||||||
|
this.setState({phase: SSOAuthEntry.PHASE_POSTAUTH});
|
||||||
|
this.props.onPhaseChange(SSOAuthEntry.PHASE_POSTAUTH);
|
||||||
|
};
|
||||||
|
|
||||||
|
onConfirmClick = () => {
|
||||||
|
this.props.submitAuthDict({});
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let continueButton = null;
|
||||||
|
const cancelButton = (
|
||||||
|
<AccessibleButton
|
||||||
|
onClick={this.props.onCancel}
|
||||||
|
kind={this.props.continueKind ? (this.props.continueKind + '_outline') : 'primary_outline'}
|
||||||
|
>{_t("Cancel")}</AccessibleButton>
|
||||||
|
);
|
||||||
|
if (this.state.phase === SSOAuthEntry.PHASE_PREAUTH) {
|
||||||
|
continueButton = (
|
||||||
|
<AccessibleButton
|
||||||
|
onClick={this.onStartAuthClick}
|
||||||
|
kind={this.props.continueKind || 'primary'}
|
||||||
|
>{this.props.continueText || _t("Single Sign On")}</AccessibleButton>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
continueButton = (
|
||||||
|
<AccessibleButton
|
||||||
|
onClick={this.onConfirmClick}
|
||||||
|
kind={this.props.continueKind || 'primary'}
|
||||||
|
>{this.props.continueText || _t("Confirm")}</AccessibleButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className='mx_InteractiveAuthEntryComponents_sso_buttons'>
|
||||||
|
{cancelButton}
|
||||||
|
{continueButton}
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const FallbackAuthEntry = createReactClass({
|
export const FallbackAuthEntry = createReactClass({
|
||||||
displayName: 'FallbackAuthEntry',
|
displayName: 'FallbackAuthEntry',
|
||||||
|
|
||||||
|
@ -573,6 +694,11 @@ export const FallbackAuthEntry = createReactClass({
|
||||||
loginType: PropTypes.string.isRequired,
|
loginType: PropTypes.string.isRequired,
|
||||||
submitAuthDict: PropTypes.func.isRequired,
|
submitAuthDict: PropTypes.func.isRequired,
|
||||||
errorText: PropTypes.string,
|
errorText: PropTypes.string,
|
||||||
|
onPhaseChange: PropTypes.func.isRequired,
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidMount: function() {
|
||||||
|
this.props.onPhaseChange(DEFAULT_PHASE);
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
|
@ -597,7 +723,10 @@ export const FallbackAuthEntry = createReactClass({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_onShowFallbackClick: function() {
|
_onShowFallbackClick: function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
const url = this.props.matrixClient.getFallbackAuthUrl(
|
const url = this.props.matrixClient.getFallbackAuthUrl(
|
||||||
this.props.loginType,
|
this.props.loginType,
|
||||||
this.props.authSessionId,
|
this.props.authSessionId,
|
||||||
|
@ -626,7 +755,7 @@ export const FallbackAuthEntry = createReactClass({
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<a ref={this._fallbackButton} onClick={this._onShowFallbackClick}>{ _t("Start authentication") }</a>
|
<a href="" ref={this._fallbackButton} onClick={this._onShowFallbackClick}>{ _t("Start authentication") }</a>
|
||||||
{errorSection}
|
{errorSection}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -639,11 +768,12 @@ const AuthEntryComponents = [
|
||||||
EmailIdentityAuthEntry,
|
EmailIdentityAuthEntry,
|
||||||
MsisdnAuthEntry,
|
MsisdnAuthEntry,
|
||||||
TermsAuthEntry,
|
TermsAuthEntry,
|
||||||
|
SSOAuthEntry,
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function getEntryComponentForLoginType(loginType) {
|
export default function getEntryComponentForLoginType(loginType) {
|
||||||
for (const c of AuthEntryComponents) {
|
for (const c of AuthEntryComponents) {
|
||||||
if (c.LOGIN_TYPE == loginType) {
|
if (c.LOGIN_TYPE === loginType || c.UNSTABLE_LOGIN_TYPE === loginType) {
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2016 OpenMarket Ltd
|
Copyright 2016 OpenMarket Ltd
|
||||||
Copyright 2017 Vector Creations Ltd
|
Copyright 2017 Vector Creations Ltd
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -23,6 +24,7 @@ import * as sdk from '../../../index';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
|
import {ERROR_USER_CANCELLED} from "../../structures/InteractiveAuth";
|
||||||
|
|
||||||
export default createReactClass({
|
export default createReactClass({
|
||||||
displayName: 'InteractiveAuthDialog',
|
displayName: 'InteractiveAuthDialog',
|
||||||
|
@ -44,12 +46,36 @@ export default createReactClass({
|
||||||
|
|
||||||
onFinished: PropTypes.func.isRequired,
|
onFinished: PropTypes.func.isRequired,
|
||||||
|
|
||||||
|
// Optional title and body to show when not showing a particular stage
|
||||||
title: PropTypes.string,
|
title: PropTypes.string,
|
||||||
|
body: PropTypes.string,
|
||||||
|
|
||||||
|
// Optional title and body pairs for particular stages and phases within
|
||||||
|
// those stages. Object structure/example is:
|
||||||
|
// {
|
||||||
|
// "org.example.stage_type": {
|
||||||
|
// 1: {
|
||||||
|
// "body": "This is a body for phase 1" of org.example.stage_type,
|
||||||
|
// "title": "Title for phase 1 of org.example.stage_type"
|
||||||
|
// },
|
||||||
|
// 2: {
|
||||||
|
// "body": "This is a body for phase 2 of org.example.stage_type",
|
||||||
|
// "title": "Title for phase 2 of org.example.stage_type"
|
||||||
|
// "continueText": "Confirm identity with Example Auth",
|
||||||
|
// "continueKind": "danger"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
aestheticsForStagePhases: PropTypes.object,
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
authError: null,
|
authError: null,
|
||||||
|
|
||||||
|
// See _onUpdateStagePhase()
|
||||||
|
uiaStage: null,
|
||||||
|
uiaStagePhase: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -57,12 +83,21 @@ export default createReactClass({
|
||||||
if (success) {
|
if (success) {
|
||||||
this.props.onFinished(true, result);
|
this.props.onFinished(true, result);
|
||||||
} else {
|
} else {
|
||||||
this.setState({
|
if (result === ERROR_USER_CANCELLED) {
|
||||||
authError: result,
|
this.props.onFinished(false, null);
|
||||||
});
|
} else {
|
||||||
|
this.setState({
|
||||||
|
authError: result,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_onUpdateStagePhase: function(newStage, newPhase) {
|
||||||
|
// We copy the stage and stage phase params into state for title selection in render()
|
||||||
|
this.setState({uiaStage: newStage, uiaStagePhase: newPhase});
|
||||||
|
},
|
||||||
|
|
||||||
_onDismissClick: function() {
|
_onDismissClick: function() {
|
||||||
this.props.onFinished(false);
|
this.props.onFinished(false);
|
||||||
},
|
},
|
||||||
|
@ -71,6 +106,23 @@ export default createReactClass({
|
||||||
const InteractiveAuth = sdk.getComponent("structures.InteractiveAuth");
|
const InteractiveAuth = sdk.getComponent("structures.InteractiveAuth");
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
|
|
||||||
|
// Let's pick a title, body, and other params text that we'll show to the user. The order
|
||||||
|
// is most specific first, so stagePhase > our props > defaults.
|
||||||
|
|
||||||
|
let title = this.state.authError ? 'Error' : (this.props.title || _t('Authentication'));
|
||||||
|
let body = this.state.authError ? null : this.props.body;
|
||||||
|
let continueText = null;
|
||||||
|
let continueKind = null;
|
||||||
|
if (!this.state.authError && this.props.aestheticsForStagePhases) {
|
||||||
|
if (this.props.aestheticsForStagePhases[this.state.uiaStage]) {
|
||||||
|
const aesthetics = this.props.aestheticsForStagePhases[this.state.uiaStage][this.state.uiaStagePhase];
|
||||||
|
if (aesthetics && aesthetics.title) title = aesthetics.title;
|
||||||
|
if (aesthetics && aesthetics.body) body = aesthetics.body;
|
||||||
|
if (aesthetics && aesthetics.continueText) continueText = aesthetics.continueText;
|
||||||
|
if (aesthetics && aesthetics.continueKind) continueKind = aesthetics.continueKind;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let content;
|
let content;
|
||||||
if (this.state.authError) {
|
if (this.state.authError) {
|
||||||
content = (
|
content = (
|
||||||
|
@ -88,11 +140,16 @@ export default createReactClass({
|
||||||
} else {
|
} else {
|
||||||
content = (
|
content = (
|
||||||
<div id='mx_Dialog_content'>
|
<div id='mx_Dialog_content'>
|
||||||
<InteractiveAuth ref={this._collectInteractiveAuth}
|
{body}
|
||||||
|
<InteractiveAuth
|
||||||
|
ref={this._collectInteractiveAuth}
|
||||||
matrixClient={this.props.matrixClient}
|
matrixClient={this.props.matrixClient}
|
||||||
authData={this.props.authData}
|
authData={this.props.authData}
|
||||||
makeRequest={this.props.makeRequest}
|
makeRequest={this.props.makeRequest}
|
||||||
onAuthFinished={this._onAuthFinished}
|
onAuthFinished={this._onAuthFinished}
|
||||||
|
onStagePhaseChange={this._onUpdateStagePhase}
|
||||||
|
continueText={continueText}
|
||||||
|
continueKind={continueKind}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -101,7 +158,7 @@ export default createReactClass({
|
||||||
return (
|
return (
|
||||||
<BaseDialog className="mx_InteractiveAuthDialog"
|
<BaseDialog className="mx_InteractiveAuthDialog"
|
||||||
onFinished={this.props.onFinished}
|
onFinished={this.props.onFinished}
|
||||||
title={this.state.authError ? 'Error' : (this.props.title || _t('Authentication'))}
|
title={title}
|
||||||
contentId='mx_Dialog_content'
|
contentId='mx_Dialog_content'
|
||||||
>
|
>
|
||||||
{ content }
|
{ content }
|
||||||
|
|
|
@ -23,6 +23,7 @@ import * as sdk from '../../../index';
|
||||||
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
|
import {SSOAuthEntry} from "../auth/InteractiveAuthEntryComponents";
|
||||||
|
|
||||||
export default class DevicesPanel extends React.Component {
|
export default class DevicesPanel extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -123,11 +124,29 @@ export default class DevicesPanel extends React.Component {
|
||||||
// pop up an interactive auth dialog
|
// pop up an interactive auth dialog
|
||||||
const InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog");
|
const InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog");
|
||||||
|
|
||||||
|
const dialogAesthetics = {
|
||||||
|
[SSOAuthEntry.PHASE_PREAUTH]: {
|
||||||
|
title: _t("Use Single Sign On to continue"),
|
||||||
|
body: _t("Confirm deleting these sessions by using Single Sign On to prove your identity."),
|
||||||
|
continueText: _t("Single Sign On"),
|
||||||
|
continueKind: "primary",
|
||||||
|
},
|
||||||
|
[SSOAuthEntry.PHASE_POSTAUTH]: {
|
||||||
|
title: _t("Confirm deleting these sessions"),
|
||||||
|
body: _t("Click the button below to confirm deleting these sessions."),
|
||||||
|
continueText: _t("Delete sessions"),
|
||||||
|
continueKind: "danger",
|
||||||
|
},
|
||||||
|
};
|
||||||
Modal.createTrackedDialog('Delete Device Dialog', '', InteractiveAuthDialog, {
|
Modal.createTrackedDialog('Delete Device Dialog', '', InteractiveAuthDialog, {
|
||||||
title: _t("Authentication"),
|
title: _t("Authentication"),
|
||||||
matrixClient: MatrixClientPeg.get(),
|
matrixClient: MatrixClientPeg.get(),
|
||||||
authData: error.data,
|
authData: error.data,
|
||||||
makeRequest: this._makeDeleteRequest.bind(this),
|
makeRequest: this._makeDeleteRequest.bind(this),
|
||||||
|
aestheticsForStagePhases: {
|
||||||
|
[SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics,
|
||||||
|
[SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
console.error("Error deleting sessions", e);
|
console.error("Error deleting sessions", e);
|
||||||
|
|
|
@ -1,8 +1,17 @@
|
||||||
{
|
{
|
||||||
"This email address is already in use": "This email address is already in use",
|
"This email address is already in use": "This email address is already in use",
|
||||||
"This phone number is already in use": "This phone number is already in use",
|
"This phone number is already in use": "This phone number is already in use",
|
||||||
|
"Use Single Sign On to continue": "Use Single Sign On to continue",
|
||||||
|
"Confirm adding this email address by using Single Sign On to prove your identity.": "Confirm adding this email address by using Single Sign On to prove your identity.",
|
||||||
|
"Single Sign On": "Single Sign On",
|
||||||
|
"Confirm adding email": "Confirm adding email",
|
||||||
|
"Click the button below to confirm adding this email address.": "Click the button below to confirm adding this email address.",
|
||||||
|
"Confirm": "Confirm",
|
||||||
"Add Email Address": "Add Email Address",
|
"Add Email Address": "Add Email Address",
|
||||||
"Failed to verify email address: make sure you clicked the link in the email": "Failed to verify email address: make sure you clicked the link in the email",
|
"Failed to verify email address: make sure you clicked the link in the email": "Failed to verify email address: make sure you clicked the link in the email",
|
||||||
|
"Confirm adding this phone number by using Single Sign On to prove your identity.": "Confirm adding this phone number by using Single Sign On to prove your identity.",
|
||||||
|
"Confirm adding phone number": "Confirm adding phone number",
|
||||||
|
"Click the button below to confirm adding this phone number.": "Click the button below to confirm adding this phone number.",
|
||||||
"Add Phone Number": "Add Phone Number",
|
"Add Phone Number": "Add Phone Number",
|
||||||
"The platform you're on": "The platform you're on",
|
"The platform you're on": "The platform you're on",
|
||||||
"The version of Riot": "The version of Riot",
|
"The version of Riot": "The version of Riot",
|
||||||
|
@ -599,6 +608,10 @@
|
||||||
"up to date": "up to date",
|
"up to date": "up to date",
|
||||||
"Your homeserver does not support session management.": "Your homeserver does not support session management.",
|
"Your homeserver does not support session management.": "Your homeserver does not support session management.",
|
||||||
"Unable to load session list": "Unable to load session list",
|
"Unable to load session list": "Unable to load session list",
|
||||||
|
"Confirm deleting these sessions by using Single Sign On to prove your identity.": "Confirm deleting these sessions by using Single Sign On to prove your identity.",
|
||||||
|
"Confirm deleting these sessions": "Confirm deleting these sessions",
|
||||||
|
"Click the button below to confirm deleting these sessions.": "Click the button below to confirm deleting these sessions.",
|
||||||
|
"Delete sessions": "Delete sessions",
|
||||||
"Authentication": "Authentication",
|
"Authentication": "Authentication",
|
||||||
"Delete %(count)s sessions|other": "Delete %(count)s sessions",
|
"Delete %(count)s sessions|other": "Delete %(count)s sessions",
|
||||||
"Delete %(count)s sessions|one": "Delete %(count)s session",
|
"Delete %(count)s sessions|one": "Delete %(count)s session",
|
||||||
|
@ -1862,7 +1875,6 @@
|
||||||
"Use lowercase letters, numbers, dashes and underscores only": "Use lowercase letters, numbers, dashes and underscores only",
|
"Use lowercase letters, numbers, dashes and underscores only": "Use lowercase letters, numbers, dashes and underscores only",
|
||||||
"Enter username": "Enter username",
|
"Enter username": "Enter username",
|
||||||
"Email (optional)": "Email (optional)",
|
"Email (optional)": "Email (optional)",
|
||||||
"Confirm": "Confirm",
|
|
||||||
"Phone (optional)": "Phone (optional)",
|
"Phone (optional)": "Phone (optional)",
|
||||||
"Create your Matrix account on %(serverName)s": "Create your Matrix account on %(serverName)s",
|
"Create your Matrix account on %(serverName)s": "Create your Matrix account on %(serverName)s",
|
||||||
"Create your Matrix account on <underlinedServerName />": "Create your Matrix account on <underlinedServerName />",
|
"Create your Matrix account on <underlinedServerName />": "Create your Matrix account on <underlinedServerName />",
|
||||||
|
|
Loading…
Reference in a new issue