diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index bf775fc63..da9a18367 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -44,17 +44,29 @@ jobs: run: npm test - name: Run py tests with coverage run: poetry run coverage run --omit=*/notifications_utils/* -m pytest --maxfail=10 --ignore=tests/end_to_end tests/ - - name: Run E2E tests - run: poetry run pytest -v --browser chromium --browser firefox --browser webkit tests/end_to_end - env: - NOTIFY_E2E_AUTH_STATE_PATH: ${{ secrets.NOTIFY_E2E_AUTH_STATE_PATH }} - NOTIFY_E2E_TEST_EMAIL: ${{ secrets.NOTIFY_E2E_TEST_EMAIL }} - NOTIFY_E2E_TEST_HTTP_AUTH_PASSWORD: ${{ secrets.NOTIFY_E2E_TEST_HTTP_AUTH_PASSWORD }} - NOTIFY_E2E_TEST_HTTP_AUTH_USER: ${{ secrets.NOTIFY_E2E_TEST_HTTP_AUTH_USER }} - NOTIFY_E2E_TEST_PASSWORD: ${{ secrets.NOTIFY_E2E_TEST_PASSWORD }} - NOTIFY_E2E_TEST_URI: ${{ secrets.NOTIFY_E2E_TEST_URI }} + # - name: Run E2E tests + # run: poetry run pytest -v --browser chromium --browser firefox --browser webkit tests/end_to_end + # env: + # NOTIFY_E2E_AUTH_STATE_PATH: ${{ secrets.NOTIFY_E2E_AUTH_STATE_PATH }} + # NOTIFY_E2E_TEST_EMAIL: ${{ secrets.NOTIFY_E2E_TEST_EMAIL }} + # NOTIFY_E2E_TEST_PASSWORD: ${{ secrets.NOTIFY_E2E_TEST_PASSWORD }} + # NOTIFY_E2E_TEST_URI: ${{ secrets.NOTIFY_E2E_TEST_URI }} - name: Check coverage threshold run: poetry run coverage report --fail-under=90 + # - name: Health check + # run: | + # response=$(curl -url ${{secrets.NOTIFY_E2E_TEST_URI}}_status) + # if grep -q "ok" <<< "$response"; then + # echo "Health check passed" + # else + # echo "Health check failed" + # exit 1 + # fi + # env: + # NOTIFY_E2E_AUTH_STATE_PATH: ${{ secrets.NOTIFY_E2E_AUTH_STATE_PATH }} + # NOTIFY_E2E_TEST_EMAIL: ${{ secrets.NOTIFY_E2E_TEST_EMAIL }} + # NOTIFY_E2E_TEST_PASSWORD: ${{ secrets.NOTIFY_E2E_TEST_PASSWORD }} + # NOTIFY_E2E_TEST_URI: ${{ secrets.NOTIFY_E2E_TEST_URI }} validate-new-relic-config: runs-on: ubuntu-latest diff --git a/.github/workflows/deploy-demo.yml b/.github/workflows/deploy-demo.yml index 6baeb7912..e4cf662de 100644 --- a/.github/workflows/deploy-demo.yml +++ b/.github/workflows/deploy-demo.yml @@ -52,7 +52,6 @@ jobs: DANGEROUS_SALT: ${{ secrets.DANGEROUS_SALT }} SECRET_KEY: ${{ secrets.SECRET_KEY }} ADMIN_CLIENT_SECRET: ${{ secrets.ADMIN_CLIENT_SECRET }} - BASIC_AUTH_PASSWORD: ${{ secrets.BASIC_AUTH_PASSWORD }} NEW_RELIC_LICENSE_KEY: ${{ secrets.NEW_RELIC_LICENSE_KEY }} NR_BROWSER_KEY: ${{ secrets.NR_BROWSER_KEY }} with: @@ -66,8 +65,6 @@ jobs: --var SECRET_KEY="$SECRET_KEY" --var ADMIN_CLIENT_USERNAME="notify-admin" --var ADMIN_CLIENT_SECRET="$ADMIN_CLIENT_SECRET" - --var BASIC_AUTH_USERNAME="curiousabout" - --var BASIC_AUTH_PASSWORD="$BASIC_AUTH_PASSWORD" --var NEW_RELIC_LICENSE_KEY="$NEW_RELIC_LICENSE_KEY" --var NR_BROWSER_KEY="$NR_BROWSER_KEY" diff --git a/.github/workflows/deploy-prod.yml b/.github/workflows/deploy-prod.yml index 5801d8e68..df6c5614e 100644 --- a/.github/workflows/deploy-prod.yml +++ b/.github/workflows/deploy-prod.yml @@ -52,7 +52,6 @@ jobs: DANGEROUS_SALT: ${{ secrets.DANGEROUS_SALT }} SECRET_KEY: ${{ secrets.SECRET_KEY }} ADMIN_CLIENT_SECRET: ${{ secrets.ADMIN_CLIENT_SECRET }} - BASIC_AUTH_PASSWORD: ${{ secrets.BASIC_AUTH_PASSWORD }} NEW_RELIC_LICENSE_KEY: ${{ secrets.NEW_RELIC_LICENSE_KEY }} NR_BROWSER_KEY: ${{ secrets.NR_BROWSER_KEY }} with: @@ -66,8 +65,6 @@ jobs: --var SECRET_KEY="$SECRET_KEY" --var ADMIN_CLIENT_USERNAME="notify-admin" --var ADMIN_CLIENT_SECRET="$ADMIN_CLIENT_SECRET" - --var BASIC_AUTH_USERNAME="curiousabout" - --var BASIC_AUTH_PASSWORD="$BASIC_AUTH_PASSWORD" --var NEW_RELIC_LICENSE_KEY="$NEW_RELIC_LICENSE_KEY" --var NR_BROWSER_KEY="$NR_BROWSER_KEY" diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index b754e327a..83c141370 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -57,7 +57,6 @@ jobs: DANGEROUS_SALT: ${{ secrets.DANGEROUS_SALT }} SECRET_KEY: ${{ secrets.SECRET_KEY }} ADMIN_CLIENT_SECRET: ${{ secrets.ADMIN_CLIENT_SECRET }} - BASIC_AUTH_PASSWORD: ${{ secrets.BASIC_AUTH_PASSWORD }} NEW_RELIC_LICENSE_KEY: ${{ secrets.NEW_RELIC_LICENSE_KEY }} NR_BROWSER_KEY: ${{ secrets.NR_BROWSER_KEY }} with: @@ -71,8 +70,6 @@ jobs: --var SECRET_KEY="$SECRET_KEY" --var ADMIN_CLIENT_USERNAME="notify-admin" --var ADMIN_CLIENT_SECRET="$ADMIN_CLIENT_SECRET" - --var BASIC_AUTH_USERNAME="curiousabout" - --var BASIC_AUTH_PASSWORD="$BASIC_AUTH_PASSWORD" --var NEW_RELIC_LICENSE_KEY="$NEW_RELIC_LICENSE_KEY" --var NR_BROWSER_KEY="$NR_BROWSER_KEY" diff --git a/app/__init__.py b/app/__init__.py index 5f5bb4680..7c5879a30 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -2,6 +2,7 @@ import os import pathlib from functools import partial from time import monotonic +from urllib.parse import urlparse, urlunparse import jinja2 from flask import ( @@ -36,7 +37,6 @@ from werkzeug.local import LocalProxy from app import proxy_fix from app.asset_fingerprinter import asset_fingerprinter from app.config import configs -from app.custom_auth import CustomBasicAuth from app.extensions import redis_client, zendesk_client from app.formatters import ( convert_to_boolean, @@ -121,7 +121,6 @@ from app.url_converters import ( login_manager = LoginManager() csrf = CSRFProtect() talisman = Talisman() -basic_auth = CustomBasicAuth() # The current service attached to the request stack. @@ -234,8 +233,6 @@ def create_app(application): login_manager.session_protection = None login_manager.anonymous_user = AnonymousUser - setup_basic_auth(application) - # make sure we handle unicode correctly redis_client.redis_store.decode_responses = True @@ -254,6 +251,7 @@ def create_app(application): def init_app(application): + application.before_request(redirect_notify_to_beta) application.before_request(load_service_before_request) application.before_request(load_organization_before_request) application.before_request(request_helper.check_proxy_header_before_request) @@ -332,6 +330,23 @@ def make_session_permanent(): session.permanent = True +def create_beta_url(url): + url_created = urlparse(url) + url_list = list(url_created) + url_list[1] = "beta.notify.gov" + url_for_redirect = urlunparse(url_list) + return url_for_redirect + + +def redirect_notify_to_beta(): + if ( + current_app.config["NOTIFY_ENVIRONMENT"] == "production" + and "beta.notify.gov" not in request.url + ): + url_to_beta = create_beta_url(request.url) + return redirect(url_to_beta, 302) + + def load_service_before_request(): if "/static/" in request.url: request_ctx.service = None @@ -565,7 +580,3 @@ def init_jinja(application): ] jinja_loader = jinja2.FileSystemLoader(template_folders) application.jinja_loader = jinja_loader - - -def setup_basic_auth(application): - application.basic_auth = CustomBasicAuth(application) diff --git a/app/assets/javascripts/fileUpload.js b/app/assets/javascripts/fileUpload.js index d12e554ed..9ef72d531 100644 --- a/app/assets/javascripts/fileUpload.js +++ b/app/assets/javascripts/fileUpload.js @@ -16,7 +16,7 @@ // The label gets styled like a button and is used to hide the native file upload control. This is so that // users see a button that looks like the others on the site. // - this.$form.find('label.file-upload-button').addClass('usa-button'); + this.$form.find('label.file-upload-button').addClass('usa-button margin-bottom-1'); // Clear the form if the user navigates back to the page $(window).on("pageshow", () => this.$form[0].reset()); diff --git a/app/assets/javascripts/radioSelect.js b/app/assets/javascripts/radioSelect.js index fc26013b2..e8e212257 100644 --- a/app/assets/javascripts/radioSelect.js +++ b/app/assets/javascripts/radioSelect.js @@ -9,48 +9,48 @@ let states = { 'initial': Hogan.compile(` {{#showNowAsDefault}} -
+
{{/showNowAsDefault}} -
+
{{#categories}} - + {{/categories}}
`), 'choose': Hogan.compile(` {{#showNowAsDefault}} -
+
{{/showNowAsDefault}} -
+
{{#choices}}
{{/choices}} - +
`), 'chosen': Hogan.compile(` {{#showNowAsDefault}} -
+
{{/showNowAsDefault}} -
+
{{#choices}}
@@ -58,8 +58,8 @@
{{/choices}}
-
- +
+
`) }; diff --git a/app/assets/sass/uswds/_legacy-styles.scss b/app/assets/sass/uswds/_legacy-styles.scss new file mode 100644 index 000000000..3b09e2218 --- /dev/null +++ b/app/assets/sass/uswds/_legacy-styles.scss @@ -0,0 +1,342 @@ +@use "uswds-core" as *; + +// SMS Box Style + +.sms-message-wrapper { + + position: relative; + width: 100%; + max-width: 464px; + box-sizing: border-box; + padding: units(1); + background: color('gray-cool-10'); + border: 1px solid color('gray-cool-10'); + border-radius: 5px; + white-space: normal; + margin: 0 0 units(1) 0; + clear: both; + word-wrap: break-word; + + &:after { + content: ""; + display: block; + position: absolute; + bottom: -5px; + right: -20px; + border: 10px solid transparent; + border-left-width: 13px; + border-right-width: 13px; + border-bottom-color: color('gray-cool-10'); + border-left-color: color('gray-cool-10'); + transform: rotate(17deg); + } + +} + +.sms-message-inbound { + + .sms-message-wrapper { + + &:after { + border-left-color: transparent; + border-bottom-color: color('gray-cool-10'); + border-right-color: color('gray-cool-10'); + right: auto; + left: -20px; + transform: rotate(17deg); + } + } +} + +.sms-message-sender { + margin: units(1) 0 0; +} + +.sms-message-recipient { + color: color('gray-cool-90'); + margin: 0 0 units(1); +} + +.sms-message-status { + color: color('gray-cool-90'); + margin: -20px units(1) 20px units(1); +} + +.sms-message-status-outbound { + text-align: right; +} + +.sms-message-row { + &:focus { + outline: none; + padding-top: 120px; + margin-top: -120px; + } +} + +.sms-message-reply-link { + text-align: right; +} + +// File upload area + +.page-footer { + position: relative; + margin-bottom: 30px; + &-link { + line-height: 40px; + padding: 1px 0 0 15px; + font-weight: normal; + &:first-of-type { + padding-left: 0px; + } + } + + &-delete-link-without-button { + padding: 0; + display: inline-block; + } + + &-secondary-link { + display: block; + margin-top: units(1); + } + + &-right-aligned-link { + position: absolute; + right: 0; + top: 9px; // align baseline with buttons + &-without-button { + position: absolute; + right: 0; + top: 0; + } + } + + &__button { + margin-right: 10px; + } + + .js-cancel { + margin: 0; + } + + &__button--centred { + display: block; + margin: 0 auto; + } +} + +.file-upload { + &-label, + &-button-label { + font-weight: bold; + font-size: 19px; + display: block; + margin: 0 0 10px 0; + } + + &-label .error-message, + &-button-label.error-message { + padding: 0; + } + + &-field { + margin-bottom: 10px; + } + + // Hide normal upload form if we're adding a custom version with JS + .js-enabled &-label, + .js-enabled &-field, + .js-enabled &-submit { + display: none; + } + + &-alternate-link { + display: inline-block; + line-height: 35px; + a { + font-weight: bold; + } + } +} + +.spreadsheet { + + margin-bottom: units (1); + + .table { + margin-bottom: 0; + } + + .usa-table--borderless thead th { + border-top: 1px solid color('gray-cool-10'); + } + + th, + .table-field-index { + background: color('gray-cool-10'); + border: 1px solid color('gray-cool-10'); + font-weight: bold; + text-align: center; + } + + th, td { + padding-left: 10px; + padding-right: 10px; + border: 1px solid color('gray-cool-10'); + } + + td { + border-top: 0; + // 194 is the width of the table * 1/3.5, so the overflow cuts off + // at 3.5 columns wide. + // 11 accounts for the padding of the table cell + min-width: 194px - 11px; + &:first-child { + min-width: auto; + } + } + + .fullscreen-fixed-table { + z-index: 1000; + .table-field-heading-first { + background: color('gray-cool-10'); + } + } +} + +.fullscreen { + &-content { + background: #FFFFFF; + z-index: 10; + overflow-y: hidden; + box-sizing: border-box; + margin: 0 0 units(1) 0; + padding: 0 0 0 0; + overflow: hidden; + border-bottom: 1px solid color('gray-cool-10'); + .table { + margin-bottom: 0; + tr:last-child { + td { + border-bottom: 1px solid #FFFFFF; + } + } + + } + + th, + .table-field-error-label, + .table-field-left-aligned { + white-space: nowrap; + } + } + + &-right-shadow { + position: absolute; + top: 0; + right: 0; + width: 4px; + height: 100%; + z-index: 200; + &.visible { + &.with-transition { + transition: box-shadow 0.6s ease-out; + } + box-shadow: inset -1px 0 0 0 color('gray-cool-10'), inset -3px 0 0 0 rgba(color('gray-cool-10'), 0.2); + } + } + + &-scrollable-table { + + overflow-x: auto; + overflow-y: hidden; + + .table-field-heading-first, + .table-field-index { + display: none; + } + + .table-field-left-aligned { + position: relative; + z-index: 150; + background: #FFFFFF; + } + + &::-webkit-scrollbar { + -webkit-appearance: none; + } + + &::-webkit-scrollbar:horizontal { + height: 11px; + background-color: #FFFFFF; + } + + &::-webkit-scrollbar-thumb { + border-radius: 8px; + border: 2px solid #FFFFFF; + background-color: rgba(0, 0, 0, .5); + } + + &::-webkit-scrollbar-track { + background-color: #FFFFFF; + border-radius: 8px; + } + + } + + &-fixed-table { + + position: absolute; + top: 0; + overflow: hidden; + + .table-field-heading { + visibility: hidden; + } + + .table-field-left-aligned { + width: 0; + position: relative; + z-index: 100; + visibility: hidden; + } + + .table-field-heading-first, + .table-field-index { + transition: none; + position: relative; + z-index: 200; + background: #FFFFFF; + } + } + + &-scrolled-table { + padding-bottom: 20px; + .table-field-heading-first, + .table-field-index { + transition: box-shadow 0.3s ease-in-out; + box-shadow: 1px 0 0 0 color('gray-cool-10'), 3px 0 0 0 rgba(color('gray-cool-10'), 0.2); + } + } + + &-shim { + width: 100%; + position: relative; + z-index: 9; + margin-bottom: 30px; + pointer-events: none; + & + .table-show-more-link { + margin-top: -20px; + } + } +} + +.js-focus-style { + outline: 3px solid color("blue-40v"); + box-shadow: 0 0 0 7px color("blue-40v"); + *:focus { + outline: none; + } +} diff --git a/app/assets/sass/uswds/_uswds-theme-custom-styles.scss b/app/assets/sass/uswds/_uswds-theme-custom-styles.scss index a2f148fc3..049ea8b3e 100644 --- a/app/assets/sass/uswds/_uswds-theme-custom-styles.scss +++ b/app/assets/sass/uswds/_uswds-theme-custom-styles.scss @@ -33,10 +33,19 @@ i.e. } } -.usa-nav__primary > .usa-nav__primary-item > a { - font-size: size("body", 4); +.usa-nav__primary { + &> .usa-nav__primary-item > a { + font-size: size("body", 4); + } + &> .usa-nav__primary-item:last-child { + margin-left: auto; + @include u-margin-right(-4); + } } +.usa-nav__primary + + h1 { font-weight: bold !important; } @@ -250,6 +259,9 @@ td.table-empty-message { background: color("green-cool-40v"); display: flex; padding: units(1) units(2); + &--failing { + background: color("red-warm-50v"); + } } } .usa-table { @@ -443,16 +455,3 @@ details form { padding-left: 5px; letter-spacing: 0.04em; } - -// .textbox-colour-preview { -// @include govuk-media-query($from: desktop) { -// width: 38px; -// height: 38px; -// margin-left: 5px; -// border-radius: 50%; -// box-shadow: inset 0 0 0 1px rgba(govuk-colour("black"), 0.2); -// display: inline-block; -// vertical-align: top; -// transition: background 0.3s ease-out; -// } -// } \ No newline at end of file diff --git a/app/assets/sass/uswds/styles.scss b/app/assets/sass/uswds/styles.scss index 23bd83c6f..3ef97c065 100644 --- a/app/assets/sass/uswds/styles.scss +++ b/app/assets/sass/uswds/styles.scss @@ -1,3 +1,4 @@ @forward "uswds-theme"; @forward "uswds"; -@forward "uswds-theme-custom-styles"; \ No newline at end of file +@forward "uswds-theme-custom-styles"; +@forward "legacy-styles"; \ No newline at end of file diff --git a/app/config.py b/app/config.py index a1f968359..6215a744d 100644 --- a/app/config.py +++ b/app/config.py @@ -28,8 +28,6 @@ class Config(object): # ZENDESK_API_KEY = getenv('ZENDESK_API_KEY') ROUTE_SECRET_KEY_1 = getenv("ROUTE_SECRET_KEY_1", "dev-route-secret-key-1") ROUTE_SECRET_KEY_2 = getenv("ROUTE_SECRET_KEY_2", "dev-route-secret-key-2") - BASIC_AUTH_USERNAME = getenv("BASIC_AUTH_USERNAME") - BASIC_AUTH_PASSWORD = getenv("BASIC_AUTH_PASSWORD") NR_ACCOUNT_ID = getenv("NR_ACCOUNT_ID") NR_TRUST_KEY = getenv("NR_TRUST_KEY") @@ -101,7 +99,6 @@ def _s3_credentials_from_env(bucket_prefix): class Development(Config): - BASIC_AUTH_FORCE = False DEBUG = True SESSION_COOKIE_SECURE = False SESSION_PROTECTION = None @@ -138,7 +135,6 @@ class Test(Development): class Production(Config): HEADER_COLOUR = "#005EA5" # $govuk-blue HTTP_PROTOCOL = "https" - BASIC_AUTH_FORCE = True ASSET_DOMAIN = "" # TODO use a CDN ASSET_PATH = "/static/" # TODO use a CDN DEBUG = False @@ -153,7 +149,6 @@ class Production(Config): class Staging(Production): - BASIC_AUTH_FORCE = True HEADER_COLOUR = "#00ff00" # $green @@ -166,7 +161,6 @@ class Sandbox(Staging): class Scanning(Production): - BASIC_AUTH_FORCE = False HTTP_PROTOCOL = "http" API_HOST_NAME = "https://notify-api-staging.app.cloud.gov/" SECRET_KEY = "dev-notify-secret-key" # nosec B105 - only used in development diff --git a/app/custom_auth.py b/app/custom_auth.py deleted file mode 100644 index 8f5186b2c..000000000 --- a/app/custom_auth.py +++ /dev/null @@ -1,18 +0,0 @@ -from flask import jsonify, request -from flask_basicauth import BasicAuth - - -class CustomBasicAuth(BasicAuth): - """ - Description: - Override BasicAuth to permit anonymous healthcheck at /_status?simple=true - """ - - def challenge(self): - if "/_status" in request.url: - if request.args.get("elb", None) or request.args.get("simple", None): - return jsonify(status="ok"), 200 - return super(CustomBasicAuth, self).challenge() - - -custom_basic_auth = CustomBasicAuth() diff --git a/app/templates/components/big-number.html b/app/templates/components/big-number.html index 5bf4baa08..52b0dadbe 100644 --- a/app/templates/components/big-number.html +++ b/app/templates/components/big-number.html @@ -39,7 +39,7 @@ {{ big_number(number, label, link=link, smaller=smaller, smallest=smallest) }} {% if show_failures %} - + {% if failures %} {% if failure_link %} diff --git a/app/templates/components/folder-path.html b/app/templates/components/folder-path.html index 6ba0108aa..c686702ba 100644 --- a/app/templates/components/folder-path.html +++ b/app/templates/components/folder-path.html @@ -6,7 +6,7 @@ link_current_item=False, root_element='h1' ) %} - <{{ root_element }} class="font-body-lg folder-heading"{% if root_element == 'h1' %} id="page-header"{% endif %}> + <{{ root_element }} class="font-body-lg folder-heading margin-bottom-0"{% if root_element == 'h1' %} id="page-header"{% endif %}> {% for folder in folders %} {% if loop.last and not link_current_item %} {% if folder.template_type or not folder.id %} diff --git a/app/templates/components/status-box.html b/app/templates/components/status-box.html index 1832fe1fd..5fbc5d78e 100644 --- a/app/templates/components/status-box.html +++ b/app/templates/components/status-box.html @@ -1,6 +1,6 @@ {% macro status_box(number, label, failing=false, percentage=None, url=None) %}
-
+
{% if url %} {{ number }} {{ label }} {% else %} diff --git a/app/templates/partials/count.html b/app/templates/partials/count.html index 902d38a1a..7915b31e1 100644 --- a/app/templates/partials/count.html +++ b/app/templates/partials/count.html @@ -3,7 +3,7 @@
{% if notifications_deleted %} -
+
{% for label, query_param, url, count in counts %} {% if query_param == 'pending' %}
{{ big_number(count, query_param, smaller=True) }}
@@ -14,7 +14,7 @@ {% endfor %}
{% else %} -
+
{{ pill(counts, request.args.get('status', '')) }}
{% endif %} diff --git a/app/templates/views/api/index.html b/app/templates/views/api/index.html index 8fa4d8d29..0bd3e2a94 100644 --- a/app/templates/views/api/index.html +++ b/app/templates/views/api/index.html @@ -12,7 +12,7 @@ API integration -