diff --git a/.github/workflows/deploy-demo.yml b/.github/workflows/deploy-demo.yml index bf9757493..139c8c4c4 100644 --- a/.github/workflows/deploy-demo.yml +++ b/.github/workflows/deploy-demo.yml @@ -54,6 +54,7 @@ jobs: ADMIN_CLIENT_SECRET: ${{ secrets.ADMIN_CLIENT_SECRET }} NEW_RELIC_LICENSE_KEY: ${{ secrets.NEW_RELIC_LICENSE_KEY }} NR_BROWSER_KEY: ${{ secrets.NR_BROWSER_KEY }} + COMMIT_HASH: ${{ github.sha }} LOGIN_PEM: ${{ secrets.LOGIN_PEM }} LOGIN_DOT_GOV_CLIENT_ID: "urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:notify-gov" LOGIN_DOT_GOV_USER_INFO_URL: "https://secure.login.gov/api/openid_connect/userinfo" diff --git a/.github/workflows/deploy-prod.yml b/.github/workflows/deploy-prod.yml index a0aaa81c5..683ec5b70 100644 --- a/.github/workflows/deploy-prod.yml +++ b/.github/workflows/deploy-prod.yml @@ -54,6 +54,7 @@ jobs: ADMIN_CLIENT_SECRET: ${{ secrets.ADMIN_CLIENT_SECRET }} NEW_RELIC_LICENSE_KEY: ${{ secrets.NEW_RELIC_LICENSE_KEY }} NR_BROWSER_KEY: ${{ secrets.NR_BROWSER_KEY }} + COMMIT_HASH: ${{ github.sha }} LOGIN_PEM: ${{ secrets.LOGIN_PEM }} LOGIN_DOT_GOV_CLIENT_ID: "urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:notify-gov" LOGIN_DOT_GOV_USER_INFO_URL: "https://secure.login.gov/api/openid_connect/userinfo" diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 915994d58..f620bd662 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -60,6 +60,7 @@ jobs: ADMIN_CLIENT_SECRET: ${{ secrets.ADMIN_CLIENT_SECRET }} NEW_RELIC_LICENSE_KEY: ${{ secrets.NEW_RELIC_LICENSE_KEY }} NR_BROWSER_KEY: ${{ secrets.NR_BROWSER_KEY }} + COMMIT_HASH: ${{ github.sha }} LOGIN_PEM: ${{ secrets.LOGIN_PEM }} LOGIN_DOT_GOV_CLIENT_ID: "urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:notify-gov" LOGIN_DOT_GOV_USER_INFO_URL: "https://secure.login.gov/api/openid_connect/userinfo" @@ -81,6 +82,7 @@ jobs: --var ADMIN_CLIENT_SECRET="$ADMIN_CLIENT_SECRET" --var NEW_RELIC_LICENSE_KEY="$NEW_RELIC_LICENSE_KEY" --var NR_BROWSER_KEY="$NR_BROWSER_KEY" + --var COMMIT_HASH="$COMMIT_HASH" --var LOGIN_PEM="$LOGIN_PEM" --var LOGIN_DOT_GOV_CLIENT_ID="$LOGIN_DOT_GOV_CLIENT_ID" --var LOGIN_DOT_GOV_USER_INFO_URL="$LOGIN_DOT_GOV_USER_INFO_URL" diff --git a/README.md b/README.md index e98fe4535..0d080c9d8 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +![notify-logo](https://github.com/GSA/notifications-admin/assets/4156602/31b74039-4d87-4f89-a35e-71e9f3152342) + # Notify.gov Admin UI This is the Notify front-end for government users and admins. To see it in diff --git a/app/assets/error_pages/5xx.html b/app/assets/error_pages/5xx.html index 48cbe9e61..d9d58530f 100644 --- a/app/assets/error_pages/5xx.html +++ b/app/assets/error_pages/5xx.html @@ -22,7 +22,7 @@ - + @@ -107,4 +107,4 @@ - \ No newline at end of file + diff --git a/app/assets/images/android-chrome-192x192.png b/app/assets/images/android-chrome-192x192.png index 6eaf9fa46..a1e8670ca 100644 Binary files a/app/assets/images/android-chrome-192x192.png and b/app/assets/images/android-chrome-192x192.png differ diff --git a/app/assets/images/android-chrome-256x256.png b/app/assets/images/android-chrome-256x256.png new file mode 100644 index 000000000..40c599492 Binary files /dev/null and b/app/assets/images/android-chrome-256x256.png differ diff --git a/app/assets/images/apple-touch-icon.png b/app/assets/images/apple-touch-icon.png index b9ebb51ff..97c6d0eff 100644 Binary files a/app/assets/images/apple-touch-icon.png and b/app/assets/images/apple-touch-icon.png differ diff --git a/app/assets/images/favicon-16x16.png b/app/assets/images/favicon-16x16.png index d29295064..0660e0a75 100644 Binary files a/app/assets/images/favicon-16x16.png and b/app/assets/images/favicon-16x16.png differ diff --git a/app/assets/images/favicon-32x32.png b/app/assets/images/favicon-32x32.png index deb773a13..5d7d55940 100644 Binary files a/app/assets/images/favicon-32x32.png and b/app/assets/images/favicon-32x32.png differ diff --git a/app/assets/images/favicon.ico b/app/assets/images/favicon.ico index 457e971cd..de59f534f 100644 Binary files a/app/assets/images/favicon.ico and b/app/assets/images/favicon.ico differ diff --git a/app/assets/images/mstile-150x150.png b/app/assets/images/mstile-150x150.png new file mode 100644 index 000000000..c9f44c8b0 Binary files /dev/null and b/app/assets/images/mstile-150x150.png differ diff --git a/app/assets/images/notify-bell.svg b/app/assets/images/notify-bell.svg new file mode 100644 index 000000000..3b6a267ae --- /dev/null +++ b/app/assets/images/notify-bell.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/app/assets/images/notify-dark-favicon.png b/app/assets/images/notify-dark-favicon.png new file mode 100644 index 000000000..2dd4a2603 Binary files /dev/null and b/app/assets/images/notify-dark-favicon.png differ diff --git a/app/assets/images/notify-logo-dark.svg b/app/assets/images/notify-logo-dark.svg new file mode 100644 index 000000000..798ce99d9 --- /dev/null +++ b/app/assets/images/notify-logo-dark.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/app/assets/images/notify-logo.png b/app/assets/images/notify-logo.png new file mode 100644 index 000000000..326d959b6 Binary files /dev/null and b/app/assets/images/notify-logo.png differ diff --git a/app/assets/images/notify-logo.svg b/app/assets/images/notify-logo.svg new file mode 100644 index 000000000..3afb2dd92 --- /dev/null +++ b/app/assets/images/notify-logo.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/app/assets/images/notify-og-image.png b/app/assets/images/notify-og-image.png new file mode 100644 index 000000000..9ca227562 Binary files /dev/null and b/app/assets/images/notify-og-image.png differ diff --git a/app/assets/images/safari-pinned-tab.svg b/app/assets/images/safari-pinned-tab.svg new file mode 100644 index 000000000..580be22c2 --- /dev/null +++ b/app/assets/images/safari-pinned-tab.svg @@ -0,0 +1,24 @@ + + + + +Created by potrace 1.14, written by Peter Selinger 2001-2017 + + + + + + + diff --git a/app/assets/images/site.webmanifest b/app/assets/images/site.webmanifest index 45dc8a206..de65106f4 100644 --- a/app/assets/images/site.webmanifest +++ b/app/assets/images/site.webmanifest @@ -1 +1,19 @@ -{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} \ No newline at end of file +{ + "name": "", + "short_name": "", + "icons": [ + { + "src": "/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/android-chrome-256x256.png", + "sizes": "256x256", + "type": "image/png" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} diff --git a/app/assets/javascripts/loginAlert.js b/app/assets/javascripts/loginAlert.js new file mode 100644 index 000000000..84c2ee6a8 --- /dev/null +++ b/app/assets/javascripts/loginAlert.js @@ -0,0 +1,35 @@ +(function (window) { + +// Set the target date (10 days before March 15th, 2024) + const targetDate = new Date("April 16, 2024 00:00:00").getTime(); + + // Function to update the countdown display + function updateCountdown() { + const now = new Date().getTime(); + const difference = targetDate - now; + + // Time calculations for days only + const days = Math.floor(difference / (1000 * 60 * 60 * 24)); + + // Visibility logic + if (days < 0 || days > 10) { + // Hide if more than 10 days away OR if already past the date + document.getElementById("countdown-container").style.display = "none"; + } else { + // Show if 10 days or less remaining + document.getElementById("countdown-container").style.display = "block"; + document.getElementById("countdown").innerHTML = days + " days "; + } + + } + + // Expose the updateCountdown function to the outside world + window.updateCountdown = updateCountdown; + + // Initial display update + updateCountdown(); + + // Update the countdown every second (inside the IIFE) + setInterval(updateCountdown, 1000); + +})(window); diff --git a/app/assets/sass/uswds/_legacy-styles.scss b/app/assets/sass/uswds/_legacy-styles.scss index 599c3860f..0f16e8861 100644 --- a/app/assets/sass/uswds/_legacy-styles.scss +++ b/app/assets/sass/uswds/_legacy-styles.scss @@ -48,13 +48,13 @@ } } -.sms-message-sender { - margin: units(1) 0 0; +.sms-message-sender, .sms-message-file-name, .sms-message-scheduler, .sms-message-template, .sms-message-sender { + margin:0.25rem 0 0; } .sms-message-recipient { color: color('gray-cool-90'); - margin: 0 0 units(1); + margin: units(1) 0 units(1); } .sms-message-status { diff --git a/app/assets/sass/uswds/_uswds-theme-custom-styles.scss b/app/assets/sass/uswds/_uswds-theme-custom-styles.scss index 1d55791df..54bcd55c8 100644 --- a/app/assets/sass/uswds/_uswds-theme-custom-styles.scss +++ b/app/assets/sass/uswds/_uswds-theme-custom-styles.scss @@ -22,27 +22,46 @@ i.e. @use "uswds-core" as *; +iframe:focus, [href]:focus, [tabindex]:focus, [contentEditable=true]:focus { + outline: units(1px) dotted color('blue-40v'); + outline: units(1px) auto color('blue-40v'); + outline-offset: 0.3rem; +} + .usa-header--extended { .usa-logo { font-family: family("sans"); - margin: units(4) 0; - @include at-media-max('mobile-lg') { + margin: units(3) 0; + @include at-media-max('desktop') { margin: units(4) 0 units(4) units(2); } - img { - @include at-media($theme-header-min-width) { - width: 80px; - height: 70px; + a { + display: flex; + img { + height: 35px; + @include at-media('desktop') { + height: 60px; + } } } } .usa-nav__secondary { .usa-nav__link { - padding: 0; - &.usa-current { - text-decoration: underline; - } + padding: 0; + &.usa-current { + text-decoration: underline; + } } + @include at-media-max('desktop') { + padding: 0 units(2); + ul li { + padding-bottom: units(1); + } + } + } + .usa-nav-container { + max-width: 100%; + padding: 0; } } @@ -52,18 +71,23 @@ i.e. .usa-nav__primary { &> .usa-nav__primary-item > a { - font-size: size("body", 4); + font-size: size("body", 5); + font-weight: 400; + cursor: pointer; } - // &> .usa-nav__primary-item:last-child { - // margin-left: auto; - // @include u-margin-right(-4); - // } } h1 { font-weight: bold !important; } +.usa-section--dark h1 { + color: color('blue-30v'); + @include at-media-max('desktop') { + font-size: size("body", 12); + } +} + p, .list li { font-family: family("sans"); @@ -345,6 +369,9 @@ td.table-empty-message { background-image: url(../img/material-icons/description.svg); } } + .table-wrapper { + overflow-x: scroll; + } } .dashboard-table { @@ -555,3 +582,9 @@ details form { .edit-textbox-error-mt { margin-top: 1.5rem; } + +// Login page + +#countdown-container { + display: none; // Hide the countdown timer +} diff --git a/app/config.py b/app/config.py index 58623f426..ba89ce6bf 100644 --- a/app/config.py +++ b/app/config.py @@ -36,6 +36,7 @@ class Config(object): NR_BROWSER_KEY = getenv("NR_BROWSER_KEY") settings = newrelic.agent.global_settings() NR_MONITOR_ON = settings and settings.monitor_mode + COMMIT_HASH = getenv("COMMIT_HASH", "--------")[0:7] TEMPLATE_PREVIEW_API_HOST = getenv( "TEMPLATE_PREVIEW_API_HOST", "http://localhost:9999" diff --git a/app/main/forms.py b/app/main/forms.py index 2e6655cc5..70dbfa37f 100644 --- a/app/main/forms.py +++ b/app/main/forms.py @@ -1226,7 +1226,11 @@ class CsvUploadForm(StripWhitespaceForm): validators=[ DataRequired(message="Please pick a file"), CsvFileValidator(), - FileSize(max_size=10e6, message="File must be smaller than 10Mb"), # 10Mb + FileSize( + max_size=10e6, + message="File must be smaller than 10Mb. If you are trying to upload an Excel file, \ + please export the contents in the CSV format and then try again.", + ), # 10Mb ], ) diff --git a/app/main/views/index.py b/app/main/views/index.py index f4e3a67db..e6107ab10 100644 --- a/app/main/views/index.py +++ b/app/main/views/index.py @@ -102,15 +102,6 @@ def security(): return render_template("views/security.html", navigation_links=features_nav()) -@main.route("/features/terms", endpoint="terms") -@user_is_logged_in -def terms(): - return render_template( - "views/terms-of-use.html", - navigation_links=features_nav(), - ) - - @main.route("/features/using_notify") @user_is_logged_in def using_notify(): @@ -214,7 +205,6 @@ def send_files_by_email(): @main.route("/roadmap", endpoint="old_roadmap") -@main.route("/terms", endpoint="old_terms") @main.route("/information-security", endpoint="information_security") @main.route("/using_notify", endpoint="old_using_notify") @main.route("/information-risk-management", endpoint="information_risk_management") @@ -222,7 +212,6 @@ def send_files_by_email(): def old_page_redirects(): redirects = { "main.old_roadmap": "main.roadmap", - "main.old_terms": "main.terms", "main.information_security": "main.using_notify", "main.old_using_notify": "main.using_notify", "main.information_risk_management": "main.security", diff --git a/app/main/views/jobs.py b/app/main/views/jobs.py index afaaf6465..c1d0c2d9b 100644 --- a/app/main/views/jobs.py +++ b/app/main/views/jobs.py @@ -277,12 +277,16 @@ def get_status_filters(service, message_type, statistics): } else: stats = statistics[message_type] - stats["sending"] = stats["requested"] - stats["delivered"] - stats["failed"] + + if stats.get("failure") is not None: + stats["failed"] = stats["failure"] + + stats["pending"] = stats["requested"] - stats["delivered"] - stats["failed"] filters = [ # key, label, option ("requested", "total", "sending,delivered,failed"), - ("sending", "pending", "pending"), + ("pending", "pending", "pending"), ("delivered", "delivered", "delivered"), ("failed", "failed", "failed"), ] @@ -297,7 +301,7 @@ def get_status_filters(service, message_type, statistics): message_type=message_type, status=option, ), - stats[key], + stats.get(key), ) for key, label, option in filters ] diff --git a/app/main/views/send.py b/app/main/views/send.py index 3ca38171b..3aa5bd830 100644 --- a/app/main/views/send.py +++ b/app/main/views/send.py @@ -1,4 +1,3 @@ -import itertools import time import uuid from string import ascii_uppercase @@ -6,6 +5,7 @@ from zipfile import BadZipFile from flask import abort, flash, redirect, render_template, request, session, url_for from flask_login import current_user +from markupsafe import Markup from notifications_python_client.errors import HTTPError from notifications_utils import SMS_CHAR_COUNT_LIMIT from notifications_utils.insensitive_dict import InsensitiveDict @@ -151,8 +151,11 @@ def send_messages(service_id, template_id): # just show the first error, as we don't expect the form to have more # than one, since it only has one field first_field_errors = list(form.errors.values())[0] - flash(first_field_errors[0]) - + error_message = '' + error_message = f"{error_message}{first_field_errors[0]}" + error_message = f"{error_message}" + error_message = Markup(error_message) + flash(error_message) column_headings = get_spreadsheet_column_headings_from_template(template) return render_template( @@ -504,23 +507,38 @@ def _check_messages(service_id, template_id, upload_id, preview_row): template = get_template( db_template, current_service, - show_recipient=True, + show_recipient=False, email_reply_to=email_reply_to, sms_sender=sms_sender, ) + simplifed_template = get_template( + db_template, + current_service, + show_recipient=False, + ) + + allow_list = [] + if current_service.trial_mode: + # Adding the simulated numbers to allow list + # so they can be sent in trial mode + for user in Users(service_id): + allow_list.extend([user.name, user.mobile_number, user.email_address]) + # Failed sms number + allow_list.extend( + ["simulated user (fail)", "+14254147167", "simulated@simulated.gov"] + ) + # Success sms number + allow_list.extend( + ["simulated user (success)", "+14254147755", "simulatedtwo@simulated.gov"] + ) + else: + allow_list = None recipients = RecipientCSV( contents, - template=template, + template=template or simplifed_template, max_initial_rows_shown=50, max_errors_shown=50, - guestlist=( - itertools.chain.from_iterable( - [user.name, user.mobile_number, user.email_address] - for user in Users(service_id) - ) - if current_service.trial_mode - else None - ), + guestlist=allow_list, remaining_messages=remaining_messages, allow_international_sms=current_service.has_permission("international_sms"), ) @@ -530,11 +548,20 @@ def _check_messages(service_id, template_id, upload_id, preview_row): back_link = url_for( "main.send_one_off", service_id=service_id, template_id=template.id ) + back_link_from_preview = url_for( + "main.send_one_off", service_id=service_id, template_id=template.id + ) choose_time_form = None else: back_link = url_for( "main.send_messages", service_id=service_id, template_id=template.id ) + back_link_from_preview = url_for( + "main.check_messages", + service_id=service_id, + template_id=template.id, + upload_id=upload_id, + ) choose_time_form = ChooseTimeForm() if preview_row < 2: @@ -542,6 +569,9 @@ def _check_messages(service_id, template_id, upload_id, preview_row): if preview_row < len(recipients) + 2: template.values = recipients[preview_row - 2].recipient_and_personalisation + simplifed_template.values = recipients[ + preview_row - 2 + ].recipient_and_personalisation elif preview_row > 2: abort(404) @@ -562,11 +592,14 @@ def _check_messages(service_id, template_id, upload_id, preview_row): remaining_messages=remaining_messages, choose_time_form=choose_time_form, back_link=back_link, + back_link_from_preview=back_link_from_preview, first_recipient_column=recipients.recipient_column_headers[0], preview_row=preview_row, sent_previously=job_api_client.has_sent_previously( service_id, template.id, db_template["version"], original_file_name ), + template_id=template_id, + simplifed_template=simplifed_template, ) @@ -614,13 +647,34 @@ def check_messages(service_id, template_id, upload_id, row_index=2): return render_template("views/check/ok.html", **data) +@main.route( + "/services///check//preview", + methods=["POST"], +) +@main.route( + "/services///check//preview/row-", + methods=["POST"], +) +@user_has_permissions("send_messages", restrict_admin_usage=True) +def preview_job(service_id, template_id, upload_id, row_index=2): + session["scheduled_for"] = request.form.get("scheduled_for", "") + data = _check_messages(service_id, template_id, upload_id, row_index) + + return render_template( + "views/check/preview.html", + scheduled_for=session["scheduled_for"], + **data, + ) + + @main.route("/services//start-job/", methods=["POST"]) @user_has_permissions("send_messages", restrict_admin_usage=True) def start_job(service_id, upload_id): + scheduled_for = session.pop("scheduled_for", None) job_api_client.create_job( upload_id, service_id, - scheduled_for=request.form.get("scheduled_for", ""), + scheduled_for=scheduled_for, ) session.pop("sender_id", None) @@ -679,7 +733,20 @@ def get_send_test_page_title(template_type, entering_recipient, name=None): return "Personalize this message" -def get_back_link(service_id, template, step_index, placeholders=None): +def get_back_link( + service_id, + template, + step_index, + placeholders=None, + preview=False, +): + if preview: + return url_for( + "main.check_notification", + service_id=service_id, + template_id=template.id, + ) + if step_index == 0: if should_skip_template_page(template._template): return url_for( @@ -779,11 +846,18 @@ def _check_notification(service_id, template_id, exception=None): email_reply_to=email_reply_to, sms_sender=sms_sender, ) - + simplifed_template = get_template( + db_template, + current_service, + ) placeholders = fields_to_fill_in(template) back_link = get_back_link(service_id, template, len(placeholders), placeholders) + back_link_from_preview = get_back_link( + service_id, template, len(placeholders), placeholders, preview=True + ) + choose_time_form = ChooseTimeForm() if (not session.get("recipient")) or not all_placeholders_in_session( @@ -797,8 +871,10 @@ def _check_notification(service_id, template_id, exception=None): return dict( template=template, back_link=back_link, + back_link_from_preview=back_link_from_preview, choose_time_form=choose_time_form, **(get_template_error_dict(exception) if exception else {}), + simplifed_template=simplifed_template, ) @@ -828,12 +904,39 @@ def get_template_error_dict(exception): } +@main.route( + "/services//template//notification/check/preview", + methods=["POST"], +) +@user_has_permissions("send_messages", restrict_admin_usage=True) +def preview_notification(service_id, template_id): + recipient = get_recipient() + if not recipient: + return redirect( + url_for( + ".send_one_off", + service_id=service_id, + template_id=template_id, + ) + ) + + session["scheduled_for"] = request.form.get("scheduled_for", "") + + return render_template( + "views/notifications/preview.html", + **_check_notification(service_id, template_id), + scheduled_for=session["scheduled_for"], + recipient=recipient, + ) + + @main.route( "/services//template//notification/check", methods=["POST"], ) @user_has_permissions("send_messages", restrict_admin_usage=True) def send_notification(service_id, template_id): + scheduled_for = session.pop("scheduled_for", "") recipient = get_recipient() if not recipient: return redirect( @@ -868,7 +971,7 @@ def send_notification(service_id, template_id): job_api_client.create_job( upload_id, service_id, - scheduled_for=request.form.get("scheduled_for", ""), + scheduled_for=scheduled_for, template_id=template_id, original_file_name=filename, notification_count=1, diff --git a/app/main/views/sign_in.py b/app/main/views/sign_in.py index c6eaee87e..d39cb89af 100644 --- a/app/main/views/sign_in.py +++ b/app/main/views/sign_in.py @@ -53,22 +53,19 @@ def _get_access_token(code, state): # JWT expiration time (10 minute maximum) "exp": int(time.time()) + (10 * 60), } - current_app.logger.warning(f"Here is the raw payload {payload}") token = jwt.encode(payload, keystring, algorithm="RS256") base_url = f"{access_token_url}?" cli_assert = f"client_assertion={token}" cli_assert_type = "client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer" code_param = f"code={code}" url = f"{base_url}{cli_assert}&{cli_assert_type}&{code_param}&grant_type=authorization_code" - current_app.logger.info(f"This is the url we use to get the access token: {url}") headers = {"Authorization": "Bearer %s" % token} response = requests.post(url, headers=headers) - current_app.logger.info(f"GOT A RESPONSE {response.json()}") access_token = response.json()["access_token"] return access_token -def _get_user_email(access_token): +def _get_user_email_and_uuid(access_token): headers = {"Authorization": "Bearer %s" % access_token} user_info_url = os.getenv("LOGIN_DOT_GOV_USER_INFO_URL") user_attributes = requests.get( @@ -76,7 +73,8 @@ def _get_user_email(access_token): headers=headers, ) user_email = user_attributes.json()["email"] - return user_email + user_uuid = user_attributes.json()["sub"] + return user_email, user_uuid @main.route("/sign-in", methods=(["GET", "POST"])) @@ -88,11 +86,11 @@ def sign_in(): login_gov_error = request.args.get("error") if code and state: access_token = _get_access_token(code, state) - user_email = _get_user_email(access_token) + user_email, user_uuid = _get_user_email_and_uuid(access_token) redirect_url = request.args.get("next") # activate the user - user = user_api_client.get_user_by_email(user_email) + user = user_api_client.get_user_by_uuid_or_email(user_uuid, user_email) activate_user(user["id"]) return redirect(url_for("main.show_accounts_or_dashboard", next=redirect_url)) diff --git a/app/main/views/sub_navigation_dictionaries.py b/app/main/views/sub_navigation_dictionaries.py index f76d69c14..5e32bc003 100644 --- a/app/main/views/sub_navigation_dictionaries.py +++ b/app/main/views/sub_navigation_dictionaries.py @@ -18,10 +18,6 @@ def features_nav(): "name": "Security", "link": "main.security", }, - { - "name": "Terms of use", - "link": "main.terms", - }, ] diff --git a/app/navigation.py b/app/navigation.py index 94f970540..3c79598cc 100644 --- a/app/navigation.py +++ b/app/navigation.py @@ -45,7 +45,6 @@ class HeaderNavigation(Navigation): "features_sms", "roadmap", "security", - "terms", }, "using_notify": { "get_started", diff --git a/app/notify_client/user_api_client.py b/app/notify_client/user_api_client.py index bfe2f7182..a17059e52 100644 --- a/app/notify_client/user_api_client.py +++ b/app/notify_client/user_api_client.py @@ -44,6 +44,16 @@ class UserApiClient(NotifyAdminAPIClient): user_data = self.post("/user/email", data={"email": email_address}) return user_data["data"] + def get_user_by_uuid_or_email(self, user_uuid, email_address): + + user_data = self.post( + "/user/get-login-gov-user", + data={"login_uuid": user_uuid, "email": email_address}, + ) + if user_data is None: + raise Exception("User not found") + return user_data["data"] + def get_user_by_email_or_none(self, email_address): try: return self.get_user_by_email(email_address) diff --git a/app/templates/admin_template.html b/app/templates/admin_template.html index c48f768ce..cfa92a7e0 100644 --- a/app/templates/admin_template.html +++ b/app/templates/admin_template.html @@ -21,7 +21,7 @@ {% endblock %} {% block meta %} - + {% endblock %} {% endblock %} @@ -175,11 +175,13 @@ {% endblock %} {% block footer %} - + {% if current_service and current_service.research_mode %} {% set meta_suffix = 'Built by the Technology Transformation Servicesresearch mode' %} {% else %} - {% set meta_suffix = 'Built by the Technology Transformation Services' %} + {% set commit_hash = ", Latest version: " + config['COMMIT_HASH'] %} + {% set long_link = 'Technology Transformation Services' %} + {% set meta_suffix = "Built by the " + long_link + commit_hash %} {% endif %} {{ usaFooter({ @@ -201,10 +203,6 @@ "href": url_for("main.security"), "text": "Security" }, - { - "href": url_for("main.terms"), - "text": "Terms of use" - }, ] }, { @@ -244,7 +242,7 @@ { "href": url_for('main.support'), "text": "Contact us" - }, + } ] }, ], @@ -253,7 +251,7 @@ "html": meta_suffix } }) }} - + {% if current_user.is_authenticated %} {% block sessionUserWarning %} diff --git a/app/templates/components/components/header/template.njk b/app/templates/components/components/header/template.njk index 6cc8cabd8..c0620d502 100644 --- a/app/templates/components/components/header/template.njk +++ b/app/templates/components/components/header/template.njk @@ -44,61 +44,62 @@
-
- - {% if current_user.is_authenticated %} - - {% endif %} -
- + +
diff --git a/app/templates/content_template.html b/app/templates/content_template.html index abca0be7e..34b488bb5 100644 --- a/app/templates/content_template.html +++ b/app/templates/content_template.html @@ -11,10 +11,10 @@
{% if navigation_links %} -
+
{{ sub_navigation(navigation_links) }}
-
+
{% else %}
{% endif %} diff --git a/app/templates/main_nav.html b/app/templates/main_nav.html index 65c875d7a..2f7306f09 100644 --- a/app/templates/main_nav.html +++ b/app/templates/main_nav.html @@ -1,7 +1,7 @@ {% if help %} {% include 'partials/tour.html' %} {% else %} -