Skip to content

Commit

Permalink
[ALS-6107] Add login blocking alert and application banners (#173)
Browse files Browse the repository at this point in the history
  • Loading branch information
srpiatt authored and Luke-Sikina committed Mar 21, 2024
1 parent 1035957 commit abb1b14
Show file tree
Hide file tree
Showing 10 changed files with 179 additions and 18 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

All notable changes to this project will be documented in this file. As our fork has diverged from AWS SWB mainline branch, we are noting the SWB version and the lab version together, as <swb version>\_<lab version>, starting from SWB mainline, 5.0.0.

## [5.0.0_1.4.2](https://github.com/hms-dbmi/service-workbench-on-aws/compare/v5.0.0_1.4.1...v5.0.0_1.4.2) (03/14/2024)
- Emergency change to add login-blocking warning and system wide application warnings.

## [5.0.0_1.4.1](https://github.com/hms-dbmi/service-workbench-on-aws/compare/v5.0.0_1.4.0...v5.0.0_1.4.1) (03/01/2024)
- Add copy changes and parameter for registration page.
- Add PIC-SURE landing page if stage parameter is set.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,42 @@ const cognitoType = {
$id: '#/properties/customRegister',
type: 'boolean',
},
appAlerts: {
$id: '#/properties/appAlerts',
type: 'object',
properties: {
loginBlocking: {
$id: '#/properties/appAlerts/loginBlocking',
type: 'string',
},
banner: {
$id: '#/properties/appAlerts/banner',
type: 'array',
items: {
$id: '#/properties/appAlerts/banner/items',
type: 'object',
properties: {
title: {
$id: '#/properties/appAlerts/banner/items/title',
type: 'string',
},
text: {
$id: '#/properties/appAlerts/banner/items/text',
type: 'string',
},
type: {
$id: '#/properties/appAlerts/banner/items/type',
type: 'string',
},
dismissable: {
$id: '#/properties/appAlerts/banner/items/dismissable',
type: 'boolean',
},
},
},
},
},
},
federatedIdentityProviders: {
$id: '#/properties/providerConfig/properties/federatedIdentityProviders',
type: 'array',
Expand Down Expand Up @@ -362,6 +398,7 @@ const publicConfigurations = [
'https://test-raas1.auth.us-east-1.amazoncognito.com/oauth2/authorize?response_type=token&client_id=199999999991&redirect_uri=https://12345.cloudfront.net&idp_identifier=datalake.example.com',
signOutUri:
'https://test-raas1.auth.us-east-1.amazoncognito.com/logout?client_id=199999999991&logout_uri=https://12345.cloudfront.net',
customRegister: false,
},
{
id: 'https://cognito-idp.us-east-1.amazonaws.com/us-east-1_poolId2',
Expand All @@ -374,6 +411,7 @@ const publicConfigurations = [
clientId: '28888888888882',
enableNativeUserPoolUsers: false,
customRegister: false,
appAlerts: undefined,
},
{
id: 'datalake2.example.com',
Expand All @@ -384,6 +422,7 @@ const publicConfigurations = [
'https://test-raas2.auth.us-east-1.amazoncognito.com/login?response_type=token&client_id=28888888888882&redirect_uri=https://12345.cloudfront.net&idp_identifier=datalake2.example.com',
signOutUri:
'https://test-raas2.auth.us-east-1.amazoncognito.com/logout?client_id=28888888888882&logout_uri=https://12345.cloudfront.net',
customRegister: false,
},
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ async function configure(context) {
credentialHandlingType: provider.config.type.config.credentialHandlingType,
signInUri: provider.config.signInUri,
signOutUri: provider.config.signOutUri,
customRegister: provider.config.customRegister || false,
appAlerts: provider.appAlerts,
};

if (provider.config.type.type !== cognitoAuthType) {
Expand All @@ -58,7 +60,6 @@ async function configure(context) {
userPoolId: provider.config.userPoolId,
clientId: provider.config.clientId,
enableNativeUserPoolUsers: provider.config.enableNativeUserPoolUsers,
customRegister: provider.config.customRegister || false,
};

if (cognitoPublicInfo.enableNativeUserPoolUsers) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const deSerializeProviderConfig = providerConfigStr => JSON.parse(providerConfig

const toProviderConfig = dbResultItem =>
_.assign({}, dbResultItem, {
appAlerts: dbResultItem.appAlerts,
config: dbResultItem && deSerializeProviderConfig(dbResultItem.config),
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,42 @@
"$id": "#/properties/customRegister",
"type": "boolean"
},
"appAlerts": {
"$id": "#/properties/appAlerts",
"type": "object",
"properties": {
"loginBlocking": {
"$id": "#/properties/appAlerts/loginBlocking",
"type": "string"
},
"banner": {
"$id": "#/properties/appAlerts/banner",
"type": "array",
"items": {
"$id": "#/properties/appAlerts/banner/items",
"type": "object",
"properties": {
"title": {
"$id": "#/properties/appAlerts/banner/items/title",
"type": "string"
},
"text": {
"$id": "#/properties/appAlerts/banner/items/text",
"type": "string"
},
"type": {
"$id": "#/properties/appAlerts/banner/items/type",
"type": "string"
},
"dismissable": {
"$id": "#/properties/appAlerts/banner/items/dismissable",
"type": "boolean"
}
}
}
}
}
},
"federatedIdentityProviders": {
"$id": "#/properties/providerConfig/properties/federatedIdentityProviders",
"type": "array",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,18 @@ function adjustRedirectUri(uri, redirectType = 'login') {
return adjustedUri;
}

const BannerAlert = types.model({
title: types.maybeNull(types.string),
text: types.string,
type: types.optional(types.string, 'info'),
dismissable: types.optional(types.boolean, true),
});

const AppAlerts = types.model({
loginBlocking: types.maybeNull(types.string),
banner: types.optional(types.array(BannerAlert), []),
});

const AuthenticationProviderPublicConfig = types
.model('AuthenticationProviderPublicConfig', {
id: '',
Expand All @@ -61,6 +73,7 @@ const AuthenticationProviderPublicConfig = types
signOutUri: '',
enableNativeUserPoolUsers: types.maybeNull(types.boolean),
customRegister: types.maybeNull(types.boolean),
appAlerts: types.maybeNull(AppAlerts),
})
.actions(self => ({
cleanup() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ const AuthenticationProviderPublicConfigsStore = BaseStore.named('Authentication
const configs = self.authenticationProviderPublicConfigs || [];
return configs.find(({ type }) => type === nativeUserPool) || {};
},
get loginBlocking() {
return _.get(this.nativeUserPool, 'appAlerts.loginBlocking', false);
},
get bannerAlerts() {
return _.get(this.nativeUserPool, 'appAlerts.banner', []).slice();
},
toAuthenticationProviderFromId(authenticationProviderId) {
return _.find(self.authenticationProviderPublicConfigs, { id: authenticationProviderId });
},
Expand Down
30 changes: 19 additions & 11 deletions addons/addon-base-ui/packages/base-ui/src/parts/Login.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,26 @@ class Login extends React.Component {
);
});

renderBrandingHeader = () =>
this.props.Header ? (
this.props.Header
) : (
<>
<Image centered src={this.props.assets.images.loginImage} />
<Header as="h3" textAlign="center">
{branding.login.title}
<Header.Subheader>{branding.login.subtitle}</Header.Subheader>
</Header>
</>
);

render() {
const loginBlocking = this.props.authenticationProviderPublicConfigsStore.loginBlocking;
const BrandingHeader = this.renderBrandingHeader();
if (loginBlocking) {
return <BrandingHeader copy={branding.login} />;
}

const error = !!(this.usernameError || this.passwordError || this.authenticationProviderError);

const authenticationProviderOptions = this.getStore().authenticationProviderOptions;
Expand Down Expand Up @@ -161,17 +180,6 @@ class Login extends React.Component {

const additionalLoginComponents = this.props.AdditionalLoginComponents || (() => <></>);
const collectUserNamePassword = this.props.authentication.shouldCollectUserNamePassword;
const BrandingHeader = this.props.Header ? (
this.props.Header
) : (
<>
<Image centered src={this.props.assets.images.loginImage} />
<Header as="h3" textAlign="center">
{branding.login.title}
<Header.Subheader>{branding.login.subtitle}</Header.Subheader>
</Header>
</>
);

return (
<div className="login-form animated fadeIn">
Expand Down
54 changes: 51 additions & 3 deletions addons/addon-base-ui/packages/base-ui/src/parts/MainLayout.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@

import _ from 'lodash';
import React from 'react';
import { decorate, action } from 'mobx';
import { decorate, action, observable, runInAction } from 'mobx';
import { inject, observer } from 'mobx-react';
import { withRouter } from 'react-router-dom';
import { Menu, Icon, Image } from 'semantic-ui-react';
import { Menu, Icon, Image, Message } from 'semantic-ui-react';

import { createLink } from '../helpers/routing';
import { displayError } from '../helpers/notification';
Expand All @@ -27,6 +27,21 @@ import { branding, versionAndDate } from '../helpers/settings';
// expected props
// - userStore (via injection)
class MainLayout extends React.Component {
constructor(props) {
super(props);

runInAction(() => {
this.banners = this.props.authenticationProviderPublicConfigsStore.bannerAlerts;
});
}

handleDismiss = bannerText => {
const index = this.banners.findIndex(({ text }) => text === bannerText);
if (index !== -1) {
this.banners.splice(index, 1);
}
};

goto = pathname => () => {
const location = this.props.location;
const link = createLink({
Expand Down Expand Up @@ -107,6 +122,32 @@ class MainLayout extends React.Component {
}}
key="ml3"
>
{this.banners.length > 0 && (
<div className="ui container mt3">
{this.banners.map(({ title, text, type, dismissable }) => {
const icons = {
error: 'exclamation triangle',
info: 'info circle',
success: 'check circle',
warning: 'exclamation triangle',
};
return (
<Message
className="mb2"
key={text}
negative={type === 'error'}
info={type === 'info'}
success={type === 'success'}
warning={type === 'warning'}
icon={icons[type] || false}
onDismiss={dismissable ? () => this.handleDismiss(text) : undefined}
header={title}
content={text}
/>
);
})}
</div>
)}
{this.props.children}
</div>,
];
Expand All @@ -115,7 +156,14 @@ class MainLayout extends React.Component {

// see https://medium.com/@mweststrate/mobx-4-better-simpler-faster-smaller-c1fbc08008da
decorate(MainLayout, {
banners: observable,
handleLogout: action,
handleDismiss: action,
});

export default inject('authentication', 'userStore', 'assets')(withRouter(observer(MainLayout)));
export default inject(
'authentication',
'userStore',
'assets',
'authenticationProviderPublicConfigsStore',
)(withRouter(observer(MainLayout)));
12 changes: 9 additions & 3 deletions addons/addon-custom/packages/main/src/parts/BrandingHeader.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import React from 'react';
import { inject } from 'mobx-react';

import { Grid, Image, Header } from 'semantic-ui-react';
import { Grid, Image, Header, Message } from 'semantic-ui-react';
import { branding } from '@aws-ee/base-ui/dist/helpers/settings';

import { renderHTML } from '../helpers/utils';

export function BrandingHeader({ copy, assets, picsureBoxes = true }) {
export function BrandingHeader({ copy, assets, picsureBoxes = true, authenticationProviderPublicConfigsStore }) {
const borders = { margin: '0px 10px', border: 'solid #2A5FA3 2px', borderRadius: '4px', padding: '10px' };
const maxImageWidth = { height: 'auto', maxWidth: '350px', margin: 'auto' };
const loginBlocking = authenticationProviderPublicConfigsStore.loginBlocking || false;

return (
<>
Expand All @@ -28,6 +29,11 @@ export function BrandingHeader({ copy, assets, picsureBoxes = true }) {
<Header as="h2" textAlign="center" className="header">
{copy.title}
</Header>
{loginBlocking && (
<div className="mb2">
<Message negative icon="exclamation triangle" content={loginBlocking} />
</div>
)}
{renderHTML(copy.subtitle)}
</div>
</Grid.Column>
Expand Down Expand Up @@ -63,4 +69,4 @@ export function BrandingHeader({ copy, assets, picsureBoxes = true }) {
);
}

export default inject('assets')(BrandingHeader);
export default inject('assets', 'authenticationProviderPublicConfigsStore')(BrandingHeader);

0 comments on commit abb1b14

Please sign in to comment.