From a2717242660d03fb614b05d039e900987af7c2d9 Mon Sep 17 00:00:00 2001 From: AMStrix Date: Thu, 7 Mar 2019 14:04:40 -0600 Subject: [PATCH 1/2] BE: add SESSION_COOKIE_DOMAIN setting (#311) --- admin/src/components/MFAuth/index.tsx | 2 +- backend/.env.example | 3 +++ backend/grant/settings.py | 5 ++++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/admin/src/components/MFAuth/index.tsx b/admin/src/components/MFAuth/index.tsx index b236a6c4..704ab183 100644 --- a/admin/src/components/MFAuth/index.tsx +++ b/admin/src/components/MFAuth/index.tsx @@ -173,7 +173,7 @@ class MFAuth extends React.Component {
  1. Save two-factor recovery codes
  2. - Setup up TOTP authentication device, typically a smartphone with Google + Setup TOTP authentication device, typically a smartphone with Google Authenticator, Authy, 1Password or other compatible authenticator app.
diff --git a/backend/.env.example b/backend/.env.example index 0c54b4ec..2ab25783 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -7,6 +7,9 @@ REDISTOGO_URL="redis://localhost:6379" SECRET_KEY="not-so-secret" SENDGRID_API_KEY="optional, but emails won't send without it" +# set this so third-party cookie blocking doesn't kill backend sessions (production) +# SESSION_COOKIE_DOMAIN="zfnd.org" + # SENTRY_DSN="https://PUBLICKEY@sentry.io/PROJECTID" # SENTRY_RELEASE="optional, provides sentry logging with release info" diff --git a/backend/grant/settings.py b/backend/grant/settings.py index 1dcf6701..cabb52eb 100644 --- a/backend/grant/settings.py +++ b/backend/grant/settings.py @@ -16,7 +16,7 @@ ENV = env.str("FLASK_ENV", default="production") DEBUG = ENV == "development" SITE_URL = env.str('SITE_URL', default='https://zfnd.org') SQLALCHEMY_DATABASE_URI = env.str("DATABASE_URL") -SQLALCHEMY_ECHO = False # True will print queries to log +SQLALCHEMY_ECHO = False # True will print queries to log QUEUES = ["default"] SECRET_KEY = env.str("SECRET_KEY") BCRYPT_LOG_ROUNDS = env.int("BCRYPT_LOG_ROUNDS", default=13) @@ -25,6 +25,9 @@ DEBUG_TB_INTERCEPT_REDIRECTS = False CACHE_TYPE = "simple" # Can be "memcached", "redis", etc. SQLALCHEMY_TRACK_MODIFICATIONS = False +# so backend session cookies are first-party +SESSION_COOKIE_DOMAIN = env.str('SESSION_COOKIE_DOMAIN', default=None) + SENDGRID_API_KEY = env.str("SENDGRID_API_KEY", default="") SENDGRID_DEFAULT_FROM = "noreply@zfnd.org" From 1905dfe7bfa4ec4402a50784a5ee64a49196fb01 Mon Sep 17 00:00:00 2001 From: William O'Beirne Date: Fri, 8 Mar 2019 16:13:13 -0500 Subject: [PATCH 2/2] Frontend error handlers (#312) * Add componentDidCatch around routes with template, and outside routes at the top level. * Remove error from ErrorWrap on navigation. --- frontend/client/Routes.tsx | 10 ++-- .../client/components/ErrorScreen/index.less | 52 ++++++++++++++++++ .../client/components/ErrorScreen/index.tsx | 53 +++++++++++++++++++ frontend/client/components/ErrorWrap.tsx | 45 ++++++++++++++++ frontend/client/components/Footer/style.less | 5 +- frontend/client/components/Header/style.less | 4 +- frontend/client/index.tsx | 17 +++--- frontend/client/pages/proposal.tsx | 3 -- 8 files changed, 171 insertions(+), 18 deletions(-) create mode 100644 frontend/client/components/ErrorScreen/index.less create mode 100644 frontend/client/components/ErrorScreen/index.tsx create mode 100644 frontend/client/components/ErrorWrap.tsx diff --git a/frontend/client/Routes.tsx b/frontend/client/Routes.tsx index 03425289..c39d2d36 100644 --- a/frontend/client/Routes.tsx +++ b/frontend/client/Routes.tsx @@ -11,6 +11,9 @@ import { import loadable from '@loadable/component'; import AuthRoute from 'components/AuthRoute'; import Template, { TemplateProps } from 'components/Template'; +import ErrorWrap from 'components/ErrorWrap'; +import Loader from 'components/Loader'; +import 'styles/style.less'; // wrap components in loadable...import & they will be split // Make sure you specify chunkname! Must replace slashes with dashes. @@ -38,9 +41,6 @@ const ArbiterEmail = loadable(() => import('pages/email-arbiter'), opts); const RFP = loadable(() => import('pages/rfp'), opts); const RFPs = loadable(() => import('pages/rfps'), opts); -import 'styles/style.less'; -import Loader from 'components/Loader'; - interface RouteConfig extends RouteProps { route: RouteProps; template: TemplateProps; @@ -336,7 +336,9 @@ class Routes extends React.PureComponent { return ( ); } diff --git a/frontend/client/components/ErrorScreen/index.less b/frontend/client/components/ErrorScreen/index.less new file mode 100644 index 00000000..6c5280e2 --- /dev/null +++ b/frontend/client/components/ErrorScreen/index.less @@ -0,0 +1,52 @@ +@import '~styles/variables.less'; + +.ErrorScreen { + padding: 2rem 0; + + &-desc { + font-size: 1.25rem; + line-height: 1.6; + + &-error { + display: block; + font-size: 1rem; + padding: 1rem; + margin-bottom: 1rem; + background: rgba(#000, 0.03); + border: 1px solid rgba(#000, 0.1); + border-radius: 4px; + } + } + + &-buttons { + .ant-btn { + margin-right: 1rem; + } + } + + // Antd overrides + .antd-pro-exception-exception { + min-height: 0; + + .antd-pro-exception-imgBlock { + flex: 0 0 45%; + width: 45%; + + @media @tablet-query { + display: none; + } + } + + .antd-pro-exception-content { + padding-right: 2rem; + + @media @tablet-query { + padding-right: 0; + } + + h1 { + font-size: 4.2rem; + } + } + } +} \ No newline at end of file diff --git a/frontend/client/components/ErrorScreen/index.tsx b/frontend/client/components/ErrorScreen/index.tsx new file mode 100644 index 00000000..dd424407 --- /dev/null +++ b/frontend/client/components/ErrorScreen/index.tsx @@ -0,0 +1,53 @@ +import React from 'react'; +import * as Sentry from '@sentry/browser'; +import { Button } from 'antd'; +import Exception from 'ant-design-pro/lib/Exception'; +import './index.less'; + +interface Props { + error: Error; +} + +export default class ErrorScreen extends React.PureComponent { + componentDidMount() { + const { error } = this.props; + Sentry.captureException(error); + console.error('Error screen showing due to the following error:', error); + } + + render() { + const { error } = this.props; + return ( +
+ +

Something went wrong, and we've logged the following error:

+ {error.message} +

+ Our developers will get right on fixing it. You can either return home and + try again, or open an issue on Github to provide us some more details. +

+
+ } + actions={ + + } + /> + + ); + } +} diff --git a/frontend/client/components/ErrorWrap.tsx b/frontend/client/components/ErrorWrap.tsx new file mode 100644 index 00000000..6ae20555 --- /dev/null +++ b/frontend/client/components/ErrorWrap.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import ErrorScreen from './ErrorScreen'; + +interface Props { + children: React.ReactNode; + isFullscreen?: boolean; +} + +interface State { + error: Error | null; +} + +export default class ErrorWrap extends React.Component { + state: State = { + error: null, + }; + + componentDidCatch(error: Error) { + this.setState({ error }); + } + + render() { + let style; + if (this.props.isFullscreen) { + style = { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + width: '100vw', + minHeight: '100vh', + padding: '0 2rem', + }; + } + + if (this.state.error) { + return ( +
+ +
+ ); + } else { + return this.props.children; + } + } +} diff --git a/frontend/client/components/Footer/style.less b/frontend/client/components/Footer/style.less index bc680427..f312d54e 100644 --- a/frontend/client/components/Footer/style.less +++ b/frontend/client/components/Footer/style.less @@ -51,8 +51,9 @@ } &-logo { - height: 1.8rem; - margin-bottom: 1rem; + height: 6.8rem; + margin-bottom: 0.5rem; + margin-top: -1.25rem; } } diff --git a/frontend/client/components/Header/style.less b/frontend/client/components/Header/style.less index cc053bd1..67dcc3d9 100644 --- a/frontend/client/components/Header/style.less +++ b/frontend/client/components/Header/style.less @@ -1,5 +1,5 @@ @import '~styles/variables.less'; -@header-height: 62px; +@header-height: 68px; @header-transition: 200ms; @link-padding: 0.7rem; @small-query: ~'(max-width: 820px)'; @@ -54,7 +54,7 @@ } &-logo { - height: 1.4rem; + height: 3.6rem; width: auto; transition: transform @header-transition ease; diff --git a/frontend/client/index.tsx b/frontend/client/index.tsx index 7462fa61..dc0f7c59 100644 --- a/frontend/client/index.tsx +++ b/frontend/client/index.tsx @@ -12,6 +12,7 @@ import history from 'store/history'; import { massageSerializedState } from 'utils/api'; import Routes from './Routes'; import i18n from './i18n'; +import ErrorWrap from 'components/ErrorWrap'; Sentry.init({ dsn: process.env.SENTRY_DSN, @@ -26,13 +27,15 @@ i18n.changeLanguage(i18nLanguage.locale); i18n.addResourceBundle(i18nLanguage.locale, 'common', i18nLanguage.resources, true); const App = hot(module)(() => ( - - - - - - - + + + + + + + + + )); loadableReady(() => { diff --git a/frontend/client/pages/proposal.tsx b/frontend/client/pages/proposal.tsx index 93f10476..1e033dc8 100644 --- a/frontend/client/pages/proposal.tsx +++ b/frontend/client/pages/proposal.tsx @@ -7,9 +7,6 @@ import { withRouter, RouteComponentProps } from 'react-router'; type RouteProps = RouteComponentProps; class ProposalPage extends Component { - constructor(props: RouteProps) { - super(props); - } render() { const proposalId = extractIdFromSlug(this.props.match.params.id); return ;