From 984f9ea4aabbac033ee70470713ddff948b41ba3 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 27 Mar 2020 13:44:32 -0600 Subject: [PATCH 1/9] Fix fallback auth link to act and look like a link --- .../views/auth/InteractiveAuthEntryComponents.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/components/views/auth/InteractiveAuthEntryComponents.js b/src/components/views/auth/InteractiveAuthEntryComponents.js index aaf8c884404..db73467ff72 100644 --- a/src/components/views/auth/InteractiveAuthEntryComponents.js +++ b/src/components/views/auth/InteractiveAuthEntryComponents.js @@ -598,7 +598,10 @@ export const FallbackAuthEntry = createReactClass({ } }, - _onShowFallbackClick: function() { + _onShowFallbackClick: function(e) { + e.preventDefault(); + e.stopPropagation(); + const url = this.props.matrixClient.getFallbackAuthUrl( this.props.loginType, this.props.authSessionId, @@ -627,7 +630,7 @@ export const FallbackAuthEntry = createReactClass({ } return (
- { _t("Start authentication") } + { _t("Start authentication") } {errorSection}
); From 1e30bdb73956e0d103b5d5168645aa635cca6b33 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 27 Mar 2020 14:39:59 -0600 Subject: [PATCH 2/9] Early proof of concept for SSO UIA It works well enough to start doing design. --- .../auth/InteractiveAuthEntryComponents.js | 66 ++++++++++++++++++- src/i18n/strings/en_EN.json | 1 + 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/src/components/views/auth/InteractiveAuthEntryComponents.js b/src/components/views/auth/InteractiveAuthEntryComponents.js index db73467ff72..9f0d5f1534c 100644 --- a/src/components/views/auth/InteractiveAuthEntryComponents.js +++ b/src/components/views/auth/InteractiveAuthEntryComponents.js @@ -1,7 +1,7 @@ /* Copyright 2016 OpenMarket 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"); you may not use this file except in compliance with the License. @@ -565,6 +565,67 @@ 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, + }; + + static LOGIN_TYPE = "m.login.sso"; + static UNSTABLE_LOGIN_TYPE = "org.matrix.login.sso"; + + static STAGE_PREAUTH = 1; // button to start SSO + static STAGE_POSTAUTH = 2; // button to confirm SSO completed + + constructor(props) { + super(props); + + this.state = { + // We actually send the user through fallback auth so we don't have to + // deal with a redirect back to us, losing application context. + ssoUrl: props.matrixClient.getFallbackAuthUrl( + this.props.loginType, + this.props.authSessionId, + ), + stage: SSOAuthEntry.STAGE_PREAUTH, + }; + } + + onStartAuthClick = (e) => { + e.preventDefault(); + e.stopPropagation(); + + // 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 loosing application + // context. + + window.open(e.target.href, '_blank'); + this.setState({stage: SSOAuthEntry.STAGE_POSTAUTH}); + }; + + onConfirmClick = (e) => { + e.preventDefault(); + e.stopPropagation(); + + this.props.submitAuthDict({}); + }; + + render () { + if (this.state.stage === SSOAuthEntry.STAGE_PREAUTH) { + return + {_t("Single Sign On")} + ; + } else { + return + {_t("Continue")} + ; + } + } +} + export const FallbackAuthEntry = createReactClass({ displayName: 'FallbackAuthEntry', @@ -643,11 +704,12 @@ const AuthEntryComponents = [ EmailIdentityAuthEntry, MsisdnAuthEntry, TermsAuthEntry, + SSOAuthEntry, ]; export default function getEntryComponentForLoginType(loginType) { for (const c of AuthEntryComponents) { - if (c.LOGIN_TYPE == loginType) { + if (c.LOGIN_TYPE === loginType || c.UNSTABLE_LOGIN_TYPE === loginType) { return c; } } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index a6e195aa16e..ed89068f913 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1831,6 +1831,7 @@ "Please enter the code it contains:": "Please enter the code it contains:", "Code": "Code", "Submit": "Submit", + "Single Sign On": "Single Sign On", "Start authentication": "Start authentication", "Unable to validate homeserver/identity server": "Unable to validate homeserver/identity server", "Your Modular server": "Your Modular server", From ffa75ef48c47a0d75caeb1490bb1d4066ede2728 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 30 Mar 2020 20:03:46 -0600 Subject: [PATCH 3/9] Wire up all the dialog parts for SSO, using device deletion as a POC --- .../auth/_InteractiveAuthEntryComponents.scss | 11 ++ res/css/views/elements/_AccessibleButton.scss | 17 ++- src/components/structures/InteractiveAuth.js | 29 ++++- .../auth/InteractiveAuthEntryComponents.js | 100 ++++++++++++++---- .../views/dialogs/InteractiveAuthDialog.js | 68 +++++++++++- src/components/views/settings/DevicesPanel.js | 19 ++++ src/i18n/strings/en_EN.json | 9 +- 7 files changed, 224 insertions(+), 29 deletions(-) diff --git a/res/css/views/auth/_InteractiveAuthEntryComponents.scss b/res/css/views/auth/_InteractiveAuthEntryComponents.scss index 85007aeecbe..05cddf2c48a 100644 --- a/res/css/views/auth/_InteractiveAuthEntryComponents.scss +++ b/res/css/views/auth/_InteractiveAuthEntryComponents.scss @@ -60,3 +60,14 @@ limitations under the License. .mx_InteractiveAuthEntryComponents_passwordSection { width: 300px; } + +.mx_InteractiveAuthEntryComponents_sso_buttons { + display: flex; + flex-direction: row; + justify-content: flex-end; + margin-top: 20px; + + .mx_AccessibleButton { + margin-left: 5px; + } +} diff --git a/res/css/views/elements/_AccessibleButton.scss b/res/css/views/elements/_AccessibleButton.scss index b87071745d4..0c48a07ccfe 100644 --- a/res/css/views/elements/_AccessibleButton.scss +++ b/res/css/views/elements/_AccessibleButton.scss @@ -36,6 +36,13 @@ limitations under the License. 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 { color: $accent-color; font-weight: 600; @@ -60,7 +67,15 @@ limitations under the License. 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; background-color: $button-danger-disabled-bg-color; } diff --git a/src/components/structures/InteractiveAuth.js b/src/components/structures/InteractiveAuth.js index f4adb5751ff..2492bf79a06 100644 --- a/src/components/structures/InteractiveAuth.js +++ b/src/components/structures/InteractiveAuth.js @@ -1,6 +1,6 @@ /* 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"); 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'; +export const ERROR_USER_CANCELLED = new Error("User cancelled auth session"); + export default createReactClass({ displayName: 'InteractiveAuth', @@ -47,7 +49,7 @@ export default createReactClass({ // @param {bool} status True if the operation requiring // auth was completed sucessfully, false if canceled. // @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 // process: // * 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 // the component itself. 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() { @@ -204,6 +215,16 @@ export default createReactClass({ 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() { const stage = this.state.authStage; if (!stage) { @@ -232,6 +253,10 @@ export default createReactClass({ fail={this._onAuthStageFailed} setEmailSid={this._setEmailSid} showContinue={!this.props.continueIsManaged} + onPhaseChange={this._onPhaseChange} + continueText={this.props.continueText} + continueKind={this.props.continueKind} + onCancel={this._onStageCancel} /> ); }, diff --git a/src/components/views/auth/InteractiveAuthEntryComponents.js b/src/components/views/auth/InteractiveAuthEntryComponents.js index 26a293724ae..d262a5525d2 100644 --- a/src/components/views/auth/InteractiveAuthEntryComponents.js +++ b/src/components/views/auth/InteractiveAuthEntryComponents.js @@ -25,6 +25,7 @@ import classnames from 'classnames'; import * as sdk from '../../../index'; import { _t } from '../../../languageHandler'; import SettingsStore from "../../../settings/SettingsStore"; +import AccessibleButton from "../elements/AccessibleButton"; /* This file contains a collection of components which are used by the * 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. * setEmailSid: m.login.email.identity only: a function to be called with the * 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): * focus: set the input focus appropriately in the form. */ +export const DEFAULT_PHASE = 0; + export const PasswordAuthEntry = createReactClass({ displayName: 'PasswordAuthEntry', @@ -78,6 +89,11 @@ export const PasswordAuthEntry = createReactClass({ // is the auth logic currently waiting for something to // happen? busy: PropTypes.bool, + onPhaseChange: PropTypes.func.isRequired, + }, + + componentDidMount: function() { + this.props.onPhaseChange(DEFAULT_PHASE); }, getInitialState: function() { @@ -175,6 +191,11 @@ export const RecaptchaAuthEntry = createReactClass({ stageParams: PropTypes.object.isRequired, errorText: PropTypes.string, busy: PropTypes.bool, + onPhaseChange: PropTypes.func.isRequired, + }, + + componentDidMount: function() { + this.props.onPhaseChange(DEFAULT_PHASE); }, _onCaptchaResponse: function(response) { @@ -236,6 +257,11 @@ export const TermsAuthEntry = createReactClass({ errorText: PropTypes.string, busy: PropTypes.bool, showContinue: PropTypes.bool, + onPhaseChange: PropTypes.func.isRequired, + }, + + componentDidMount: function() { + this.props.onPhaseChange(DEFAULT_PHASE); }, componentWillMount: function() { @@ -378,6 +404,11 @@ export const EmailIdentityAuthEntry = createReactClass({ stageState: PropTypes.object.isRequired, fail: PropTypes.func.isRequired, setEmailSid: PropTypes.func.isRequired, + onPhaseChange: PropTypes.func.isRequired, + }, + + componentDidMount: function() { + this.props.onPhaseChange(DEFAULT_PHASE); }, getInitialState: function() { @@ -420,6 +451,11 @@ export const MsisdnAuthEntry = createReactClass({ clientSecret: PropTypes.func, submitAuthDict: PropTypes.func.isRequired, matrixClient: PropTypes.object, + onPhaseChange: PropTypes.func.isRequired, + }, + + componentDidMount: function() { + this.props.onPhaseChange(DEFAULT_PHASE); }, getInitialState: function() { @@ -571,13 +607,17 @@ export class SSOAuthEntry extends React.Component { 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 STAGE_PREAUTH = 1; // button to start SSO - static STAGE_POSTAUTH = 2; // button to confirm SSO completed + static PHASE_PREAUTH = 1; // button to start SSO + static PHASE_POSTAUTH = 2; // button to confirm SSO completed constructor(props) { super(props); @@ -589,39 +629,56 @@ export class SSOAuthEntry extends React.Component { this.props.loginType, this.props.authSessionId, ), - stage: SSOAuthEntry.STAGE_PREAUTH, + phase: SSOAuthEntry.PHASE_PREAUTH, }; } - onStartAuthClick = (e) => { - e.preventDefault(); - e.stopPropagation(); + 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 loosing application // context. - window.open(e.target.href, '_blank'); - this.setState({stage: SSOAuthEntry.STAGE_POSTAUTH}); + window.open(this.state.ssoUrl, '_blank'); + this.setState({phase: SSOAuthEntry.PHASE_POSTAUTH}); + this.props.onPhaseChange(SSOAuthEntry.PHASE_POSTAUTH); }; - onConfirmClick = (e) => { - e.preventDefault(); - e.stopPropagation(); - + onConfirmClick = () => { this.props.submitAuthDict({}); }; render () { - if (this.state.stage === SSOAuthEntry.STAGE_PREAUTH) { - return - {_t("Single Sign On")} - ; + let continueButton = null; + const cancelButton = ( + {_t("Cancel")} + ); + if (this.state.phase === SSOAuthEntry.PHASE_PREAUTH) { + continueButton = ( + {this.props.continueText || _t("Single Sign On")} + ); } else { - return - {_t("Continue")} - ; + continueButton = ( + {this.props.continueText || _t("Confirm")} + ); } + + return
+ {cancelButton} + {continueButton} +
; } } @@ -634,6 +691,11 @@ export const FallbackAuthEntry = createReactClass({ loginType: PropTypes.string.isRequired, submitAuthDict: PropTypes.func.isRequired, errorText: PropTypes.string, + onPhaseChange: PropTypes.func.isRequired, + }, + + componentDidMount: function() { + this.props.onPhaseChange(DEFAULT_PHASE); }, componentWillMount: function() { diff --git a/src/components/views/dialogs/InteractiveAuthDialog.js b/src/components/views/dialogs/InteractiveAuthDialog.js index ff9f55cb741..c070cc65890 100644 --- a/src/components/views/dialogs/InteractiveAuthDialog.js +++ b/src/components/views/dialogs/InteractiveAuthDialog.js @@ -1,6 +1,7 @@ /* Copyright 2016 OpenMarket 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"); 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 AccessibleButton from '../elements/AccessibleButton'; +import {ERROR_USER_CANCELLED} from "../../structures/InteractiveAuth"; export default createReactClass({ displayName: 'InteractiveAuthDialog', @@ -44,12 +46,36 @@ export default createReactClass({ onFinished: PropTypes.func.isRequired, + // Optional title and body to show when not showing a particular stage 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() { return { authError: null, + + // See _onUpdateStagePhase() + uiaStage: null, + uiaStagePhase: null, }; }, @@ -57,12 +83,22 @@ export default createReactClass({ if (success) { this.props.onFinished(true, result); } else { - this.setState({ - authError: result, - }); + if (result === ERROR_USER_CANCELLED) { + this.props.onFinished(false, null); + } else { + this.setState({ + authError: result, + }); + } } }, + _onUpdateStagePhase: function(newStage, newPhase) { + console.log({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() { this.props.onFinished(false); }, @@ -71,6 +107,23 @@ export default createReactClass({ const InteractiveAuth = sdk.getComponent("structures.InteractiveAuth"); 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; if (this.state.authError) { content = ( @@ -88,11 +141,16 @@ export default createReactClass({ } else { content = (
-
); @@ -101,7 +159,7 @@ export default createReactClass({ return ( { content } diff --git a/src/components/views/settings/DevicesPanel.js b/src/components/views/settings/DevicesPanel.js index 2120801a81e..61e7724a6f9 100644 --- a/src/components/views/settings/DevicesPanel.js +++ b/src/components/views/settings/DevicesPanel.js @@ -23,6 +23,7 @@ import * as sdk from '../../../index'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; import { _t } from '../../../languageHandler'; import Modal from '../../../Modal'; +import {SSOAuthEntry} from "../auth/InteractiveAuthEntryComponents"; export default class DevicesPanel extends React.Component { constructor(props) { @@ -123,11 +124,29 @@ export default class DevicesPanel extends React.Component { // pop up an interactive auth dialog 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, { title: _t("Authentication"), matrixClient: MatrixClientPeg.get(), authData: error.data, makeRequest: this._makeDeleteRequest.bind(this), + aestheticsForStagePhases: { + [SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics, + [SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics, + }, }); }).catch((e) => { console.error("Error deleting sessions", e); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index e867623cc94..ecc2698f6d6 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -598,6 +598,12 @@ "up to date": "up to date", "Your homeserver does not support session management.": "Your homeserver does not support session management.", "Unable to load session list": "Unable to load session list", + "Use Single Sign On to continue": "Use Single Sign On to continue", + "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.", + "Single Sign On": "Single Sign On", + "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", "Delete %(count)s sessions|other": "Delete %(count)s sessions", "Delete %(count)s sessions|one": "Delete %(count)s session", @@ -1831,7 +1837,7 @@ "Please enter the code it contains:": "Please enter the code it contains:", "Code": "Code", "Submit": "Submit", - "Single Sign On": "Single Sign On", + "Confirm": "Confirm", "Start authentication": "Start authentication", "Unable to validate homeserver/identity server": "Unable to validate homeserver/identity server", "Your Modular server": "Your Modular server", @@ -1862,7 +1868,6 @@ "Use lowercase letters, numbers, dashes and underscores only": "Use lowercase letters, numbers, dashes and underscores only", "Enter username": "Enter username", "Email (optional)": "Email (optional)", - "Confirm": "Confirm", "Phone (optional)": "Phone (optional)", "Create your Matrix account on %(serverName)s": "Create your Matrix account on %(serverName)s", "Create your Matrix account on ": "Create your Matrix account on ", From 6112d92f809c952fe80520a93b0f98d0993057d3 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 30 Mar 2020 20:11:23 -0600 Subject: [PATCH 4/9] Remove debugging --- src/components/views/dialogs/InteractiveAuthDialog.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/dialogs/InteractiveAuthDialog.js b/src/components/views/dialogs/InteractiveAuthDialog.js index c070cc65890..af5dc5108c9 100644 --- a/src/components/views/dialogs/InteractiveAuthDialog.js +++ b/src/components/views/dialogs/InteractiveAuthDialog.js @@ -94,7 +94,6 @@ export default createReactClass({ }, _onUpdateStagePhase: function(newStage, newPhase) { - console.log({newStage, newPhase}); // We copy the stage and stage phase params into state for title selection in render() this.setState({uiaStage: newStage, uiaStagePhase: newPhase}); }, From de7952015086e8e5d3bab204fd23665def9435c2 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 30 Mar 2020 20:18:52 -0600 Subject: [PATCH 5/9] Add SSO dialog copy for 3PID adding (email/phone) --- src/AddThreepid.js | 38 +++++++++++++++++++++++++++++++++++++ src/i18n/strings/en_EN.json | 12 +++++++++--- 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/src/AddThreepid.js b/src/AddThreepid.js index 7a3250d0caf..1fc492ef851 100644 --- a/src/AddThreepid.js +++ b/src/AddThreepid.js @@ -21,6 +21,7 @@ import * as sdk from './index'; import Modal from './Modal'; import { _t } from './languageHandler'; import IdentityAuthClient from './IdentityAuthClient'; +import {SSOAuthEntry} from "./components/views/auth/InteractiveAuthEntryComponents"; function getIdServerDomain() { return MatrixClientPeg.get().idBaseUrl.split("://")[1]; @@ -188,11 +189,30 @@ export default class AddThreepid { // pop up an interactive auth dialog 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, { title: _t("Add Email Address"), matrixClient: MatrixClientPeg.get(), authData: e.data, makeRequest: this._makeAddThreepidOnlyRequest, + aestheticsForStagePhases: { + [SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics, + [SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics, + }, }); return finished; } @@ -285,11 +305,29 @@ export default class AddThreepid { // pop up an interactive auth dialog 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, { title: _t("Add Phone Number"), matrixClient: MatrixClientPeg.get(), authData: e.data, makeRequest: this._makeAddThreepidOnlyRequest, + aestheticsForStagePhases: { + [SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics, + [SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics, + }, }); return finished; } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index ecc2698f6d6..572268f90d0 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1,8 +1,17 @@ { "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", + "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", "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", "The platform you're on": "The platform you're on", "The version of Riot": "The version of Riot", @@ -598,9 +607,7 @@ "up to date": "up to date", "Your homeserver does not support session management.": "Your homeserver does not support session management.", "Unable to load session list": "Unable to load session list", - "Use Single Sign On to continue": "Use Single Sign On to continue", "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.", - "Single Sign On": "Single Sign On", "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", @@ -1837,7 +1844,6 @@ "Please enter the code it contains:": "Please enter the code it contains:", "Code": "Code", "Submit": "Submit", - "Confirm": "Confirm", "Start authentication": "Start authentication", "Unable to validate homeserver/identity server": "Unable to validate homeserver/identity server", "Your Modular server": "Your Modular server", From 8bc86deaaaf93928c722a077bd17ece980a0821d Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 30 Mar 2020 20:23:34 -0600 Subject: [PATCH 6/9] Appease the style linter --- res/css/views/elements/_AccessibleButton.scss | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/res/css/views/elements/_AccessibleButton.scss b/res/css/views/elements/_AccessibleButton.scss index 0c48a07ccfe..de39525588b 100644 --- a/res/css/views/elements/_AccessibleButton.scss +++ b/res/css/views/elements/_AccessibleButton.scss @@ -48,7 +48,8 @@ limitations under the License. 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; } @@ -74,8 +75,7 @@ limitations under the License. } .mx_AccessibleButton_kind_danger.mx_AccessibleButton_disabled, -mx_AccessibleButton_kind_danger_outline.mx_AccessibleButton_disabled -{ +.mx_AccessibleButton_kind_danger_outline.mx_AccessibleButton_disabled { color: $button-danger-disabled-fg-color; background-color: $button-danger-disabled-bg-color; } From d899576578b6b2bfc66ceef7a8b13c857c8857f3 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 30 Mar 2020 20:24:53 -0600 Subject: [PATCH 7/9] Appease the linter --- src/AddThreepid.js | 6 ++++-- src/components/views/auth/InteractiveAuthEntryComponents.js | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/AddThreepid.js b/src/AddThreepid.js index 1fc492ef851..f06f7c187dc 100644 --- a/src/AddThreepid.js +++ b/src/AddThreepid.js @@ -193,7 +193,8 @@ export default class AddThreepid { 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."), + body: _t("Confirm adding this email address by using " + + "Single Sign On to prove your identity."), continueText: _t("Single Sign On"), continueKind: "primary", }, @@ -308,7 +309,8 @@ export default class AddThreepid { 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."), + body: _t("Confirm adding this phone number by using " + + "Single Sign On to prove your identity."), continueText: _t("Single Sign On"), continueKind: "primary", }, diff --git a/src/components/views/auth/InteractiveAuthEntryComponents.js b/src/components/views/auth/InteractiveAuthEntryComponents.js index d262a5525d2..a59b1354b6f 100644 --- a/src/components/views/auth/InteractiveAuthEntryComponents.js +++ b/src/components/views/auth/InteractiveAuthEntryComponents.js @@ -651,7 +651,7 @@ export class SSOAuthEntry extends React.Component { this.props.submitAuthDict({}); }; - render () { + render() { let continueButton = null; const cancelButton = ( Date: Tue, 31 Mar 2020 08:29:42 -0600 Subject: [PATCH 8/9] Update src/components/views/auth/InteractiveAuthEntryComponents.js Co-Authored-By: David Baker --- src/components/views/auth/InteractiveAuthEntryComponents.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/auth/InteractiveAuthEntryComponents.js b/src/components/views/auth/InteractiveAuthEntryComponents.js index a59b1354b6f..14a8791cda2 100644 --- a/src/components/views/auth/InteractiveAuthEntryComponents.js +++ b/src/components/views/auth/InteractiveAuthEntryComponents.js @@ -639,7 +639,7 @@ export class SSOAuthEntry extends React.Component { 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 loosing application + // certainly will need to open the thing in a new tab to avoid losing application // context. window.open(this.state.ssoUrl, '_blank'); From 64c11c35650fe37a6cbf7edb115f11383a91ef89 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 31 Mar 2020 09:26:17 -0600 Subject: [PATCH 9/9] Move ssoUrl to a class property --- .../auth/InteractiveAuthEntryComponents.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/components/views/auth/InteractiveAuthEntryComponents.js b/src/components/views/auth/InteractiveAuthEntryComponents.js index 14a8791cda2..4e2f4448445 100644 --- a/src/components/views/auth/InteractiveAuthEntryComponents.js +++ b/src/components/views/auth/InteractiveAuthEntryComponents.js @@ -619,16 +619,19 @@ export class SSOAuthEntry extends React.Component { 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 = { - // We actually send the user through fallback auth so we don't have to - // deal with a redirect back to us, losing application context. - ssoUrl: props.matrixClient.getFallbackAuthUrl( - this.props.loginType, - this.props.authSessionId, - ), phase: SSOAuthEntry.PHASE_PREAUTH, }; } @@ -642,7 +645,7 @@ export class SSOAuthEntry extends React.Component { // certainly will need to open the thing in a new tab to avoid losing application // context. - window.open(this.state.ssoUrl, '_blank'); + window.open(this._ssoUrl, '_blank'); this.setState({phase: SSOAuthEntry.PHASE_POSTAUTH}); this.props.onPhaseChange(SSOAuthEntry.PHASE_POSTAUTH); };