Merge pull request #5578 from matrix-org/t3chguy/sso_fixes

Improve SSO auth flow
This commit is contained in:
Michael Telatynski 2021-01-29 13:28:53 +00:00 committed by GitHub
commit 62712159f6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 228 additions and 63 deletions

View file

@ -609,8 +609,12 @@ export class SSOAuthEntry extends React.Component {
this.props.authSessionId,
);
this._popupWindow = null;
window.addEventListener("message", this._onReceiveMessage);
this.state = {
phase: SSOAuthEntry.PHASE_PREAUTH,
attemptFailed: false,
};
}
@ -618,12 +622,35 @@ export class SSOAuthEntry extends React.Component {
this.props.onPhaseChange(SSOAuthEntry.PHASE_PREAUTH);
}
componentWillUnmount() {
window.removeEventListener("message", this._onReceiveMessage);
if (this._popupWindow) {
this._popupWindow.close();
this._popupWindow = null;
}
}
attemptFailed = () => {
this.setState({
attemptFailed: true,
});
};
_onReceiveMessage = event => {
if (event.data === "authDone" && event.origin === this.props.matrixClient.getHomeserverUrl()) {
if (this._popupWindow) {
this._popupWindow.close();
this._popupWindow = null;
}
}
};
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._popupWindow = window.open(this._ssoUrl, "_blank");
this.setState({phase: SSOAuthEntry.PHASE_POSTAUTH});
this.props.onPhaseChange(SSOAuthEntry.PHASE_POSTAUTH);
};
@ -656,10 +683,28 @@ export class SSOAuthEntry extends React.Component {
);
}
return <div className='mx_InteractiveAuthEntryComponents_sso_buttons'>
{cancelButton}
{continueButton}
</div>;
let errorSection;
if (this.props.errorText) {
errorSection = (
<div className="error" role="alert">
{ this.props.errorText }
</div>
);
} else if (this.state.attemptFailed) {
errorSection = (
<div className="error" role="alert">
{ _t("Something went wrong in confirming your identity. Cancel and try again.") }
</div>
);
}
return <React.Fragment>
{ errorSection }
<div className="mx_InteractiveAuthEntryComponents_sso_buttons">
{cancelButton}
{continueButton}
</div>
</React.Fragment>;
}
}
@ -710,8 +755,7 @@ export class FallbackAuthEntry extends React.Component {
this.props.loginType,
this.props.authSessionId,
);
this._popupWindow = window.open(url);
this._popupWindow.opener = null;
this._popupWindow = window.open(url, "_blank");
};
_onReceiveMessage = event => {

View file

@ -34,6 +34,7 @@ import InteractiveAuthDialog from '../InteractiveAuthDialog';
export default class CreateCrossSigningDialog extends React.PureComponent {
static propTypes = {
accountPassword: PropTypes.string,
tokenLogin: PropTypes.bool,
};
constructor(props) {
@ -96,6 +97,9 @@ export default class CreateCrossSigningDialog extends React.PureComponent {
user: MatrixClientPeg.get().getUserId(),
password: this.state.accountPassword,
});
} else if (this.props.tokenLogin) {
// We are hoping the grace period is active
await makeRequest({});
} else {
const dialogAesthetics = {
[SSOAuthEntry.PHASE_PREAUTH]: {
@ -144,6 +148,12 @@ export default class CreateCrossSigningDialog extends React.PureComponent {
});
this.props.onFinished(true);
} catch (e) {
if (this.props.tokenLogin) {
// ignore any failures, we are relying on grace period here
this.props.onFinished();
return;
}
this.setState({ error: e });
console.error("Error bootstrapping cross-signing", e);
}

View file

@ -22,13 +22,33 @@ import {MatrixClient} from "matrix-js-sdk/src/client";
import PlatformPeg from "../../../PlatformPeg";
import AccessibleButton from "./AccessibleButton";
import {_t} from "../../../languageHandler";
import {IIdentityProvider, ISSOFlow} from "../../../Login";
import {IdentityProviderBrand, IIdentityProvider, ISSOFlow} from "../../../Login";
import AccessibleTooltipButton from "./AccessibleTooltipButton";
interface ISSOButtonProps extends Omit<IProps, "flow"> {
idp: IIdentityProvider;
mini?: boolean;
}
const getIcon = (brand: IdentityProviderBrand | string) => {
switch (brand) {
case IdentityProviderBrand.Apple:
return require(`../../../../res/img/element-icons/brands/apple.svg`);
case IdentityProviderBrand.Facebook:
return require(`../../../../res/img/element-icons/brands/facebook.svg`);
case IdentityProviderBrand.Github:
return require(`../../../../res/img/element-icons/brands/github.svg`);
case IdentityProviderBrand.Gitlab:
return require(`../../../../res/img/element-icons/brands/gitlab.svg`);
case IdentityProviderBrand.Google:
return require(`../../../../res/img/element-icons/brands/google.svg`);
case IdentityProviderBrand.Twitter:
return require(`../../../../res/img/element-icons/brands/twitter.svg`);
default:
return null;
}
}
const SSOButton: React.FC<ISSOButtonProps> = ({
matrixClient,
loginType,
@ -38,7 +58,6 @@ const SSOButton: React.FC<ISSOButtonProps> = ({
mini,
...props
}) => {
const kind = primary ? "primary" : "primary_outline";
const label = idp ? _t("Continue with %(provider)s", { provider: idp.name }) : _t("Sign in with single sign-on");
const onClick = () => {
@ -46,30 +65,35 @@ const SSOButton: React.FC<ISSOButtonProps> = ({
};
let icon;
if (typeof idp?.icon === "string" && (idp.icon.startsWith("mxc://") || idp.icon.startsWith("https://"))) {
icon = <img
src={matrixClient.mxcUrlToHttp(idp.icon, 24, 24, "crop", true)}
height="24"
width="24"
alt={label}
/>;
let brandClass;
const brandIcon = idp ? getIcon(idp.brand) : null;
if (brandIcon) {
const brandName = idp.brand.split(".").pop();
brandClass = `mx_SSOButton_brand_${brandName}`;
icon = <img src={brandIcon} height="24" width="24" alt={brandName} />;
} else if (typeof idp?.icon === "string" && idp.icon.startsWith("mxc://")) {
const src = matrixClient.mxcUrlToHttp(idp.icon, 24, 24, "crop", true);
icon = <img src={src} height="24" width="24" alt={idp.name} />;
}
const classes = classNames("mx_SSOButton", {
[brandClass]: brandClass,
mx_SSOButton_mini: mini,
mx_SSOButton_default: !idp,
mx_SSOButton_primary: primary,
});
if (mini) {
// TODO fallback icon
return (
<AccessibleButton {...props} className={classes} kind={kind} onClick={onClick}>
<AccessibleTooltipButton {...props} title={label} className={classes} onClick={onClick}>
{ icon }
</AccessibleButton>
</AccessibleTooltipButton>
);
}
return (
<AccessibleButton {...props} className={classes} kind={kind} onClick={onClick}>
<AccessibleButton {...props} className={classes} onClick={onClick}>
{ icon }
{ label }
</AccessibleButton>