diff --git a/CHANGELOG.md b/CHANGELOG.md index 028dcfaa04..0aab6dbe70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 \_, 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. diff --git a/addons/addon-base-rest-api/packages/base-controllers/lib/__tests__/__fixtures__/authentication-provider-configs.js b/addons/addon-base-rest-api/packages/base-controllers/lib/__tests__/__fixtures__/authentication-provider-configs.js index 966ccb0976..54d30d8349 100644 --- a/addons/addon-base-rest-api/packages/base-controllers/lib/__tests__/__fixtures__/authentication-provider-configs.js +++ b/addons/addon-base-rest-api/packages/base-controllers/lib/__tests__/__fixtures__/authentication-provider-configs.js @@ -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', @@ -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', @@ -374,6 +411,7 @@ const publicConfigurations = [ clientId: '28888888888882', enableNativeUserPoolUsers: false, customRegister: false, + appAlerts: undefined, }, { id: 'datalake2.example.com', @@ -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, }, ]; diff --git a/addons/addon-base-rest-api/packages/base-controllers/lib/authentication-provider-public-controller.js b/addons/addon-base-rest-api/packages/base-controllers/lib/authentication-provider-public-controller.js index 92d4c97835..75e313c08a 100644 --- a/addons/addon-base-rest-api/packages/base-controllers/lib/authentication-provider-public-controller.js +++ b/addons/addon-base-rest-api/packages/base-controllers/lib/authentication-provider-public-controller.js @@ -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) { @@ -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) { diff --git a/addons/addon-base-rest-api/packages/services/lib/authentication-providers/authentication-provider-config-service.js b/addons/addon-base-rest-api/packages/services/lib/authentication-providers/authentication-provider-config-service.js index 71285ae873..11c62132b2 100644 --- a/addons/addon-base-rest-api/packages/services/lib/authentication-providers/authentication-provider-config-service.js +++ b/addons/addon-base-rest-api/packages/services/lib/authentication-providers/authentication-provider-config-service.js @@ -26,6 +26,7 @@ const deSerializeProviderConfig = providerConfigStr => JSON.parse(providerConfig const toProviderConfig = dbResultItem => _.assign({}, dbResultItem, { + appAlerts: dbResultItem.appAlerts, config: dbResultItem && deSerializeProviderConfig(dbResultItem.config), }); diff --git a/addons/addon-base-rest-api/packages/services/lib/authentication-providers/built-in-providers/cogito-user-pool/create-cognito-user-pool-schema.json b/addons/addon-base-rest-api/packages/services/lib/authentication-providers/built-in-providers/cogito-user-pool/create-cognito-user-pool-schema.json index 40ec554670..b5e5785b27 100644 --- a/addons/addon-base-rest-api/packages/services/lib/authentication-providers/built-in-providers/cogito-user-pool/create-cognito-user-pool-schema.json +++ b/addons/addon-base-rest-api/packages/services/lib/authentication-providers/built-in-providers/cogito-user-pool/create-cognito-user-pool-schema.json @@ -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", diff --git a/addons/addon-base-ui/packages/base-ui/src/models/authentication/AuthenticationProviderPublicConfig.js b/addons/addon-base-ui/packages/base-ui/src/models/authentication/AuthenticationProviderPublicConfig.js index 31a9788b19..d681ce818e 100644 --- a/addons/addon-base-ui/packages/base-ui/src/models/authentication/AuthenticationProviderPublicConfig.js +++ b/addons/addon-base-ui/packages/base-ui/src/models/authentication/AuthenticationProviderPublicConfig.js @@ -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: '', @@ -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() { diff --git a/addons/addon-base-ui/packages/base-ui/src/models/authentication/AuthenticationProviderPublicConfigsStore.js b/addons/addon-base-ui/packages/base-ui/src/models/authentication/AuthenticationProviderPublicConfigsStore.js index 46d0503fd3..36f545fdb9 100644 --- a/addons/addon-base-ui/packages/base-ui/src/models/authentication/AuthenticationProviderPublicConfigsStore.js +++ b/addons/addon-base-ui/packages/base-ui/src/models/authentication/AuthenticationProviderPublicConfigsStore.js @@ -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 }); }, diff --git a/addons/addon-base-ui/packages/base-ui/src/parts/Login.js b/addons/addon-base-ui/packages/base-ui/src/parts/Login.js index a450efc806..cd4a94200d 100644 --- a/addons/addon-base-ui/packages/base-ui/src/parts/Login.js +++ b/addons/addon-base-ui/packages/base-ui/src/parts/Login.js @@ -130,7 +130,26 @@ class Login extends React.Component { ); }); + renderBrandingHeader = () => + this.props.Header ? ( + this.props.Header + ) : ( + <> + +
+ {branding.login.title} + {branding.login.subtitle} +
+ + ); + render() { + const loginBlocking = this.props.authenticationProviderPublicConfigsStore.loginBlocking; + const BrandingHeader = this.renderBrandingHeader(); + if (loginBlocking) { + return ; + } + const error = !!(this.usernameError || this.passwordError || this.authenticationProviderError); const authenticationProviderOptions = this.getStore().authenticationProviderOptions; @@ -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 - ) : ( - <> - -
- {branding.login.title} - {branding.login.subtitle} -
- - ); return (
diff --git a/addons/addon-base-ui/packages/base-ui/src/parts/MainLayout.js b/addons/addon-base-ui/packages/base-ui/src/parts/MainLayout.js index 432d3e139a..e7d85ab0ba 100644 --- a/addons/addon-base-ui/packages/base-ui/src/parts/MainLayout.js +++ b/addons/addon-base-ui/packages/base-ui/src/parts/MainLayout.js @@ -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'; @@ -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({ @@ -107,6 +122,32 @@ class MainLayout extends React.Component { }} key="ml3" > + {this.banners.length > 0 && ( +
+ {this.banners.map(({ title, text, type, dismissable }) => { + const icons = { + error: 'exclamation triangle', + info: 'info circle', + success: 'check circle', + warning: 'exclamation triangle', + }; + return ( + this.handleDismiss(text) : undefined} + header={title} + content={text} + /> + ); + })} +
+ )} {this.props.children}
, ]; @@ -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))); diff --git a/addons/addon-custom/packages/main/src/parts/BrandingHeader.js b/addons/addon-custom/packages/main/src/parts/BrandingHeader.js index db585e5d4b..2e16e7d3fb 100644 --- a/addons/addon-custom/packages/main/src/parts/BrandingHeader.js +++ b/addons/addon-custom/packages/main/src/parts/BrandingHeader.js @@ -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 ( <> @@ -28,6 +29,11 @@ export function BrandingHeader({ copy, assets, picsureBoxes = true }) {
{copy.title}
+ {loginBlocking && ( +
+ +
+ )} {renderHTML(copy.subtitle)} @@ -63,4 +69,4 @@ export function BrandingHeader({ copy, assets, picsureBoxes = true }) { ); } -export default inject('assets')(BrandingHeader); +export default inject('assets', 'authenticationProviderPublicConfigsStore')(BrandingHeader);