From a6280fcaef34109e68911a99454149655a43c630 Mon Sep 17 00:00:00 2001 From: Tim Donaworth Date: Wed, 13 Sep 2023 09:38:02 -0400 Subject: [PATCH 01/46] minor linting fix --- frontend/cypress/e2e/agreementDelete.cy.js | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/cypress/e2e/agreementDelete.cy.js b/frontend/cypress/e2e/agreementDelete.cy.js index 70578cccfe..e75d77bdd6 100644 --- a/frontend/cypress/e2e/agreementDelete.cy.js +++ b/frontend/cypress/e2e/agreementDelete.cy.js @@ -1,6 +1,7 @@ /// import { terminalLog, testLogin } from "./utils"; +// eslint-disable-next-line no-unused-vars const testAgreements = [ { agreement: 1, From beead1a55404d288bf4d735400a49b61614d9000 Mon Sep 17 00:00:00 2001 From: Tim Donaworth Date: Wed, 13 Sep 2023 09:41:06 -0400 Subject: [PATCH 02/46] refactor: :recycle: Created helper functions for Token retreival Created helper functions that should be used in place of the direct `localStorage` access; as this may change in the future. The functions will always wrap the recent methods for token retreival. --- frontend/src/components/Auth/auth.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/frontend/src/components/Auth/auth.js b/frontend/src/components/Auth/auth.js index 954bf70b16..7673f95ed9 100644 --- a/frontend/src/components/Auth/auth.js +++ b/frontend/src/components/Auth/auth.js @@ -56,3 +56,27 @@ export async function setActiveUser(token, dispatch) { dispatch(setUserDetails(userDetails)); } + +/** + * Retrieves the access token. + * @returns {string|null} The access token, or null if it is not found. + * + * @example + * const accessToken = GetAccessToken(); + */ +export const GetAccessToken = () => { + const token = localStorage.getItem("access_token"); + return token; +}; + +/** + * Retrieves the refresh token. + * @returns {string|null} The refresh token, or null if it is not found. + * + * @example + * const refreshToken = GetRefreshToken(); + */ +export const GetRefreshToken = () => { + const token = localStorage.getItem("refresh_token"); + return token; +}; From bf0e003a4eb2c918d95583057add4e0b269e0a92 Mon Sep 17 00:00:00 2001 From: Tim Donaworth Date: Wed, 13 Sep 2023 10:38:14 -0400 Subject: [PATCH 03/46] refactor: :recycle: Moved the usage scope of `authConfig` (`ApplicationContext`) Moved the `authConfig` (`ApplicationContext`) access into the functions where it is used, as prior a cyclical dependency issue was being created. --- frontend/src/components/Auth/auth.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/Auth/auth.js b/frontend/src/components/Auth/auth.js index 7673f95ed9..98b20a6ee4 100644 --- a/frontend/src/components/Auth/auth.js +++ b/frontend/src/components/Auth/auth.js @@ -4,9 +4,8 @@ import jwt_decode from "jwt-decode"; import { getUserByOidc } from "../../api/getUser"; import { setUserDetails } from "../Auth/authSlice"; -const authConfig = ApplicationContext.get().helpers().authConfig; - export const getAuthorizationCode = (provider, stateToken) => { + const authConfig = ApplicationContext.get().helpers().authConfig; const authProvider = authConfig[provider]; const providerUrl = new URL(authProvider.auth_endpoint); providerUrl.searchParams.set("acr_values", authProvider.acr_values); @@ -27,6 +26,7 @@ export const logoutUser = async (stateToken) => { // client_id=${CLIENT_ID}& // post_logout_redirect_uri=${REDIRECT_URI}& // state=abcdefghijklmnopabcdefghijklmnop + const authConfig = ApplicationContext.get().helpers().authConfig; const providerLogout = new URL(authConfig.logout_endpoint); providerLogout.searchParams.set("client_id", authConfig.client_id); providerLogout.searchParams.set("post_logout_redirect_uri", window.location.hostname); @@ -39,7 +39,7 @@ export const CheckAuth = () => { // the user is correctly authenticated and authorized. Hook into the Auth service // at some point. // const isLoggedIn = useSelector((state) => state.auth.isLoggedIn) || false; - const tokenExists = localStorage.getItem("access_token"); + const tokenExists = getAccessToken() !== null; // TODO: Verify access_token's signature // TODO: Verify access_token's claims // TODO: Verify access_token's expiration - maybe perform a refresh()? @@ -62,9 +62,9 @@ export async function setActiveUser(token, dispatch) { * @returns {string|null} The access token, or null if it is not found. * * @example - * const accessToken = GetAccessToken(); + * const accessToken = getAccessToken(); */ -export const GetAccessToken = () => { +export const getAccessToken = () => { const token = localStorage.getItem("access_token"); return token; }; @@ -74,9 +74,9 @@ export const GetAccessToken = () => { * @returns {string|null} The refresh token, or null if it is not found. * * @example - * const refreshToken = GetRefreshToken(); + * const refreshToken = getRefreshToken(); */ -export const GetRefreshToken = () => { +export const getRefreshToken = () => { const token = localStorage.getItem("refresh_token"); return token; }; From 78e388c40f9d12795764cb7bcbc77e532f77ad0c Mon Sep 17 00:00:00 2001 From: Tim Donaworth Date: Wed, 13 Sep 2023 10:42:12 -0400 Subject: [PATCH 04/46] refactor: :recycle: Implement `getAccessToken()` function Replace direct `localStorage` for the `access_token` with a wrapper `getAccessToken()` across the board. No breaking changes, but this has NOT been emplemented within E2E test, as they use a more direct window.localStorage --- frontend/src/api/opsAPI.js | 3 ++- frontend/src/components/Auth/AuthSection.jsx | 4 ++-- frontend/src/components/Auth/MultiAuthSection.jsx | 4 ++-- frontend/src/components/UI/Header/User.jsx | 4 ++-- .../UI/NotificationCenter/NotificationCenter.jsx | 3 ++- frontend/src/helpers/backend.js | 10 +++++++--- 6 files changed, 17 insertions(+), 11 deletions(-) diff --git a/frontend/src/api/opsAPI.js b/frontend/src/api/opsAPI.js index 7e954dc20a..73dce04293 100644 --- a/frontend/src/api/opsAPI.js +++ b/frontend/src/api/opsAPI.js @@ -1,4 +1,5 @@ import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"; +import { getAccessToken } from "../components/Auth/auth"; const BACKEND_DOMAIN = process.env.REACT_APP_BACKEND_DOMAIN; @@ -17,7 +18,7 @@ export const opsApi = createApi({ baseQuery: fetchBaseQuery({ baseUrl: `${BACKEND_DOMAIN}/api/v1/`, prepareHeaders: (headers) => { - const access_token = localStorage.getItem("access_token"); + const access_token = getAccessToken(); if (access_token) { headers.set("Authorization", `Bearer ${access_token}`); diff --git a/frontend/src/components/Auth/AuthSection.jsx b/frontend/src/components/Auth/AuthSection.jsx index 124ea6a709..291e1080c1 100644 --- a/frontend/src/components/Auth/AuthSection.jsx +++ b/frontend/src/components/Auth/AuthSection.jsx @@ -3,7 +3,7 @@ import { useDispatch, useSelector } from "react-redux"; import { useCallback, useEffect } from "react"; import { useNavigate } from "react-router-dom"; import cryptoRandomString from "crypto-random-string"; -import { getAuthorizationCode } from "./auth"; +import { getAccessToken, getAuthorizationCode } from "./auth"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { solid } from "@fortawesome/fontawesome-svg-core/import.macro"; import { User } from "../UI/Header/User"; @@ -35,7 +35,7 @@ const AuthSection = () => { ); useEffect(() => { - const currentJWT = localStorage.getItem("access_token"); + const currentJWT = getAccessToken(); if (currentJWT) { // TODO: we should validate the JWT here and set it on state if valid else logout diff --git a/frontend/src/components/Auth/MultiAuthSection.jsx b/frontend/src/components/Auth/MultiAuthSection.jsx index a242b79629..ea239d2997 100644 --- a/frontend/src/components/Auth/MultiAuthSection.jsx +++ b/frontend/src/components/Auth/MultiAuthSection.jsx @@ -4,7 +4,7 @@ import { login } from "./authSlice"; import { useDispatch } from "react-redux"; import { useNavigate } from "react-router-dom"; import cryptoRandomString from "crypto-random-string"; -import { getAuthorizationCode } from "./auth"; +import { getAccessToken, getAuthorizationCode } from "./auth"; import { apiLogin } from "../../api/apiLogin"; import ContainerModal from "../UI/Modals/ContainerModal"; import { setActiveUser } from "./auth"; @@ -47,7 +47,7 @@ const MultiAuthSection = () => { ); React.useEffect(() => { - const currentJWT = localStorage.getItem("access_token"); + const currentJWT = getAccessToken(); if (currentJWT) { // TODO: we should validate the JWT here and set it on state if valid else logout dispatch(login()); diff --git a/frontend/src/components/UI/Header/User.jsx b/frontend/src/components/UI/Header/User.jsx index a28cd4fcc0..9cb183e781 100644 --- a/frontend/src/components/UI/Header/User.jsx +++ b/frontend/src/components/UI/Header/User.jsx @@ -1,10 +1,10 @@ import { Link } from "react-router-dom"; -import { CheckAuth } from "../../Auth/auth"; +import { CheckAuth, getAccessToken } from "../../Auth/auth"; import { useGetUserByOIDCIdQuery } from "../../../api/opsAPI"; import jwt_decode from "jwt-decode"; export const User = () => { - const currentJWT = localStorage.getItem("access_token"); + const currentJWT = getAccessToken(); const decodedJwt = jwt_decode(currentJWT); const userId = decodedJwt["sub"]; const { data: user } = useGetUserByOIDCIdQuery(userId); diff --git a/frontend/src/components/UI/NotificationCenter/NotificationCenter.jsx b/frontend/src/components/UI/NotificationCenter/NotificationCenter.jsx index 85566752c9..44df2b2305 100644 --- a/frontend/src/components/UI/NotificationCenter/NotificationCenter.jsx +++ b/frontend/src/components/UI/NotificationCenter/NotificationCenter.jsx @@ -5,10 +5,11 @@ import jwt_decode from "jwt-decode"; import icons from "../../../uswds/img/sprite.svg"; import customStyles from "./NotificationCenter.module.css"; import LogItem from "../LogItem"; +import { getAccessToken } from "../../Auth/auth"; const NotificationCenter = () => { const [showModal, setShowModal] = React.useState(false); - const currentJWT = localStorage.getItem("access_token"); + const currentJWT = getAccessToken(); let userId = ""; if (currentJWT) { diff --git a/frontend/src/helpers/backend.js b/frontend/src/helpers/backend.js index 88a81b7584..7da491e9dd 100644 --- a/frontend/src/helpers/backend.js +++ b/frontend/src/helpers/backend.js @@ -1,13 +1,17 @@ import axios from "axios"; +import { getAccessToken } from "../components/Auth/auth"; const BACKEND_DOMAIN = process.env.REACT_APP_BACKEND_DOMAIN; export const callBackend = async (urlPath, action, requestBody, queryParams) => { console.debug(`Calling backend at ${urlPath} ${queryParams ? "with params:" + JSON.stringify(queryParams) : ""}`); - - if (localStorage.getItem("access_token")) { - axios.defaults.headers.common["Authorization"] = `Bearer ${localStorage.getItem("access_token")}`; + const accessToken = getAccessToken(); + if (accessToken) { + axios.defaults.headers.common["Authorization"] = `Bearer ${accessToken}`; } + // if (localStorage.getItem("access_token")) { + // axios.defaults.headers.common["Authorization"] = `Bearer ${localStorage.getItem("access_token")}`; + // } const response = await axios({ method: action, url: `${BACKEND_DOMAIN}${urlPath}`, From 940dc898b621a25d500da3bd05dfee6699353351 Mon Sep 17 00:00:00 2001 From: Tim Donaworth Date: Tue, 19 Sep 2023 14:42:12 -0400 Subject: [PATCH 05/46] refactor: :recycle: Added token parameter Added a boolean parameter, to determine which getToken function to use; Debated simply passing the function directly, but there's only really two use cases. --- frontend/src/helpers/backend.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/frontend/src/helpers/backend.js b/frontend/src/helpers/backend.js index 7da491e9dd..02d4bf2880 100644 --- a/frontend/src/helpers/backend.js +++ b/frontend/src/helpers/backend.js @@ -1,14 +1,20 @@ import axios from "axios"; -import { getAccessToken } from "../components/Auth/auth"; +import { getAccessToken, getRefreshToken } from "../components/Auth/auth"; const BACKEND_DOMAIN = process.env.REACT_APP_BACKEND_DOMAIN; -export const callBackend = async (urlPath, action, requestBody, queryParams) => { - console.debug(`Calling backend at ${urlPath} ${queryParams ? "with params:" + JSON.stringify(queryParams) : ""}`); - const accessToken = getAccessToken(); +export const callBackend = async (urlPath, action, requestBody, queryParams, useRefresh = false) => { + console.debug( + `Calling backend at ${BACKEND_DOMAIN}${urlPath} ${ + queryParams ? "with params:" + JSON.stringify(queryParams) : "" + }` + ); + + const accessToken = useRefresh ? getRefreshToken() : getAccessToken(); if (accessToken) { axios.defaults.headers.common["Authorization"] = `Bearer ${accessToken}`; } + // if (localStorage.getItem("access_token")) { // axios.defaults.headers.common["Authorization"] = `Bearer ${localStorage.getItem("access_token")}`; // } From c344396e7fc58bc4038276c229bb73703b4a61e4 Mon Sep 17 00:00:00 2001 From: Tim Donaworth Date: Tue, 19 Sep 2023 14:43:10 -0400 Subject: [PATCH 06/46] refactor: :recycle: added refresh token back in. Added the storage of refresh token back in, not sure where it was lost from previous implementations --- frontend/src/components/Auth/MultiAuthSection.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/Auth/MultiAuthSection.jsx b/frontend/src/components/Auth/MultiAuthSection.jsx index ea239d2997..1d967ad2b0 100644 --- a/frontend/src/components/Auth/MultiAuthSection.jsx +++ b/frontend/src/components/Auth/MultiAuthSection.jsx @@ -29,8 +29,9 @@ const MultiAuthSection = () => { console.error("API Login Failed!"); navigate("/login"); } else { - console.log(`DEBUG:::ACCESS_TOKEN: ${response.access_token}`); + // console.log(`DEBUG:::ACCESS_TOKEN: ${response.access_token}`); localStorage.setItem("access_token", response.access_token); + localStorage.setItem("refresh_token", response.refresh_token); dispatch(login()); if (response.is_new_user) { From aa2b8c8dca1ad563e589eaef2034b38cadd80adf Mon Sep 17 00:00:00 2001 From: Tim Donaworth Date: Tue, 19 Sep 2023 14:44:23 -0400 Subject: [PATCH 07/46] feat: :bug: Clear out localStorage on Logout Make sure to fully clear out localStorage when someone logs out. --- frontend/src/components/Auth/authSlice.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/src/components/Auth/authSlice.js b/frontend/src/components/Auth/authSlice.js index 8b078326de..4473395b98 100644 --- a/frontend/src/components/Auth/authSlice.js +++ b/frontend/src/components/Auth/authSlice.js @@ -13,6 +13,10 @@ export const authSlice = createSlice({ logout: (state) => { state.isLoggedIn = false; state.activeUser = null; + localStorage.removeItem("access_token"); + localStorage.removeItem("refresh_token"); + localStorage.removeItem("ops-state-key"); + localStorage.removeItem("activeProvider"); }, setUserDetails: (state, action) => { state.activeUser = action.payload; From 2af923cb6c10d1d9fed21a9b07712709c7cf6044 Mon Sep 17 00:00:00 2001 From: Tim Donaworth Date: Tue, 19 Sep 2023 14:45:11 -0400 Subject: [PATCH 08/46] updated jose dependency --- frontend/package.json | 2 +- frontend/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index c650966072..775e727dae 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -53,7 +53,7 @@ "eslint-plugin-react-hooks": "4.6.0", "eslint-plugin-react-refresh": "0.4.3", "history": "5.3.0", - "jose": "4.14.4", + "jose": "4.14.6", "msw": "1.2.3", "prettier": "2.8.8", "redux-mock-store": "1.5.4" diff --git a/frontend/yarn.lock b/frontend/yarn.lock index fe3673cef1..d1e0c807fe 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -8654,10 +8654,10 @@ jiti@^1.18.2: resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.19.3.tgz#ef554f76465b3c2b222dc077834a71f0d4a37569" integrity sha512-5eEbBDQT/jF1xg6l36P+mWGGoH9Spuy0PCdSr2dtWRDGC6ph/w9ZCL4lmESW8f8F7MwT3XKescfP0wnZWAKL9w== -jose@4.14.4: - version "4.14.4" - resolved "https://registry.yarnpkg.com/jose/-/jose-4.14.4.tgz#59e09204e2670c3164ee24cbfe7115c6f8bff9ca" - integrity sha512-j8GhLiKmUAh+dsFXlX1aJCbt5KMibuKb+d7j1JaOJG6s2UjX1PQlW+OKB/sD4a/5ZYF4RcmYmLSndOoU3Lt/3g== +jose@4.14.6: + version "4.14.6" + resolved "https://registry.yarnpkg.com/jose/-/jose-4.14.6.tgz#94dca1d04a0ad8c6bff0998cdb51220d473cc3af" + integrity sha512-EqJPEUlZD0/CSUMubKtMaYUOtWe91tZXTWMJZoKSbLk+KtdhNdcvppH8lA9XwVu2V4Ailvsj0GBZJ2ZwDjfesQ== js-levenshtein@^1.1.6: version "1.1.6" From 056de07df978db3d2ca76a273d98b5038a71b4eb Mon Sep 17 00:00:00 2001 From: Tim Donaworth Date: Tue, 19 Sep 2023 14:46:28 -0400 Subject: [PATCH 09/46] feat: :lock: Added "iss" and "aud" to tokens Added "iss" and "aud" to tokens, so that additional validation can occur. --- backend/ops_api/ops/utils/auth_views.py | 31 ++++++++++++++++--------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/backend/ops_api/ops/utils/auth_views.py b/backend/ops_api/ops/utils/auth_views.py index 1e03501a9f..7d53fb165a 100644 --- a/backend/ops_api/ops/utils/auth_views.py +++ b/backend/ops_api/ops/utils/auth_views.py @@ -4,13 +4,13 @@ import requests from authlib.integrations.requests_client import OAuth2Session from flask import Response, current_app, request -from flask_jwt_extended import create_access_token, create_refresh_token, get_jwt_identity, jwt_required +from flask_jwt_extended import create_access_token, create_refresh_token, get_current_user, get_jwt, get_jwt_identity, jwt_required from models.events import OpsEventType from ops_api.ops.utils.auth import create_oauth_jwt, decode_user from ops_api.ops.utils.authentication import AuthenticationGateway from ops_api.ops.utils.events import OpsEventHandler from ops_api.ops.utils.response import make_response_with_headers -from ops_api.ops.utils.user import register_user +from ops_api.ops.utils.user import register_user, get_user_from_token def login() -> Union[Response, tuple[str, int]]: @@ -108,6 +108,9 @@ def _get_token_and_user_data_from_internal_auth(user_data: dict[str, str]): # authZ, given a valid login from the prior AuthN steps above. additional_claims = {} + additional_claims["iss"] = "https://opre-ops-backend" # Placeholder + additional_claims["aud"] = "https://opre-ops-frontend" # Placeholder + if user.roles: additional_claims["roles"] = [role.name for role in user.roles] access_token = create_access_token( @@ -168,13 +171,19 @@ def _get_token_and_user_data_from_oauth_provider(provider: str, auth_code: str): # We are using the `refresh=True` options in jwt_required to only allow # refresh tokens to access this route. -@jwt_required(refresh=True) +@jwt_required(refresh=True, verify_type=True, locations=["headers", "cookies"]) def refresh() -> Response: - identity = get_jwt_identity() - additional_claims = {} - if identity.roles: - additional_claims["roles"] = [role.name for role in identity.roles] - access_token = create_access_token( - identity=identity, expires_delta=None, additional_claims=additional_claims, fresh=False - ) - return make_response_with_headers({"access_token": access_token}) + user = get_current_user() + if(user): + additional_claims = { "iss": None, "aud": None, "roles": []} + additional_claims["iss"] = "https://opre-ops-backend" # Placeholder + additional_claims["aud"] = "https://opre-ops-frontend" # Placeholder + current_app.logger.debug(f"user {user}") + if user.roles: + additional_claims["roles"] = [role.name for role in user.roles] + access_token = create_access_token( + identity=user, expires_delta=None, additional_claims=additional_claims, fresh=False + ) + return make_response_with_headers({"access_token": access_token}) + else: + return make_response_with_headers({"message": "Invalid User"}, 401) From 7e358a12ba30fd866e2cee53941640c273d00d25 Mon Sep 17 00:00:00 2001 From: Tim Donaworth Date: Tue, 19 Sep 2023 14:49:22 -0400 Subject: [PATCH 10/46] feat: :sparkles: wire up `refresh` on frontend AccessToken will now be refreshed on expiration, giving you a longer session; Aslo added a bit of validation on the token. --- frontend/src/components/Auth/auth.js | 95 +++++++++++++++++++++++++++- 1 file changed, 93 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/Auth/auth.js b/frontend/src/components/Auth/auth.js index 98b20a6ee4..3b925d2259 100644 --- a/frontend/src/components/Auth/auth.js +++ b/frontend/src/components/Auth/auth.js @@ -2,8 +2,21 @@ import ApplicationContext from "../../applicationContext/ApplicationContext"; import cryptoRandomString from "crypto-random-string"; import jwt_decode from "jwt-decode"; import { getUserByOidc } from "../../api/getUser"; -import { setUserDetails } from "../Auth/authSlice"; +import { logout, setUserDetails } from "../Auth/authSlice"; +import { callBackend } from "../../helpers/backend"; +/** + * Generates the authorization code URL for the specified provider and state token. + * @function getAuthorizationCode + * @param {string} provider - The name of the provider to generate the authorization code URL for. + * @param {string} stateToken - The state token to include in the authorization code URL. + * @returns {URL} The authorization code URL for the specified provider and state token. + * + * @example + * const provider = "login.gov"; + * const stateToken = "12345"; + * const authUrl = getAuthorizationCode(provider, stateToken); + */ export const getAuthorizationCode = (provider, stateToken) => { const authConfig = ApplicationContext.get().helpers().authConfig; const authProvider = authConfig[provider]; @@ -18,6 +31,11 @@ export const getAuthorizationCode = (provider, stateToken) => { return providerUrl; }; +/** + * Logs out the user and returns the URL to redirect to for logout. + * @param {string} stateToken - The state token to include in the logout URL. + * @returns {URL} - The URL to redirect to for logout. + */ export const logoutUser = async (stateToken) => { // As documented here: https://developers.login.gov/oidc/ // Example: @@ -34,6 +52,14 @@ export const logoutUser = async (stateToken) => { return providerLogout; }; +/** + * Checks if the user is authenticated and authorized. + * @todo Implement token signature validation + * @todo Implement token claims validation + * @todo Implement token expiration validation + * @todo Implement Authorization checks. + * @returns {boolean} Returns true if the user is authenticated and authorized, otherwise false. + */ export const CheckAuth = () => { // TODO: We'll most likely want to include multiple checks here to determine if // the user is correctly authenticated and authorized. Hook into the Auth service @@ -47,6 +73,19 @@ export const CheckAuth = () => { return tokenExists; // && payload; }; +/** + * Sets the active user details in the Redux store by decoding the JWT token and fetching user details from the API. + * @async + * @function setActiveUser + * @param {string} token - The JWT token to decode and fetch user details. + * @param {function} dispatch - The Redux dispatch function to set the user details in the store. + * @returns {Promise} A Promise that resolves when the user details are set in the store. + * + * @example + * const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"; + * const dispatch = useDispatch(); + * setActiveUser(token, dispatch); + */ export async function setActiveUser(token, dispatch) { // TODO: Vefiry the Token! //const isValidToken = validateTooken(token); @@ -66,7 +105,12 @@ export async function setActiveUser(token, dispatch) { */ export const getAccessToken = () => { const token = localStorage.getItem("access_token"); - return token; + if (isValidToken(token)) { + return token; + } else { + // TODO: determine proper exception or action to take here + return null; + } }; /** @@ -80,3 +124,50 @@ export const getRefreshToken = () => { const token = localStorage.getItem("refresh_token"); return token; }; + +/** + * Checks if the access token is valid by decoding the JWT and comparing the expiration time with the current time. + * @returns {boolean} Returns true if the access token is valid, false otherwise. + */ +export const isValidToken = (token) => { + if (!token) { + return false; + } + + const decodedJwt = jwt_decode(token); + + // Check expiration time + const exp = decodedJwt["exp"]; + const now = Date.now() / 1000; + if (exp < now) { + // lets try to get a new token + // is the refresh token still valid? + callBackend("/api/v1/auth/refresh/", "POST", {}, null, true) + .then((response) => { + console.log(response); + localStorage.setItem("access_token", response.access_token); + return true; + // localStorage.setItem("refresh_token", response.refresh_token); + }) + .catch((error) => { + console.log(error); + logout(); + }); + } + + // TODO: Check signature + // const signature = decodedJwt["signature"]; + // if (!verifySignature(token, signature)) { + // throw new InvalidSignatureException("Token signature is invalid"); + // } + + // Check issuer + const issuer = decodedJwt["iss"]; + // TODO: Update this when we have a real issuer value + if (issuer !== "https://opre-ops-backend") { + console.log("Token issuer is invalid"); + return false; + } + + return true; +}; From dd02e656f36a3af341bf6858e157503deec8a55b Mon Sep 17 00:00:00 2001 From: Tim Donaworth Date: Tue, 19 Sep 2023 16:14:12 -0400 Subject: [PATCH 11/46] refactor: :recycle: created a custom return object for token checks Created a `TokenValidationStatus` class for returning the status of validation checks on the tokens. --- frontend/src/components/Auth/auth.js | 52 ++++++++++++++--------- frontend/src/components/Auth/auth.test.js | 30 ++++++++++++- 2 files changed, 61 insertions(+), 21 deletions(-) diff --git a/frontend/src/components/Auth/auth.js b/frontend/src/components/Auth/auth.js index 3b925d2259..6be68944c5 100644 --- a/frontend/src/components/Auth/auth.js +++ b/frontend/src/components/Auth/auth.js @@ -5,6 +5,16 @@ import { getUserByOidc } from "../../api/getUser"; import { logout, setUserDetails } from "../Auth/authSlice"; import { callBackend } from "../../helpers/backend"; +/** + * Represents the status of a token. + */ +class TokenValidationStatus { + constructor(isValid, msg) { + this.isValid = isValid; + this.msg = msg; + } +} + /** * Generates the authorization code URL for the specified provider and state token. * @function getAuthorizationCode @@ -105,10 +115,24 @@ export async function setActiveUser(token, dispatch) { */ export const getAccessToken = () => { const token = localStorage.getItem("access_token"); - if (isValidToken(token)) { + const validToken = isValidToken(token); + if (validToken.isValid) { return token; + } else if (validToken.msg == "EXPIRED") { + // lets try to get a new token + // is the refresh token still valid? + callBackend("/api/v1/auth/refresh/", "POST", {}, null, true) + .then((response) => { + console.log(response); + localStorage.setItem("access_token", response.access_token); + return response.access_token; + // localStorage.setItem("refresh_token", response.refresh_token); + }) + .catch((error) => { + console.log(error); + logout(); + }); } else { - // TODO: determine proper exception or action to take here return null; } }; @@ -127,11 +151,11 @@ export const getRefreshToken = () => { /** * Checks if the access token is valid by decoding the JWT and comparing the expiration time with the current time. - * @returns {boolean} Returns true if the access token is valid, false otherwise. + * @returns {boolean|str} Returns true if the access token is valid, false otherwise. */ export const isValidToken = (token) => { if (!token) { - return false; + return new TokenValidationStatus(false, "NOT_FOUND"); } const decodedJwt = jwt_decode(token); @@ -140,19 +164,7 @@ export const isValidToken = (token) => { const exp = decodedJwt["exp"]; const now = Date.now() / 1000; if (exp < now) { - // lets try to get a new token - // is the refresh token still valid? - callBackend("/api/v1/auth/refresh/", "POST", {}, null, true) - .then((response) => { - console.log(response); - localStorage.setItem("access_token", response.access_token); - return true; - // localStorage.setItem("refresh_token", response.refresh_token); - }) - .catch((error) => { - console.log(error); - logout(); - }); + return new TokenValidationStatus(false, "EXPIRED"); } // TODO: Check signature @@ -163,11 +175,11 @@ export const isValidToken = (token) => { // Check issuer const issuer = decodedJwt["iss"]; + console.log(`Issuer: ${issuer}`); // TODO: Update this when we have a real issuer value if (issuer !== "https://opre-ops-backend") { - console.log("Token issuer is invalid"); - return false; + return new TokenValidationStatus(false, "ISSUER"); } - return true; + return new TokenValidationStatus(true, "VALID"); }; diff --git a/frontend/src/components/Auth/auth.test.js b/frontend/src/components/Auth/auth.test.js index 55a3729819..b23a0d79d6 100644 --- a/frontend/src/components/Auth/auth.test.js +++ b/frontend/src/components/Auth/auth.test.js @@ -1,4 +1,11 @@ -import { getAuthorizationCode } from "./auth"; +import { getAuthorizationCode, isValidToken } from "./auth"; + +const expiredToken = + "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6dHJ1ZSwiaWF0IjoxNjk1MTQ5NTkxLCJqdGkiOiJlOGUwMTY2ZS1lNTYyLTQ3N2UtOWJiMy05MjA1OTFiNmEyMjUiLCJ0eXBlIjoiYWNjZXNzIiwic3ViIjoiMDAwMDAwMDAtMDAwMC0xMTExLWExMTEtMDAwMDAwMDAwMDE4IiwibmJmIjoxNjk1MTQ5NTkxLCJleHAiOjEwMDAwMDAwMDEsImlzcyI6Imh0dHBzOi8vb3ByZS1vcHMtYmFja2VuZCIsImF1ZCI6Imh0dHBzOi8vb3ByZS1vcHMtZnJvbnRlbmQiLCJyb2xlcyI6WyJhZG1pbiJdfQ.RWvGtcZCxGGaqX_28LSfFqcza32sU4zZnfC_niCJP3X6V9vTRGjONEfLoZS9_TjGbx0TZHHMdZRnCWF2LW_84mTJEhizqad8nmVHnagvehfrjUcS9Fe1wn6ZvjlQfSiX9iibgfXjV0WjlNeKuNx3nLhcSt0yKiwpw-7BoPUjQKYQcjmNYfL6rzqxj85MIsHo09ifxKDmmOePiFqZEgyUDakovSMrS-nGFif3sArUC7dmJRuEO3KdFGD9Dc9rY1RItlBYl4xSlq3bZDwDmH5DWlJ_22LjW2Xq04LJwLFbagOjcsDQhRIOHevviX6S3xBfzZf_NDx8ZFhj3FXIItQ4QQw"; +const badIssToken = + "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6dHJ1ZSwiaWF0IjoxNjk1MTQ5NTkxLCJqdGkiOiJlOGUwMTY2ZS1lNTYyLTQ3N2UtOWJiMy05MjA1OTFiNmEyMjUiLCJ0eXBlIjoiYWNjZXNzIiwic3ViIjoiMDAwMDAwMDAtMDAwMC0xMTExLWExMTEtMDAwMDAwMDAwMDE4IiwibmJmIjoxNjk1MTQ5NTkxLCJleHAiOjE5NjUxNTEzOTEsImlzcyI6Imh0dHA6Ly9vcHMtaW52YWxpZC1pc3N1ZXIiLCJhdWQiOiJodHRwczovL29wcmUtb3BzLWZyb250ZW5kIiwicm9sZXMiOlsiYWRtaW4iXX0.MQG1wzAEZV4Aq-KPXeT0E0NFGB-2d1Xm9ZkhUz15BGna-c37VredS-r75zA9OkK5r5pPvjdJU5mNPrr1co4SdEtnZK8PW4Ilvi_XMHwTflBV8cOhoz74jEbf0Hj_CDPX3PCsH4Surxun7CELTR775QYRa5EdEgxUX7LREJXZj1PhHissr8tQpr30LWAKLqNUr0KXJGauXN-YxfbuT_fxlV_P6Q_mY0RqEZAdvgmZs3KB3L_hqb7tj6TCtieXXIEkZICZGIPCq9rd3kYAQoDjGO8Qw5hnePTK_focZ46Rj1gcrLa_Ot-qg0L6GzJdv_Qmby5akIGc8i7kCDzmL_BHZw"; +const validToken = + "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6dHJ1ZSwiaWF0IjoxNjk1MTQ5NTkxLCJqdGkiOiJlOGUwMTY2ZS1lNTYyLTQ3N2UtOWJiMy05MjA1OTFiNmEyMjUiLCJ0eXBlIjoiYWNjZXNzIiwic3ViIjoiMDAwMDAwMDAtMDAwMC0xMTExLWExMTEtMDAwMDAwMDAwMDE4IiwibmJmIjoxNjk1MTQ5NTkxLCJleHAiOjE5NjUxNTEzOTEsImlzcyI6Imh0dHBzOi8vb3ByZS1vcHMtYmFja2VuZCIsImF1ZCI6Imh0dHBzOi8vb3ByZS1vcHMtZnJvbnRlbmQiLCJyb2xlcyI6WyJhZG1pbiJdfQ.sC7t-OAFR_FptdmErn5id8NIW-sYQpoUezX7n07Wp3BbV0pcULhoQL7C-sFf2uRiXjWKwDdXU1ZVOzRcxb97v-e7FsMJd5Ds7va2uz8U3qGaJwH3dWrGotl4m1CWIgQ807euIhVtDLFTeePyhXwhpAPF-WLVWGWP1GaTTnhM_bpRwYqt653Dt5A6aU9TKtcUPhhHaEnZMxpooo5L1vX32agxvDEsf8D7X78f7S18eFM7GWyVSU0ZByPjaj6iAggUYmiuDu8acCaZfFTQKz-0v_zEyupP8goqCixbxEkhcUiY6V6Ud0szVryvlQcN-hsrq1wQWxk8SYQDKOrxmWu99Q"; test("construct the URL to get the authentication code to send to the backend", async () => { // the nonce is generated at runtime so do not test here @@ -14,3 +21,24 @@ test("construct the URL to get the authentication code to send to the backend", expect(actualProviderUrl.searchParams.get("redirect_uri")).toEqual("http://uri/login"); expect(actualProviderUrl.searchParams.get("state")).toEqual(stateToken); }); + +describe("isValidToken", () => { + it("returns false if token is not provided", () => { + expect(isValidToken().isValid).toBe(false); + }); + + it("returns false if token is expired", () => { + expect(isValidToken(expiredToken).isValid).toBe(false); + expect(isValidToken(expiredToken).msg).toBe("EXPIRED"); + }); + + it("returns false if token is not issued by the backend", () => { + expect(isValidToken(badIssToken).isValid).toBe(false); + expect(isValidToken(badIssToken).msg).toBe("ISSUER"); + }); + + it("returns true if token is valid", () => { + expect(isValidToken(validToken).isValid).toBe(true); + expect(isValidToken(validToken).msg).toBe("VALID"); + }); +}); From 97d19094a0caa9b8c1f7ed19f726973c30c1c41e Mon Sep 17 00:00:00 2001 From: Tim Donaworth Date: Tue, 19 Sep 2023 16:17:52 -0400 Subject: [PATCH 12/46] format & lint --- backend/ops_api/ops/utils/auth_views.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/backend/ops_api/ops/utils/auth_views.py b/backend/ops_api/ops/utils/auth_views.py index 7d53fb165a..73fb74c8dd 100644 --- a/backend/ops_api/ops/utils/auth_views.py +++ b/backend/ops_api/ops/utils/auth_views.py @@ -4,13 +4,19 @@ import requests from authlib.integrations.requests_client import OAuth2Session from flask import Response, current_app, request -from flask_jwt_extended import create_access_token, create_refresh_token, get_current_user, get_jwt, get_jwt_identity, jwt_required +from flask_jwt_extended import ( + create_access_token, + create_refresh_token, + get_current_user, + get_jwt_identity, + jwt_required, +) from models.events import OpsEventType from ops_api.ops.utils.auth import create_oauth_jwt, decode_user from ops_api.ops.utils.authentication import AuthenticationGateway from ops_api.ops.utils.events import OpsEventHandler from ops_api.ops.utils.response import make_response_with_headers -from ops_api.ops.utils.user import register_user, get_user_from_token +from ops_api.ops.utils.user import register_user def login() -> Union[Response, tuple[str, int]]: @@ -108,9 +114,9 @@ def _get_token_and_user_data_from_internal_auth(user_data: dict[str, str]): # authZ, given a valid login from the prior AuthN steps above. additional_claims = {} - additional_claims["iss"] = "https://opre-ops-backend" # Placeholder - additional_claims["aud"] = "https://opre-ops-frontend" # Placeholder - + additional_claims["iss"] = "https://opre-ops-backend" # Placeholder + additional_claims["aud"] = "https://opre-ops-frontend" # Placeholder + if user.roles: additional_claims["roles"] = [role.name for role in user.roles] access_token = create_access_token( @@ -174,10 +180,10 @@ def _get_token_and_user_data_from_oauth_provider(provider: str, auth_code: str): @jwt_required(refresh=True, verify_type=True, locations=["headers", "cookies"]) def refresh() -> Response: user = get_current_user() - if(user): - additional_claims = { "iss": None, "aud": None, "roles": []} - additional_claims["iss"] = "https://opre-ops-backend" # Placeholder - additional_claims["aud"] = "https://opre-ops-frontend" # Placeholder + if user: + additional_claims = {"iss": None, "aud": None, "roles": []} + additional_claims["iss"] = "https://opre-ops-backend" # Placeholder + additional_claims["aud"] = "https://opre-ops-frontend" # Placeholder current_app.logger.debug(f"user {user}") if user.roles: additional_claims["roles"] = [role.name for role in user.roles] From d926ed4a37f7505c676fc53a5ef3f5da490646f9 Mon Sep 17 00:00:00 2001 From: Tim Donaworth Date: Tue, 19 Sep 2023 16:31:38 -0400 Subject: [PATCH 13/46] update semgrep check for the test tokens --- frontend/src/components/Auth/auth.test.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/Auth/auth.test.js b/frontend/src/components/Auth/auth.test.js index b23a0d79d6..c827620c15 100644 --- a/frontend/src/components/Auth/auth.test.js +++ b/frontend/src/components/Auth/auth.test.js @@ -1,9 +1,11 @@ import { getAuthorizationCode, isValidToken } from "./auth"; - +// nosemgrep - test tokens only const expiredToken = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6dHJ1ZSwiaWF0IjoxNjk1MTQ5NTkxLCJqdGkiOiJlOGUwMTY2ZS1lNTYyLTQ3N2UtOWJiMy05MjA1OTFiNmEyMjUiLCJ0eXBlIjoiYWNjZXNzIiwic3ViIjoiMDAwMDAwMDAtMDAwMC0xMTExLWExMTEtMDAwMDAwMDAwMDE4IiwibmJmIjoxNjk1MTQ5NTkxLCJleHAiOjEwMDAwMDAwMDEsImlzcyI6Imh0dHBzOi8vb3ByZS1vcHMtYmFja2VuZCIsImF1ZCI6Imh0dHBzOi8vb3ByZS1vcHMtZnJvbnRlbmQiLCJyb2xlcyI6WyJhZG1pbiJdfQ.RWvGtcZCxGGaqX_28LSfFqcza32sU4zZnfC_niCJP3X6V9vTRGjONEfLoZS9_TjGbx0TZHHMdZRnCWF2LW_84mTJEhizqad8nmVHnagvehfrjUcS9Fe1wn6ZvjlQfSiX9iibgfXjV0WjlNeKuNx3nLhcSt0yKiwpw-7BoPUjQKYQcjmNYfL6rzqxj85MIsHo09ifxKDmmOePiFqZEgyUDakovSMrS-nGFif3sArUC7dmJRuEO3KdFGD9Dc9rY1RItlBYl4xSlq3bZDwDmH5DWlJ_22LjW2Xq04LJwLFbagOjcsDQhRIOHevviX6S3xBfzZf_NDx8ZFhj3FXIItQ4QQw"; +// nosemgrep - test tokens only const badIssToken = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6dHJ1ZSwiaWF0IjoxNjk1MTQ5NTkxLCJqdGkiOiJlOGUwMTY2ZS1lNTYyLTQ3N2UtOWJiMy05MjA1OTFiNmEyMjUiLCJ0eXBlIjoiYWNjZXNzIiwic3ViIjoiMDAwMDAwMDAtMDAwMC0xMTExLWExMTEtMDAwMDAwMDAwMDE4IiwibmJmIjoxNjk1MTQ5NTkxLCJleHAiOjE5NjUxNTEzOTEsImlzcyI6Imh0dHA6Ly9vcHMtaW52YWxpZC1pc3N1ZXIiLCJhdWQiOiJodHRwczovL29wcmUtb3BzLWZyb250ZW5kIiwicm9sZXMiOlsiYWRtaW4iXX0.MQG1wzAEZV4Aq-KPXeT0E0NFGB-2d1Xm9ZkhUz15BGna-c37VredS-r75zA9OkK5r5pPvjdJU5mNPrr1co4SdEtnZK8PW4Ilvi_XMHwTflBV8cOhoz74jEbf0Hj_CDPX3PCsH4Surxun7CELTR775QYRa5EdEgxUX7LREJXZj1PhHissr8tQpr30LWAKLqNUr0KXJGauXN-YxfbuT_fxlV_P6Q_mY0RqEZAdvgmZs3KB3L_hqb7tj6TCtieXXIEkZICZGIPCq9rd3kYAQoDjGO8Qw5hnePTK_focZ46Rj1gcrLa_Ot-qg0L6GzJdv_Qmby5akIGc8i7kCDzmL_BHZw"; +// nosemgrep - test tokens only const validToken = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6dHJ1ZSwiaWF0IjoxNjk1MTQ5NTkxLCJqdGkiOiJlOGUwMTY2ZS1lNTYyLTQ3N2UtOWJiMy05MjA1OTFiNmEyMjUiLCJ0eXBlIjoiYWNjZXNzIiwic3ViIjoiMDAwMDAwMDAtMDAwMC0xMTExLWExMTEtMDAwMDAwMDAwMDE4IiwibmJmIjoxNjk1MTQ5NTkxLCJleHAiOjE5NjUxNTEzOTEsImlzcyI6Imh0dHBzOi8vb3ByZS1vcHMtYmFja2VuZCIsImF1ZCI6Imh0dHBzOi8vb3ByZS1vcHMtZnJvbnRlbmQiLCJyb2xlcyI6WyJhZG1pbiJdfQ.sC7t-OAFR_FptdmErn5id8NIW-sYQpoUezX7n07Wp3BbV0pcULhoQL7C-sFf2uRiXjWKwDdXU1ZVOzRcxb97v-e7FsMJd5Ds7va2uz8U3qGaJwH3dWrGotl4m1CWIgQ807euIhVtDLFTeePyhXwhpAPF-WLVWGWP1GaTTnhM_bpRwYqt653Dt5A6aU9TKtcUPhhHaEnZMxpooo5L1vX32agxvDEsf8D7X78f7S18eFM7GWyVSU0ZByPjaj6iAggUYmiuDu8acCaZfFTQKz-0v_zEyupP8goqCixbxEkhcUiY6V6Ud0szVryvlQcN-hsrq1wQWxk8SYQDKOrxmWu99Q"; From ff75d00f1cd0c674f4e7a356c480c68e7d0c1bbc Mon Sep 17 00:00:00 2001 From: Tim Donaworth Date: Tue, 19 Sep 2023 16:38:27 -0400 Subject: [PATCH 14/46] cleanup --- frontend/src/components/Auth/auth.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/Auth/auth.js b/frontend/src/components/Auth/auth.js index 6be68944c5..49f8933e96 100644 --- a/frontend/src/components/Auth/auth.js +++ b/frontend/src/components/Auth/auth.js @@ -92,7 +92,7 @@ export const CheckAuth = () => { * @returns {Promise} A Promise that resolves when the user details are set in the store. * * @example - * const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"; + * const token = ""; * const dispatch = useDispatch(); * setActiveUser(token, dispatch); */ From 5f8d6982e9218f6ffcd99d844435101e34e644dc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 20 Sep 2023 12:28:56 +0000 Subject: [PATCH 15/46] chore(deps): update dependency pandas to v2 --- backend/data_tools/Pipfile | 2 +- backend/data_tools/Pipfile.lock | 64 ++++++++++++++++----------------- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/backend/data_tools/Pipfile b/backend/data_tools/Pipfile index 2f63df96dc..e5f5571fe4 100644 --- a/backend/data_tools/Pipfile +++ b/backend/data_tools/Pipfile @@ -5,7 +5,7 @@ name = "pypi" [packages] SQLAlchemy = "==2.0.21" -pandas = "==1.5.3" +pandas = "==2.1.0" json5 = "==0.9.14" psycopg2-binary = "==2.9.7" cfenv = "==0.5.3" diff --git a/backend/data_tools/Pipfile.lock b/backend/data_tools/Pipfile.lock index c401289a15..cb831abf31 100644 --- a/backend/data_tools/Pipfile.lock +++ b/backend/data_tools/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "df361d8e24e8fcfdf059f4dd08725ce0bb644e59238ef77817a79e64c38008a0" + "sha256": "6a50461779aa7b3d5a3abbc73c1cfa12b7803bbb683d750b302fbc6b604694f0" }, "pipfile-spec": 6, "requires": { @@ -177,7 +177,7 @@ "sha256:f8db2f125746e44dce707dd44d4f4efeea8d7e2b43aace3f8d1f235cfa2733dd", "sha256:f93fc78fe8bf15afe2b8d6b6499f1c73953169fad1e9a8dd086cdff3190e7fdf" ], - "markers": "python_version >= '3.10'", + "markers": "python_version < '3.11'", "version": "==1.26.0" }, "orderedmultidict": { @@ -197,37 +197,29 @@ }, "pandas": { "hashes": [ - "sha256:14e45300521902689a81f3f41386dc86f19b8ba8dd5ac5a3c7010ef8d2932813", - "sha256:26d9c71772c7afb9d5046e6e9cf42d83dd147b5cf5bcb9d97252077118543792", - "sha256:3749077d86e3a2f0ed51367f30bf5b82e131cc0f14260c4d3e499186fccc4406", - "sha256:41179ce559943d83a9b4bbacb736b04c928b095b5f25dd2b7389eda08f46f373", - "sha256:478ff646ca42b20376e4ed3fa2e8d7341e8a63105586efe54fa2508ee087f328", - "sha256:50869a35cbb0f2e0cd5ec04b191e7b12ed688874bd05dd777c19b28cbea90996", - "sha256:565fa34a5434d38e9d250af3c12ff931abaf88050551d9fbcdfafca50d62babf", - "sha256:5f2b952406a1588ad4cad5b3f55f520e82e902388a6d5a4a91baa8d38d23c7f6", - "sha256:5fbcb19d6fceb9e946b3e23258757c7b225ba450990d9ed63ccceeb8cae609f7", - "sha256:6973549c01ca91ec96199e940495219c887ea815b2083722821f1d7abfa2b4dc", - "sha256:74a3fd7e5a7ec052f183273dc7b0acd3a863edf7520f5d3a1765c04ffdb3b0b1", - "sha256:7a0a56cef15fd1586726dace5616db75ebcfec9179a3a55e78f72c5639fa2a23", - "sha256:7cec0bee9f294e5de5bbfc14d0573f65526071029d036b753ee6507d2a21480a", - "sha256:87bd9c03da1ac870a6d2c8902a0e1fd4267ca00f13bc494c9e5a9020920e1d51", - "sha256:972d8a45395f2a2d26733eb8d0f629b2f90bebe8e8eddbb8829b180c09639572", - "sha256:9842b6f4b8479e41968eced654487258ed81df7d1c9b7b870ceea24ed9459b31", - "sha256:9f69c4029613de47816b1bb30ff5ac778686688751a5e9c99ad8c7031f6508e5", - "sha256:a50d9a4336a9621cab7b8eb3fb11adb82de58f9b91d84c2cd526576b881a0c5a", - "sha256:bc4c368f42b551bf72fac35c5128963a171b40dce866fb066540eeaf46faa003", - "sha256:c39a8da13cede5adcd3be1182883aea1c925476f4e84b2807a46e2775306305d", - "sha256:c3ac844a0fe00bfaeb2c9b51ab1424e5c8744f89860b138434a363b1f620f354", - "sha256:c4c00e0b0597c8e4f59e8d461f797e5d70b4d025880516a8261b2817c47759ee", - "sha256:c74a62747864ed568f5a82a49a23a8d7fe171d0c69038b38cedf0976831296fa", - "sha256:dd05f7783b3274aa206a1af06f0ceed3f9b412cf665b7247eacd83be41cf7bf0", - "sha256:dfd681c5dc216037e0b0a2c821f5ed99ba9f03ebcf119c7dac0e9a7b960b9ec9", - "sha256:e474390e60ed609cec869b0da796ad94f420bb057d86784191eefc62b65819ae", - "sha256:f76d097d12c82a535fda9dfe5e8dd4127952b45fea9b0276cb30cca5ea313fbc" + "sha256:0164b85937707ec7f70b34a6c3a578dbf0f50787f910f21ca3b26a7fd3363437", + "sha256:28f330845ad21c11db51e02d8d69acc9035edfd1116926ff7245c7215db57957", + "sha256:38f74ef7ebc0ffb43b3d633e23d74882bce7e27bfa09607f3c5d3e03ffd9a4a5", + "sha256:40dd20439ff94f1b2ed55b393ecee9cb6f3b08104c2c40b0cb7186a2f0046242", + "sha256:629124923bcf798965b054a540f9ccdfd60f71361255c81fa1ecd94a904b9dd3", + "sha256:62c24c7fc59e42b775ce0679cfa7b14a5f9bfb7643cfbe708c960699e05fb918", + "sha256:6e6a0fe052cf27ceb29be9429428b4918f3740e37ff185658f40d8702f0b3e09", + "sha256:70cf866af3ab346a10debba8ea78077cf3a8cd14bd5e4bed3d41555a3280041c", + "sha256:86f100b3876b8c6d1a2c66207288ead435dc71041ee4aea789e55ef0e06408cb", + "sha256:9d81e1813191070440d4c7a413cb673052b3b4a984ffd86b8dd468c45742d3cc", + "sha256:b31da36d376d50a1a492efb18097b9101bdbd8b3fbb3f49006e02d4495d4c644", + "sha256:b9a6ccf0963db88f9b12df6720e55f337447aea217f426a22d71f4213a3099a6", + "sha256:cda72cc8c4761c8f1d97b169661f23a86b16fdb240bdc341173aee17e4d6cedd", + "sha256:d4f38e4fedeba580285eaac7ede4f686c6701a9e618d8a857b138a126d067f2f", + "sha256:d53c8c1001f6a192ff1de1efe03b31a423d0eee2e9e855e69d004308e046e694", + "sha256:d8c58b1113892e0c8078f006a167cc210a92bdae23322bb4614f2f0b7a4b510f", + "sha256:d97daeac0db8c993420b10da4f5f5b39b01fc9ca689a17844e07c0a35ac96b4b", + "sha256:d99e678180bc59b0c9443314297bddce4ad35727a1a2656dbe585fd78710b3b9", + "sha256:eb20252720b1cc1b7d0b2879ffc7e0542dd568f24d7c4b2347cb035206936421" ], "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==1.5.3" + "markers": "python_version >= '3.9'", + "version": "==2.1.0" }, "psycopg2-binary": { "hashes": [ @@ -405,6 +397,14 @@ "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78" ], "version": "==0.9.0" + }, + "tzdata": { + "hashes": [ + "sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a", + "sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda" + ], + "markers": "python_version >= '2'", + "version": "==2023.3" } }, "develop": { @@ -1203,7 +1203,7 @@ "sha256:f8db2f125746e44dce707dd44d4f4efeea8d7e2b43aace3f8d1f235cfa2733dd", "sha256:f93fc78fe8bf15afe2b8d6b6499f1c73953169fad1e9a8dd086cdff3190e7fdf" ], - "markers": "python_version >= '3.10'", + "markers": "python_version < '3.11'", "version": "==1.26.0" }, "packaging": { From 221801a944ce31fbec223de22f328b5113110e80 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 20 Sep 2023 12:30:35 +0000 Subject: [PATCH 16/46] chore(deps): update dependency cypress to v13 --- frontend/package.json | 2 +- frontend/yarn.lock | 44 +++++++++++++++++++++---------------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 121bd1d07c..623a1db592 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -42,7 +42,7 @@ "@types/testing-library__react": "10.2.0", "@uswds/compile": "1.0.0", "axe-core": "4.8.1", - "cypress": "12.17.4", + "cypress": "13.2.0", "cypress-axe": "1.5.0", "cypress-localstorage-commands": "2.2.4", "eslint-config-prettier": "9.0.0", diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 55274ed7b2..bc90adc5ca 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1278,10 +1278,10 @@ resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-2.2.0.tgz#2cbcf822bf3764c9658c4d2e568bd0c0cb748016" integrity sha512-+OJ9konv95ClSTOJCmMZqpd5+YGsB2S+x6w3E1oaM8UuR5j8nTNHYSz8c9BEPGDOCMQYIEEGlVPj/VY64iTbGw== -"@cypress/request@2.88.12": - version "2.88.12" - resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.12.tgz#ba4911431738494a85e93fb04498cb38bc55d590" - integrity sha512-tOn+0mDZxASFM+cuAP9szGUGPI1HwWVSvdzm7V4cCsPdFTx6qMj29CwaQmRAMIEhORIUBFBsYROYJcveK4uOjA== +"@cypress/request@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@cypress/request/-/request-3.0.1.tgz#72d7d5425236a2413bd3d8bb66d02d9dc3168960" + integrity sha512-TWivJlJi8ZDx2wGOw1dbLuHJKUYX7bWySw377nlnGOW3hP9/MUKIsEdXT/YngWxVdgNCHRBmFlBipE+5/2ZZlQ== dependencies: aws-sign2 "~0.7.0" aws4 "^1.8.0" @@ -1296,7 +1296,7 @@ json-stringify-safe "~5.0.1" mime-types "~2.1.19" performance-now "^2.1.0" - qs "~6.10.3" + qs "6.10.4" safe-buffer "^5.1.2" tough-cookie "^4.1.3" tunnel-agent "^0.6.0" @@ -2627,10 +2627,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.54.tgz#fc304bd66419030141fa997dc5a9e0e374029ae8" integrity sha512-uq7O52wvo2Lggsx1x21tKZgqkJpvwCseBBPtX/nKQfpVlEsLOb11zZ1CRsWUKvJF0+lzuA9jwvA7Pr2Wt7i3xw== -"@types/node@^16.18.39": - version "16.18.41" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.41.tgz#61b14360fd3f7444b326ac3207c83005371e3f8a" - integrity sha512-YZJjn+Aaw0xihnpdImxI22jqGbp0DCgTFKRycygjGx/Y27NnWFJa5FJ7P+MRT3u07dogEeMVh70pWpbIQollTA== +"@types/node@^18.17.5": + version "18.17.18" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.17.18.tgz#acae19ad9011a2ab3d792232501c95085ba1838f" + integrity sha512-/4QOuy3ZpV7Ya1GTRz5CYSz3DgkKpyUptXuQ5PPce7uuyJAOR7r9FhkmxJfvcNUXyklbC63a+YvB3jxy7s9ngw== "@types/parse-json@^4.0.0": version "4.0.0" @@ -4931,14 +4931,14 @@ cypress-localstorage-commands@2.2.4: resolved "https://registry.yarnpkg.com/cypress-localstorage-commands/-/cypress-localstorage-commands-2.2.4.tgz#deba47b5128156abd40eb5b69a9e7bfa943fbc6e" integrity sha512-XiWFJhhgcoWf7p6sS5igGuN8WWGF0K1jRCPo2XlikwEWgts1uvPl80cybGiztYoNrU1xCbQO9/IOz6Y0eTl4Lg== -cypress@12.17.4: - version "12.17.4" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-12.17.4.tgz#b4dadf41673058493fa0d2362faa3da1f6ae2e6c" - integrity sha512-gAN8Pmns9MA5eCDFSDJXWKUpaL3IDd89N9TtIupjYnzLSmlpVr+ZR+vb4U/qaMp+lB6tBvAmt7504c3Z4RU5KQ== +cypress@13.2.0: + version "13.2.0" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-13.2.0.tgz#10f73d06a0764764ffbb903a31e96e2118dcfc1d" + integrity sha512-AvDQxBydE771GTq0TR4ZUBvv9m9ffXuB/ueEtpDF/6gOcvFR96amgwSJP16Yhqw6VhmwqspT5nAGzoxxB+D89g== dependencies: - "@cypress/request" "2.88.12" + "@cypress/request" "^3.0.0" "@cypress/xvfb" "^1.2.4" - "@types/node" "^16.18.39" + "@types/node" "^18.17.5" "@types/sinonjs__fake-timers" "8.1.1" "@types/sizzle" "^2.3.2" arch "^2.2.0" @@ -11089,6 +11089,13 @@ q@^1.1.2: resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" integrity sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw== +qs@6.10.4: + version "6.10.4" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.4.tgz#6a3003755add91c0ec9eacdc5f878b034e73f9e7" + integrity sha512-OQiU+C+Ds5qiH91qh/mg0w+8nwQuLjM4F4M/PbmhDOoYehPh+Fb0bDjtR1sOvy7YKxvj28Y/M0PhP5uVX0kB+g== + dependencies: + side-channel "^1.0.4" + qs@6.11.0: version "6.11.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" @@ -11096,13 +11103,6 @@ qs@6.11.0: dependencies: side-channel "^1.0.4" -qs@~6.10.3: - version "6.10.5" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.5.tgz#974715920a80ff6a262264acd2c7e6c2a53282b4" - integrity sha512-O5RlPh0VFtR78y79rgcgKK4wbAI0C5zGVLztOIdpWX6ep368q5Hv6XRxDvXuZ9q3C6v+e3n8UfZZJw7IIG27eQ== - dependencies: - side-channel "^1.0.4" - querystringify@^2.1.1: version "2.2.0" resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" From 47dcac27d18533d756fbe3fc7671afe2913cf5da Mon Sep 17 00:00:00 2001 From: Tim Donaworth Date: Wed, 20 Sep 2023 14:25:59 -0400 Subject: [PATCH 17/46] add `nosemgrep` testing keys --- frontend/src/components/Auth/auth.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/Auth/auth.test.js b/frontend/src/components/Auth/auth.test.js index c827620c15..806b0e6736 100644 --- a/frontend/src/components/Auth/auth.test.js +++ b/frontend/src/components/Auth/auth.test.js @@ -1,13 +1,13 @@ import { getAuthorizationCode, isValidToken } from "./auth"; // nosemgrep - test tokens only const expiredToken = - "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6dHJ1ZSwiaWF0IjoxNjk1MTQ5NTkxLCJqdGkiOiJlOGUwMTY2ZS1lNTYyLTQ3N2UtOWJiMy05MjA1OTFiNmEyMjUiLCJ0eXBlIjoiYWNjZXNzIiwic3ViIjoiMDAwMDAwMDAtMDAwMC0xMTExLWExMTEtMDAwMDAwMDAwMDE4IiwibmJmIjoxNjk1MTQ5NTkxLCJleHAiOjEwMDAwMDAwMDEsImlzcyI6Imh0dHBzOi8vb3ByZS1vcHMtYmFja2VuZCIsImF1ZCI6Imh0dHBzOi8vb3ByZS1vcHMtZnJvbnRlbmQiLCJyb2xlcyI6WyJhZG1pbiJdfQ.RWvGtcZCxGGaqX_28LSfFqcza32sU4zZnfC_niCJP3X6V9vTRGjONEfLoZS9_TjGbx0TZHHMdZRnCWF2LW_84mTJEhizqad8nmVHnagvehfrjUcS9Fe1wn6ZvjlQfSiX9iibgfXjV0WjlNeKuNx3nLhcSt0yKiwpw-7BoPUjQKYQcjmNYfL6rzqxj85MIsHo09ifxKDmmOePiFqZEgyUDakovSMrS-nGFif3sArUC7dmJRuEO3KdFGD9Dc9rY1RItlBYl4xSlq3bZDwDmH5DWlJ_22LjW2Xq04LJwLFbagOjcsDQhRIOHevviX6S3xBfzZf_NDx8ZFhj3FXIItQ4QQw"; + "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6dHJ1ZSwiaWF0IjoxNjk1MTQ5NTkxLCJqdGkiOiJlOGUwMTY2ZS1lNTYyLTQ3N2UtOWJiMy05MjA1OTFiNmEyMjUiLCJ0eXBlIjoiYWNjZXNzIiwic3ViIjoiMDAwMDAwMDAtMDAwMC0xMTExLWExMTEtMDAwMDAwMDAwMDE4IiwibmJmIjoxNjk1MTQ5NTkxLCJleHAiOjEwMDAwMDAwMDEsImlzcyI6Imh0dHBzOi8vb3ByZS1vcHMtYmFja2VuZCIsImF1ZCI6Imh0dHBzOi8vb3ByZS1vcHMtZnJvbnRlbmQiLCJyb2xlcyI6WyJhZG1pbiJdfQ.RWvGtcZCxGGaqX_28LSfFqcza32sU4zZnfC_niCJP3X6V9vTRGjONEfLoZS9_TjGbx0TZHHMdZRnCWF2LW_84mTJEhizqad8nmVHnagvehfrjUcS9Fe1wn6ZvjlQfSiX9iibgfXjV0WjlNeKuNx3nLhcSt0yKiwpw-7BoPUjQKYQcjmNYfL6rzqxj85MIsHo09ifxKDmmOePiFqZEgyUDakovSMrS-nGFif3sArUC7dmJRuEO3KdFGD9Dc9rY1RItlBYl4xSlq3bZDwDmH5DWlJ_22LjW2Xq04LJwLFbagOjcsDQhRIOHevviX6S3xBfzZf_NDx8ZFhj3FXIItQ4QQw"; // nosemgrep // nosemgrep - test tokens only const badIssToken = - "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6dHJ1ZSwiaWF0IjoxNjk1MTQ5NTkxLCJqdGkiOiJlOGUwMTY2ZS1lNTYyLTQ3N2UtOWJiMy05MjA1OTFiNmEyMjUiLCJ0eXBlIjoiYWNjZXNzIiwic3ViIjoiMDAwMDAwMDAtMDAwMC0xMTExLWExMTEtMDAwMDAwMDAwMDE4IiwibmJmIjoxNjk1MTQ5NTkxLCJleHAiOjE5NjUxNTEzOTEsImlzcyI6Imh0dHA6Ly9vcHMtaW52YWxpZC1pc3N1ZXIiLCJhdWQiOiJodHRwczovL29wcmUtb3BzLWZyb250ZW5kIiwicm9sZXMiOlsiYWRtaW4iXX0.MQG1wzAEZV4Aq-KPXeT0E0NFGB-2d1Xm9ZkhUz15BGna-c37VredS-r75zA9OkK5r5pPvjdJU5mNPrr1co4SdEtnZK8PW4Ilvi_XMHwTflBV8cOhoz74jEbf0Hj_CDPX3PCsH4Surxun7CELTR775QYRa5EdEgxUX7LREJXZj1PhHissr8tQpr30LWAKLqNUr0KXJGauXN-YxfbuT_fxlV_P6Q_mY0RqEZAdvgmZs3KB3L_hqb7tj6TCtieXXIEkZICZGIPCq9rd3kYAQoDjGO8Qw5hnePTK_focZ46Rj1gcrLa_Ot-qg0L6GzJdv_Qmby5akIGc8i7kCDzmL_BHZw"; + "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6dHJ1ZSwiaWF0IjoxNjk1MTQ5NTkxLCJqdGkiOiJlOGUwMTY2ZS1lNTYyLTQ3N2UtOWJiMy05MjA1OTFiNmEyMjUiLCJ0eXBlIjoiYWNjZXNzIiwic3ViIjoiMDAwMDAwMDAtMDAwMC0xMTExLWExMTEtMDAwMDAwMDAwMDE4IiwibmJmIjoxNjk1MTQ5NTkxLCJleHAiOjE5NjUxNTEzOTEsImlzcyI6Imh0dHA6Ly9vcHMtaW52YWxpZC1pc3N1ZXIiLCJhdWQiOiJodHRwczovL29wcmUtb3BzLWZyb250ZW5kIiwicm9sZXMiOlsiYWRtaW4iXX0.MQG1wzAEZV4Aq-KPXeT0E0NFGB-2d1Xm9ZkhUz15BGna-c37VredS-r75zA9OkK5r5pPvjdJU5mNPrr1co4SdEtnZK8PW4Ilvi_XMHwTflBV8cOhoz74jEbf0Hj_CDPX3PCsH4Surxun7CELTR775QYRa5EdEgxUX7LREJXZj1PhHissr8tQpr30LWAKLqNUr0KXJGauXN-YxfbuT_fxlV_P6Q_mY0RqEZAdvgmZs3KB3L_hqb7tj6TCtieXXIEkZICZGIPCq9rd3kYAQoDjGO8Qw5hnePTK_focZ46Rj1gcrLa_Ot-qg0L6GzJdv_Qmby5akIGc8i7kCDzmL_BHZw"; // nosemgrep // nosemgrep - test tokens only const validToken = - "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6dHJ1ZSwiaWF0IjoxNjk1MTQ5NTkxLCJqdGkiOiJlOGUwMTY2ZS1lNTYyLTQ3N2UtOWJiMy05MjA1OTFiNmEyMjUiLCJ0eXBlIjoiYWNjZXNzIiwic3ViIjoiMDAwMDAwMDAtMDAwMC0xMTExLWExMTEtMDAwMDAwMDAwMDE4IiwibmJmIjoxNjk1MTQ5NTkxLCJleHAiOjE5NjUxNTEzOTEsImlzcyI6Imh0dHBzOi8vb3ByZS1vcHMtYmFja2VuZCIsImF1ZCI6Imh0dHBzOi8vb3ByZS1vcHMtZnJvbnRlbmQiLCJyb2xlcyI6WyJhZG1pbiJdfQ.sC7t-OAFR_FptdmErn5id8NIW-sYQpoUezX7n07Wp3BbV0pcULhoQL7C-sFf2uRiXjWKwDdXU1ZVOzRcxb97v-e7FsMJd5Ds7va2uz8U3qGaJwH3dWrGotl4m1CWIgQ807euIhVtDLFTeePyhXwhpAPF-WLVWGWP1GaTTnhM_bpRwYqt653Dt5A6aU9TKtcUPhhHaEnZMxpooo5L1vX32agxvDEsf8D7X78f7S18eFM7GWyVSU0ZByPjaj6iAggUYmiuDu8acCaZfFTQKz-0v_zEyupP8goqCixbxEkhcUiY6V6Ud0szVryvlQcN-hsrq1wQWxk8SYQDKOrxmWu99Q"; + "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6dHJ1ZSwiaWF0IjoxNjk1MTQ5NTkxLCJqdGkiOiJlOGUwMTY2ZS1lNTYyLTQ3N2UtOWJiMy05MjA1OTFiNmEyMjUiLCJ0eXBlIjoiYWNjZXNzIiwic3ViIjoiMDAwMDAwMDAtMDAwMC0xMTExLWExMTEtMDAwMDAwMDAwMDE4IiwibmJmIjoxNjk1MTQ5NTkxLCJleHAiOjE5NjUxNTEzOTEsImlzcyI6Imh0dHBzOi8vb3ByZS1vcHMtYmFja2VuZCIsImF1ZCI6Imh0dHBzOi8vb3ByZS1vcHMtZnJvbnRlbmQiLCJyb2xlcyI6WyJhZG1pbiJdfQ.sC7t-OAFR_FptdmErn5id8NIW-sYQpoUezX7n07Wp3BbV0pcULhoQL7C-sFf2uRiXjWKwDdXU1ZVOzRcxb97v-e7FsMJd5Ds7va2uz8U3qGaJwH3dWrGotl4m1CWIgQ807euIhVtDLFTeePyhXwhpAPF-WLVWGWP1GaTTnhM_bpRwYqt653Dt5A6aU9TKtcUPhhHaEnZMxpooo5L1vX32agxvDEsf8D7X78f7S18eFM7GWyVSU0ZByPjaj6iAggUYmiuDu8acCaZfFTQKz-0v_zEyupP8goqCixbxEkhcUiY6V6Ud0szVryvlQcN-hsrq1wQWxk8SYQDKOrxmWu99Q"; // nosemgrep test("construct the URL to get the authentication code to send to the backend", async () => { // the nonce is generated at runtime so do not test here From d726fa9c6944d4085e47256da1138a29914329f9 Mon Sep 17 00:00:00 2001 From: Tim Donaworth Date: Wed, 20 Sep 2023 14:30:22 -0400 Subject: [PATCH 18/46] unused code cleanup --- frontend/src/components/Auth/MultiAuthSection.jsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/frontend/src/components/Auth/MultiAuthSection.jsx b/frontend/src/components/Auth/MultiAuthSection.jsx index 1d967ad2b0..0c3a0b5c72 100644 --- a/frontend/src/components/Auth/MultiAuthSection.jsx +++ b/frontend/src/components/Auth/MultiAuthSection.jsx @@ -24,12 +24,10 @@ const MultiAuthSection = () => { } const response = await apiLogin(activeProvider, authCode); - // console.debug(`API Login Response = ${JSON.stringify(response)}`); if (response.access_token === null || response.access_token === undefined) { console.error("API Login Failed!"); navigate("/login"); } else { - // console.log(`DEBUG:::ACCESS_TOKEN: ${response.access_token}`); localStorage.setItem("access_token", response.access_token); localStorage.setItem("refresh_token", response.refresh_token); dispatch(login()); @@ -72,7 +70,6 @@ const MultiAuthSection = () => { throw new Error("Response from OIDC provider is invalid."); } else { const authCode = queryParams.get("code"); - console.log(`Received Authentication Code = ${authCode}`); callBackend(authCode).catch(console.error); } } @@ -85,7 +82,6 @@ const MultiAuthSection = () => { // TODO: Replace these tokens with config variables, that can be passed in at deploy-time, // So that we don't actually store anything in code. const handleFakeAuthLogin = (user_type) => { - // console.debug(`Logging in with FakeAuth: ${user_type}`); localStorage.setItem("activeProvider", "fakeauth"); callBackend(user_type).catch(console.error); From 46fb1e8548f6f512e3a83d52565a8ff02e5926a6 Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Wed, 20 Sep 2023 13:32:39 -0500 Subject: [PATCH 19/46] feat: :art: adds DefaultLayout with Alert (#1472) * feat: :art: adds DefaultLayout with Alert * refactor: :art: moves BreadCrumb to DefaultLayout * refactor: :sparkles: pins Alert to parent component * refactor: :art: removes Page level Alerts * style: linter fixes * test: :test_tube: updates tests * feat: :sparkles: adds alert when group of BLIs are saved * feat: :art: adds custom hook for Alert logix * refactor: :art: use useAlert hook and updates tests --- frontend/src/App.jsx | 27 +++-- .../AgreementEditor/AgreementEditForm.jsx | 59 ++++------ frontend/src/components/UI/Alert/Alert.jsx | 14 +-- frontend/src/components/UI/Footer/Footer.jsx | 6 +- .../Footer/{Idnetifier.jsx => Identifier.jsx} | 0 frontend/src/components/UI/Footer/index.js | 1 + .../src/components/UI/Header/Breadcrumb.jsx | 10 +- frontend/src/components/UI/Header/index.js | 1 + .../UI/Layouts/DefaultLayout/DefaultLayout.js | 35 ++++++ .../UI/Layouts/DefaultLayout/index.js | 1 + .../UI/RoundedBox/RoundedBox.test.js | 4 +- .../StepCreateBudgetLines.jsx | 108 ++++++++---------- frontend/src/helpers/use-alert.js | 38 ++++++ frontend/src/index.jsx | 2 +- frontend/src/pages/ErrorPage.jsx | 4 - frontend/src/pages/Login.jsx | 2 +- .../pages/agreements/CreateEditAgreement.jsx | 20 ++-- .../src/pages/agreements/details/Agreement.js | 8 +- .../details/AgreementBudgetLines.js | 12 +- .../agreements/list/AgreementTableRow.jsx | 31 +++-- .../agreements/list/AgreementTableRow.test.js | 3 + .../pages/agreements/list/AgreementsList.jsx | 3 - .../agreements/list/AgreementsTable.test.js | 3 + .../agreements/review/ReviewAgreement.jsx | 36 +++--- .../src/pages/budgetLines/StepSuccess.jsx | 21 ++-- .../budgetLines/list/BudgetLineItemList.jsx | 3 - .../portfolios/detail/PortfolioDetail.jsx | 47 ++++---- frontend/src/pages/projects/CreateProject.jsx | 51 ++++----- .../src/pages/projects/CreateProject.test.js | 2 +- .../src/pages/users/detail/UserDetail.jsx | 12 +- frontend/src/pages/users/edit/EditUser.jsx | 17 +-- 31 files changed, 292 insertions(+), 289 deletions(-) rename frontend/src/components/UI/Footer/{Idnetifier.jsx => Identifier.jsx} (100%) create mode 100644 frontend/src/components/UI/Footer/index.js create mode 100644 frontend/src/components/UI/Header/index.js create mode 100644 frontend/src/components/UI/Layouts/DefaultLayout/DefaultLayout.js create mode 100644 frontend/src/components/UI/Layouts/DefaultLayout/index.js create mode 100644 frontend/src/helpers/use-alert.js diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 27170756a5..b80a135057 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,17 +1,16 @@ -import { Footer } from "./components/UI/Footer/Footer"; -import Header from "./components/UI/Header/Header"; +import PropTypes from "prop-types"; +import DefaultLayout from "./components/UI/Layouts/DefaultLayout"; -function App(props) { - return ( -
-
-
-
- {props.children} -
-
-
- ); -} +/** + * DefaultLayout component + * @param {Object} props - Properties passed to component + * @param {string} [props.breadCrumbName] - The name of the current page to be displayed in the breadcrumb + * @param {React.ReactNode} props.children - The child nodes to be rendered within the layout + */ +const App = ({ children, breadCrumbName }) => {children}; +App.propTypes = { + children: PropTypes.element.isRequired, + breadCrumbName: PropTypes.string, +}; export default App; diff --git a/frontend/src/components/Agreements/AgreementEditor/AgreementEditForm.jsx b/frontend/src/components/Agreements/AgreementEditor/AgreementEditForm.jsx index f940acbcca..2731ee8934 100644 --- a/frontend/src/components/Agreements/AgreementEditor/AgreementEditForm.jsx +++ b/frontend/src/components/Agreements/AgreementEditor/AgreementEditForm.jsx @@ -1,8 +1,8 @@ import React, { useEffect } from "react"; import PropTypes from "prop-types"; import { useNavigate } from "react-router-dom"; -import { useDispatch } from "react-redux"; import classnames from "vest/classnames"; +import _ from "lodash"; import ProcurementShopSelectWithFee from "../../UI/Form/ProcurementShopSelectWithFee"; import AgreementReasonSelect from "../../UI/Form/AgreementReasonSelect"; @@ -14,7 +14,6 @@ import ConfirmationModal from "../../UI/Modals/ConfirmationModal"; import { formatTeamMember } from "../../../api/postAgreements"; import ProductServiceCodeSummaryBox from "../../UI/Form/ProductServiceCodeSummaryBox"; import { useEditAgreement, useEditAgreementDispatch, useSetState, useUpdateAgreement } from "./AgreementEditorContext"; -import { setAlert } from "../../UI/Alert/alertSlice"; import suite from "./AgreementEditFormSuite"; import Input from "../../UI/Form/Input"; import TextArea from "../../UI/Form/TextArea/TextArea"; @@ -25,7 +24,7 @@ import { } from "../../../api/opsAPI"; import ProjectOfficerComboBox from "../../UI/Form/ProjectOfficerComboBox"; import { getUser } from "../../../api/getUser"; -import _ from "lodash"; +import useAlert from "../../../helpers/use-alert"; /** * Renders the "Create Agreement" step of the Create Agreement flow. @@ -62,7 +61,7 @@ export const AgreementEditForm = ({ goBack, goToNext, isReviewMode, isEditMode, const navigate = useNavigate(); const dispatch = useEditAgreementDispatch(); - const globalDispatch = useDispatch(); + const { setAlert } = useAlert(); const [updateAgreement] = useUpdateAgreementMutation(); const [addAgreement] = useAddAgreementMutation(); @@ -179,24 +178,20 @@ export const AgreementEditForm = ({ goBack, goToNext, isReviewMode, isEditMode, .unwrap() .then((fulfilled) => { console.log(`UPDATE: agreement updated: ${JSON.stringify(fulfilled, null, 2)}`); - globalDispatch( - setAlert({ - type: "success", - heading: "Agreement Edited", - message: `The agreement ${agreement.name} has been successfully updated.`, - }) - ); + setAlert({ + type: "success", + heading: "Agreement Edited", + message: `The agreement ${agreement.name} has been successfully updated.`, + }); }) .catch((rejected) => { console.error(`UPDATE: agreement updated failed: ${JSON.stringify(rejected, null, 2)}`); - globalDispatch( - setAlert({ - type: "error", - heading: "Error", - message: "An error occurred while saving the agreement.", - }) - ); - navigate("/error"); + setAlert({ + type: "error", + heading: "Error", + message: "An error occurred while saving the agreement.", + redirectUrl: "/error", + }); }); } else { await addAgreement(cleanData) @@ -207,24 +202,20 @@ export const AgreementEditForm = ({ goBack, goToNext, isReviewMode, isEditMode, }) .then((fulfilled) => { console.log(`CREATE: agreement success: ${JSON.stringify(fulfilled, null, 2)}`); - globalDispatch( - setAlert({ - type: "success", - heading: "Agreement Draft Saved", - message: `The agreement ${agreement.name} has been successfully created.`, - }) - ); + setAlert({ + type: "success", + heading: "Agreement Draft Saved", + message: `The agreement ${agreement.name} has been successfully created.`, + }); }) .catch((rejected) => { console.error(`CREATE: agreement failed: ${JSON.stringify(rejected, null, 2)}`); - globalDispatch( - setAlert({ - type: "error", - heading: "Error", - message: "An error occurred while creating the agreement.", - }) - ); - navigate("/error"); + setAlert({ + type: "error", + heading: "Error", + message: "An error occurred while creating the agreement.", + redirectUrl: "/error", + }); }); } }; diff --git a/frontend/src/components/UI/Alert/Alert.jsx b/frontend/src/components/UI/Alert/Alert.jsx index 9483ad8b6a..12712cf9a6 100644 --- a/frontend/src/components/UI/Alert/Alert.jsx +++ b/frontend/src/components/UI/Alert/Alert.jsx @@ -8,7 +8,7 @@ import { setIsActive, clearState } from "./alertSlice"; /** * A component that displays an alert. * @param {Object} props - The component props. - * @param {React.JSX.Element} [props.children] - The JSX children to render. (optional) + * @param {React.ReactNode} [props.children] - The children to render. * @returns {React.JSX.Element} The JSX element to render. * @see {@link https://designsystem.digital.gov/components/alerts/} */ @@ -19,7 +19,6 @@ export const Alert = ({ children }) => { const message = useSelector((state) => state.alert.message); const type = useSelector((state) => state.alert.type); const redirectUrl = useSelector((state) => state.alert.redirectUrl); - let classNames = "usa-alert margin-left-neg-4 margin-right-neg-4"; React.useEffect(() => { if (redirectUrl) { @@ -37,21 +36,22 @@ export const Alert = ({ children }) => { showAlert(); }, [navigate, dispatch, redirectUrl]); + let typeClass = null; switch (type) { case "success": - classNames += " usa-alert--success"; + typeClass = "usa-alert--success"; break; case "warning": - classNames += " usa-alert--warning"; + typeClass = "usa-alert--warning"; break; case "error": - classNames += " usa-alert--error"; + typeClass = "usa-alert--error"; break; default: } return ( -
+

{heading}

@@ -61,7 +61,7 @@ export const Alert = ({ children }) => { dispatch(setIsActive(false))} diff --git a/frontend/src/components/UI/Footer/Footer.jsx b/frontend/src/components/UI/Footer/Footer.jsx index 25d5a1b3ca..4630b94b04 100644 --- a/frontend/src/components/UI/Footer/Footer.jsx +++ b/frontend/src/components/UI/Footer/Footer.jsx @@ -1,8 +1,8 @@ import { AgencyInfo } from "./AgencyInfo"; -import { Identifier } from "./Idnetifier"; +import { Identifier } from "./Identifier"; import { NavFooter } from "./NavFooter"; -export const Footer = () => { +const Footer = () => { const scrollToTop = () => { window.scrollTo({ top: 0, @@ -26,3 +26,5 @@ export const Footer = () => { ); }; + +export default Footer; diff --git a/frontend/src/components/UI/Footer/Idnetifier.jsx b/frontend/src/components/UI/Footer/Identifier.jsx similarity index 100% rename from frontend/src/components/UI/Footer/Idnetifier.jsx rename to frontend/src/components/UI/Footer/Identifier.jsx diff --git a/frontend/src/components/UI/Footer/index.js b/frontend/src/components/UI/Footer/index.js new file mode 100644 index 0000000000..3738288b05 --- /dev/null +++ b/frontend/src/components/UI/Footer/index.js @@ -0,0 +1 @@ +export { default } from "./Footer"; diff --git a/frontend/src/components/UI/Header/Breadcrumb.jsx b/frontend/src/components/UI/Header/Breadcrumb.jsx index 62beb76f00..0b0961efe2 100644 --- a/frontend/src/components/UI/Header/Breadcrumb.jsx +++ b/frontend/src/components/UI/Header/Breadcrumb.jsx @@ -1,7 +1,13 @@ import { Link, useMatches } from "react-router-dom"; import PropTypes from "prop-types"; -export const Breadcrumb = ({ currentName }) => { +/** + * Breadcrumb component + * @param {Object} props - Properties passed to component + * @param {string} props.currentName - The name of the current breadcrumb + * @returns {React.JSX.Element} - The rendered component + */ +const Breadcrumb = ({ currentName }) => { let matches = useMatches(); let crumbs = matches // first get rid of any matches that don't have handle and crumb @@ -11,7 +17,7 @@ export const Breadcrumb = ({ currentName }) => { .map((match) => match.handle.crumb(match.data)); return ( -
+
+ +
+ ); }; diff --git a/frontend/src/pages/projects/CreateProject.jsx b/frontend/src/pages/projects/CreateProject.jsx index dad838e1ad..434e6573be 100644 --- a/frontend/src/pages/projects/CreateProject.jsx +++ b/frontend/src/pages/projects/CreateProject.jsx @@ -1,19 +1,16 @@ -import React from "react"; import { useState } from "react"; import { useNavigate } from "react-router-dom"; -import { useSelector, useDispatch } from "react-redux"; import App from "../../App"; import ProjectTypeSelect from "../../components/UI/Form/ProjectTypeSelect/ProjectTypeSelect"; import { useAddResearchProjectsMutation } from "../../api/opsAPI"; -import Alert from "../../components/UI/Alert"; import Input from "../../components/UI/Form/Input"; import TextArea from "../../components/UI/Form/TextArea"; import suite from "./suite"; import classnames from "vest/classnames"; -import { setAlert } from "../../components/UI/Alert/alertSlice"; import ConfirmationModal from "../../components/UI/Modals/ConfirmationModal"; +import useAlert from "../../helpers/use-alert"; -export const CreateProject = () => { +const CreateProject = () => { const [showModal, setShowModal] = useState(false); const [modalProps, setModalProps] = useState({ heading: "", @@ -27,12 +24,12 @@ export const CreateProject = () => { title: "", description: "", }); - const isAlertActive = useSelector((state) => state.alert.isActive); + const [addResearchProject, { isSuccess, isError, error, reset, data: rpData }] = useAddResearchProjectsMutation(); + const { setAlert } = useAlert(); let res = suite.get(); const navigate = useNavigate(); - const dispatch = useDispatch(); const handleClearingForm = () => { setProject({ @@ -67,21 +64,17 @@ export const CreateProject = () => { console.dir(error); } - React.useEffect(() => { - if (isSuccess) { - console.log(`New Project Created: ${rpData.id}`); - reset(); - handleClearingForm(); - dispatch( - setAlert({ - type: "success", - heading: "New Project Created!", - message: "The project has been successfully created.", - redirectUrl: `/agreements`, - }) - ); - } - }, [isSuccess, rpData, reset, dispatch, navigate]); + if (isSuccess) { + console.log(`New Project Created: ${rpData.id}`); + reset(); + handleClearingForm(); + setAlert({ + type: "success", + heading: "New Project Created!", + message: "The project has been successfully created.", + redirectUrl: `/agreements`, + }); + } const handleCancel = () => { setShowModal(true); @@ -98,15 +91,6 @@ export const CreateProject = () => { return ( - {isAlertActive ? ( - - ) : ( - <> -

Create New Project

-

Fill out this form to create a new project.

- - )} - {showModal && ( { secondaryButtonText={modalProps.secondaryButtonText} /> )} +

Create New Project

+

Fill out this form to create a new project.

+

Select the Project Type

Select the type of project you’d like to create.

{
); }; + +export default CreateProject; diff --git a/frontend/src/pages/projects/CreateProject.test.js b/frontend/src/pages/projects/CreateProject.test.js index 91a0ea7740..64941f0c4c 100644 --- a/frontend/src/pages/projects/CreateProject.test.js +++ b/frontend/src/pages/projects/CreateProject.test.js @@ -1,5 +1,5 @@ import { render } from "@testing-library/react"; -import { CreateProject } from "./CreateProject"; +import CreateProject from "./CreateProject"; import store from "../../store"; import { Provider } from "react-redux"; diff --git a/frontend/src/pages/users/detail/UserDetail.jsx b/frontend/src/pages/users/detail/UserDetail.jsx index 77b099ba26..d4f8a419e0 100644 --- a/frontend/src/pages/users/detail/UserDetail.jsx +++ b/frontend/src/pages/users/detail/UserDetail.jsx @@ -1,11 +1,10 @@ import { useSelector, useDispatch } from "react-redux"; -import { getUser } from "../../../api/getUser"; import { useEffect } from "react"; import { useParams } from "react-router-dom"; import App from "../../../App"; -import { Breadcrumb } from "../../../components/UI/Header/Breadcrumb"; import { setUser } from "./userSlice"; import UserInfo from "../../../components/Users/UserInfo/UserInfo"; +import { getUser } from "../../../api/getUser"; const UserDetail = () => { const dispatch = useDispatch(); @@ -23,12 +22,9 @@ const UserDetail = () => { }, [dispatch, userId]); return ( - <> - - - - - + + + ); }; diff --git a/frontend/src/pages/users/edit/EditUser.jsx b/frontend/src/pages/users/edit/EditUser.jsx index 626d3ee558..2b041f2172 100644 --- a/frontend/src/pages/users/edit/EditUser.jsx +++ b/frontend/src/pages/users/edit/EditUser.jsx @@ -1,7 +1,5 @@ -import { useEffect } from "react"; import { useParams } from "react-router-dom"; import App from "../../../App"; -import { Breadcrumb } from "../../../components/UI/Header/Breadcrumb"; import EditUserForm from "../../../components/Users/UserInfoForm/EditUserForm"; import { useGetUserByIdQuery } from "../../../api/opsAPI"; @@ -13,14 +11,8 @@ const UserDetail = () => { data: user, error: errorAgreement, isLoading: isLoadingAgreement, - refetch, } = useGetUserByIdQuery(userId, { refetchOnMountOrArgChange: true }); - useEffect(() => { - refetch(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - if (isLoadingAgreement) { return ( @@ -37,12 +29,9 @@ const UserDetail = () => { } return ( - <> - - - - - + + + ); }; From 5b51fbdebf9dfa23fc6cdd739e3223e89762440e Mon Sep 17 00:00:00 2001 From: Tim Donaworth Date: Wed, 20 Sep 2023 14:32:45 -0400 Subject: [PATCH 20/46] unused code cleanup --- frontend/src/components/Auth/auth.js | 1 - frontend/src/helpers/backend.js | 3 --- 2 files changed, 4 deletions(-) diff --git a/frontend/src/components/Auth/auth.js b/frontend/src/components/Auth/auth.js index 49f8933e96..e5788b69be 100644 --- a/frontend/src/components/Auth/auth.js +++ b/frontend/src/components/Auth/auth.js @@ -126,7 +126,6 @@ export const getAccessToken = () => { console.log(response); localStorage.setItem("access_token", response.access_token); return response.access_token; - // localStorage.setItem("refresh_token", response.refresh_token); }) .catch((error) => { console.log(error); diff --git a/frontend/src/helpers/backend.js b/frontend/src/helpers/backend.js index 02d4bf2880..0b982aff7d 100644 --- a/frontend/src/helpers/backend.js +++ b/frontend/src/helpers/backend.js @@ -15,9 +15,6 @@ export const callBackend = async (urlPath, action, requestBody, queryParams, use axios.defaults.headers.common["Authorization"] = `Bearer ${accessToken}`; } - // if (localStorage.getItem("access_token")) { - // axios.defaults.headers.common["Authorization"] = `Bearer ${localStorage.getItem("access_token")}`; - // } const response = await axios({ method: action, url: `${BACKEND_DOMAIN}${urlPath}`, From 1036647aaec5c1fa54a54a26e3202346874c98f9 Mon Sep 17 00:00:00 2001 From: Tim Donaworth Date: Wed, 20 Sep 2023 14:39:32 -0400 Subject: [PATCH 21/46] moved the `iss` and `aud` to default config --- backend/ops_api/ops/environment/cloud/dev.py | 1 - backend/ops_api/ops/environment/cloud/prod.py | 1 - backend/ops_api/ops/environment/cloud/staging.py | 1 - backend/ops_api/ops/environment/default_settings.py | 4 ++++ backend/ops_api/ops/utils/auth_views.py | 7 +------ 5 files changed, 5 insertions(+), 9 deletions(-) diff --git a/backend/ops_api/ops/environment/cloud/dev.py b/backend/ops_api/ops/environment/cloud/dev.py index 85d6c0db26..7bb0cad46c 100644 --- a/backend/ops_api/ops/environment/cloud/dev.py +++ b/backend/ops_api/ops/environment/cloud/dev.py @@ -5,7 +5,6 @@ DEBUG = True - # Helper function def get_json_env_var(variable_name): """Retrieve and serialize a JSON environment variable.""" diff --git a/backend/ops_api/ops/environment/cloud/prod.py b/backend/ops_api/ops/environment/cloud/prod.py index a4efa4ef7d..d95605d872 100644 --- a/backend/ops_api/ops/environment/cloud/prod.py +++ b/backend/ops_api/ops/environment/cloud/prod.py @@ -5,7 +5,6 @@ DEBUG = False - # Helper function def get_json_env_var(variable_name): """Retrieve and serialize a JSON environment variable.""" diff --git a/backend/ops_api/ops/environment/cloud/staging.py b/backend/ops_api/ops/environment/cloud/staging.py index a4efa4ef7d..d95605d872 100644 --- a/backend/ops_api/ops/environment/cloud/staging.py +++ b/backend/ops_api/ops/environment/cloud/staging.py @@ -5,7 +5,6 @@ DEBUG = False - # Helper function def get_json_env_var(variable_name): """Retrieve and serialize a JSON environment variable.""" diff --git a/backend/ops_api/ops/environment/default_settings.py b/backend/ops_api/ops/environment/default_settings.py index bd5af85ba8..1e42ec98cf 100644 --- a/backend/ops_api/ops/environment/default_settings.py +++ b/backend/ops_api/ops/environment/default_settings.py @@ -17,6 +17,10 @@ JWT_ACCESS_TOKEN_EXPIRES = timedelta(minutes=30) # FedRAMP AC-12 Control is 30 min JWT_REFRESH_TOKEN_EXPIRES = timedelta(hours=12) +# OPS-API JWT +JWT_ENCODE_ISSUER = "https://opre-ops-backend-dev" +JWT_ENCODE_AUDIENCE = "https://opre-ops-frontend-dev" + AUTHLIB_OAUTH_CLIENTS = { "logingov": { "server_metadata_url": "https://idp.int.identitysandbox.gov/.well-known/openid-configuration", diff --git a/backend/ops_api/ops/utils/auth_views.py b/backend/ops_api/ops/utils/auth_views.py index 73fb74c8dd..2e7f1ae2c5 100644 --- a/backend/ops_api/ops/utils/auth_views.py +++ b/backend/ops_api/ops/utils/auth_views.py @@ -114,9 +114,6 @@ def _get_token_and_user_data_from_internal_auth(user_data: dict[str, str]): # authZ, given a valid login from the prior AuthN steps above. additional_claims = {} - additional_claims["iss"] = "https://opre-ops-backend" # Placeholder - additional_claims["aud"] = "https://opre-ops-frontend" # Placeholder - if user.roles: additional_claims["roles"] = [role.name for role in user.roles] access_token = create_access_token( @@ -181,9 +178,7 @@ def _get_token_and_user_data_from_oauth_provider(provider: str, auth_code: str): def refresh() -> Response: user = get_current_user() if user: - additional_claims = {"iss": None, "aud": None, "roles": []} - additional_claims["iss"] = "https://opre-ops-backend" # Placeholder - additional_claims["aud"] = "https://opre-ops-frontend" # Placeholder + additional_claims = {"roles": []} current_app.logger.debug(f"user {user}") if user.roles: additional_claims["roles"] = [role.name for role in user.roles] From 9064995afe50e574b1b4e37f322ddf9c0998018e Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Wed, 20 Sep 2023 16:18:56 -0500 Subject: [PATCH 22/46] chore: :memo: updates Vest to v5 (#1482) Co-authored-by: Tim Donaworth <56687505+tdonaworth@users.noreply.github.com> --- frontend/package.json | 2 +- frontend/yarn.lock | 69 ++++++++++++++++++++++++------------------- 2 files changed, 40 insertions(+), 31 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 623a1db592..b64417e1d7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -30,7 +30,7 @@ "react-select": "5.7.4", "sass": "1.67.0", "sass-loader": "13.3.2", - "vest": "4.6.11" + "vest": "5.0.5" }, "devDependencies": { "@babel/plugin-proposal-private-property-in-object": "7.21.11", diff --git a/frontend/yarn.lock b/frontend/yarn.lock index bc90adc5ca..3b290b61f6 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -4581,12 +4581,12 @@ content-type@~1.0.4: resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== -context@^3.0.7: - version "3.0.7" - resolved "https://registry.yarnpkg.com/context/-/context-3.0.7.tgz#5b4fbe25173c049dad2f539089c32cd6b965fc83" - integrity sha512-ceW8mQwFhOin5ih5Sm6jq9mju7+p/usZFkf+PwNPXcpARplz1eAE4PB0V90Os/STyd1387Gu4C8I5Jmy3wDoIA== +context@^3.0.12: + version "3.0.12" + resolved "https://registry.yarnpkg.com/context/-/context-3.0.12.tgz#88ea1a2dd7a68f4a33b51bd471247d786881d2b3" + integrity sha512-LYsCOM/kjSw7brAjPZYQJJ0DReW14h1+4IGLiHNzo+M9WUuM9kI2nySI4fRoYmb2RV1dpGEbH1o48PX53l1Dyw== dependencies: - vest-utils "^0.1.1" + vest-utils "^1.0.3" convert-source-map@^1.0.0, convert-source-map@^1.4.0, convert-source-map@^1.5.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: version "1.9.0" @@ -9661,13 +9661,13 @@ mz@^2.7.0: object-assign "^4.0.1" thenify-all "^1.0.0" -n4s@^4.3.7: - version "4.3.7" - resolved "https://registry.yarnpkg.com/n4s/-/n4s-4.3.7.tgz#28c82f70889d2687d1ad5ffcd59a9f693cfd2f00" - integrity sha512-uzm6CL9cBMHaB/YuGBHRDRa6RuvpZ/xXgyoWgVS/+8/E0zQC3IyZVnIOPCOavCs/UOm4zomn99T4ng9HWcL7Cw== +n4s@^5.0.5: + version "5.0.5" + resolved "https://registry.yarnpkg.com/n4s/-/n4s-5.0.5.tgz#1724215219299d5f84b3a661e10c85b836b795f0" + integrity sha512-7HQmpM7nLCFgW0As6GYo0gR1C+S/Q4nCtyajKOeB/Y553d1RaAmPAkefai3jy6kAj2zHdiDuY2cudRhJF4w3Fg== dependencies: - context "^3.0.7" - vest-utils "^0.1.1" + context "^3.0.12" + vest-utils "^1.0.3" nan@^2.12.1: version "2.17.0" @@ -13527,12 +13527,12 @@ vary@~1.1.2: resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== -vast@^1.0.17: - version "1.0.17" - resolved "https://registry.yarnpkg.com/vast/-/vast-1.0.17.tgz#805bef4fe0ada79c7bd48e1cfd3302725a06d04a" - integrity sha512-D2fZtQp8RfZ8WMfzC0izcrrmOsZ8Dmc4B57G/J3z4wCAuvAZskyPOXBKLR5QRoQ4nb7doB/ueihEMnpDcXl7JA== +vast@^1.0.21: + version "1.0.21" + resolved "https://registry.yarnpkg.com/vast/-/vast-1.0.21.tgz#585a0e6e725c3e8d43d8a78c8ad5bab535a4d08b" + integrity sha512-8RJ0N0XfAQWs6aWDxtexUyHnbmVa8pPfwhpaj+5cl5S9tT6lkIlTAzNq02m5cqkn1IjaELfL9wnKn4BCRRV7iQ== dependencies: - vest-utils "^0.1.1" + vest-utils "^1.0.3" verror@1.10.0: version "1.10.0" @@ -13543,20 +13543,29 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" -vest-utils@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/vest-utils/-/vest-utils-0.1.1.tgz#a695552df5e437f8cf6656807aa458b11697359a" - integrity sha512-dsH4zt4IsBvDY3BhUo6mNgwN9WVIfUP8jwBp2XEZQAq7XYAuVQnGTCrdF8+/OtoP141jCxMppXYpO4xbMWYwhA== - -vest@4.6.11: - version "4.6.11" - resolved "https://registry.yarnpkg.com/vest/-/vest-4.6.11.tgz#aa72463c92f67e53a064d6db98db79552199b0e7" - integrity sha512-2C2PfDselDX5cgaxZmp4v1IaAhnGzlSkuQSSYIsOac3TW1+N6QFHoUx2n5qkLpe0thJLZjEWT4fM4anQSSjP2Q== - dependencies: - context "^3.0.7" - n4s "^4.3.7" - vast "^1.0.17" - vest-utils "^0.1.1" +vest-utils@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/vest-utils/-/vest-utils-1.0.3.tgz#430accf360ea65404616b9814b16ddf6c4928b48" + integrity sha512-zswws9QpoO1B0jG8CrnKiwPuEo6TyZNEnqWARmEYaGpNpr8niTHyxY4uvJ6RhPczW0lLy8exlmBtiqq7ArfhHw== + +vest@5.0.5: + version "5.0.5" + resolved "https://registry.yarnpkg.com/vest/-/vest-5.0.5.tgz#356da35f09cf0e99d7e81e4cacade7b668105c3e" + integrity sha512-2Lc2tKTlh72Qt6zeILws5C+RIXvLAI2bzLNQ2N85Nxrlqp3dpHhAjdB6WpnTQhvZ2EQGoiSsmSs+Weu1mD8Pbw== + dependencies: + context "^3.0.12" + n4s "^5.0.5" + vast "^1.0.21" + vest-utils "^1.0.3" + vestjs-runtime "^1.0.3" + +vestjs-runtime@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/vestjs-runtime/-/vestjs-runtime-1.0.3.tgz#69731bea2391dab840172ddba5724ae701697825" + integrity sha512-aRiY7eqTIZydTb23EdQteyYcrsTr3E5TIZo3qlg3A7Z+LalljjYFkkbP+jL34ca0iKbkg4NpJT+ogmkkdNIrig== + dependencies: + context "^3.0.12" + vest-utils "^1.0.3" vfile-message@^3.0.0: version "3.1.4" From e25a8799f42bf40a55301d504bb91ed52d5cb282 Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Thu, 21 Sep 2023 12:17:23 -0500 Subject: [PATCH 23/46] chore: :memo: updates prettier (#1477) * style: :rotating_light: updates prettier and eslint-plugin-prettier * style: :memo: yarn lint --fix * style: :art: yarn && yarn lint --fix * docs: adds prettier badge * style: :art: updates prettier config adds trailing comma none and singleAttributePerLine for better readability --------- Co-authored-by: Tim Donaworth <56687505+tdonaworth@users.noreply.github.com> --- README.md | 1 + frontend/.prettierrc.json | 4 +- frontend/package.json | 4 +- frontend/src/App.jsx | 2 +- frontend/src/api/apiLogin.js | 2 +- frontend/src/api/apiLogin.test.js | 2 +- frontend/src/api/getCANFundingSummary.test.js | 2 +- frontend/src/api/getCanFiscalYear.test.js | 2 +- .../src/api/getProcurementShopList.test.js | 6 +- frontend/src/api/getResearchProjects.test.js | 8 +- frontend/src/api/getUser.test.js | 4 +- frontend/src/api/opsAPI.js | 84 ++++----- frontend/src/api/opsAPI.test.js | 2 +- frontend/src/api/patchAgreements.test.js | 2 +- frontend/src/api/postAgreements.js | 4 +- frontend/src/api/postAgreements.test.js | 14 +- .../DeployedApplicationContext.js | 2 +- .../TestApplicationContext.js | 2 +- .../AgreementDetails/AgreementHistoryList.js | 21 ++- .../AgreementHistoryPanel.jsx | 9 +- .../AgreementDetails/InfiniteScroll.js | 9 +- .../AgreementTotalBudgetLinesCard.jsx | 7 +- .../AgreementTotalBudgetLinesCard.test.js | 11 +- .../AgreementValuesCard.jsx | 11 +- .../AgreementValuesCard.test.js | 18 +- .../AgreementEditor/AgreementEditForm.jsx | 42 +++-- .../AgreementEditor/AgreementEditorContext.js | 14 +- .../Agreements/DetailsTabs/DetailsTabs.jsx | 8 +- .../src/components/Auth/MultiAuthSection.jsx | 5 +- frontend/src/components/Auth/authSlice.js | 6 +- .../BudgetLineItems/AllBLIRow/AllBLIRow.js | 78 ++++++-- .../AllBudgetLinesTable.js | 10 +- .../BudgetLineItems/BLIRow/BLIRow.js | 47 +++-- .../BudgetLinesTable/BudgetLinesTable.jsx | 4 +- .../BudgetLinesTable/BudgetLinesTable.test.js | 6 +- .../BudgetLinesTotalSummaryCard.js | 7 +- .../ChangeIcons/ChangeIcons.js | 4 +- .../SummaryCardsSection.js | 7 +- .../TotalSummaryCard/TotalSummaryCard.jsx | 17 +- .../TotalSummaryCard/TotalSummaryCard.test.js | 6 +- .../CANBudgetSummary/CANBudgetSummary.jsx | 5 +- .../CANs/CANFundingBar/CANFundingBar.test.js | 15 +- .../CANs/CANFundingYTD/CANFundingYTD.jsx | 33 +++- .../src/components/CANs/CanCard/CanCard.jsx | 17 +- .../components/CANs/CanCard/CanCard.test.js | 2 +- .../TablePageLayout/TablePageLayout.js | 4 +- .../BudgetAndFunding/BudgetAndFunding.test.js | 6 +- .../PeopleAndTeams/PeopleAndTeams.test.js | 6 +- .../portfolioBudgetSummarySlice.js | 38 ++-- .../PortfolioBudgetSummary/util.test.js | 14 +- .../PortfolioFundingByBudgetStatus.jsx | 17 +- .../PortfolioFundingByCAN.jsx | 29 ++- .../PortfolioHeader/PortfolioHeader.jsx | 5 +- .../PortfolioCarryForwardFunding.jsx | 7 +- .../PortfolioFundingTotal.jsx | 28 ++- .../PortfolioNewFunding.jsx | 7 +- .../PortfolioTabsSection.jsx | 19 +- .../AdminAndSupportProjectsTable.jsx | 60 ++++-- .../AdminAndSupportProjectsTable.test.js | 5 +- .../ProjectsAndAgreements.jsx | 9 +- .../ResearchBudgetVsSpending.jsx | 28 ++- .../ResearchProjectFundingSlice.js | 6 +- .../ResearchProjects/ResearchProjects.jsx | 10 +- .../ResearchProjects/ResearchProjects.test.js | 6 +- .../ResearchProjectsTable.jsx | 60 ++++-- .../ResearchProjectsTable.test.js | 5 +- .../Portfolios/ResearchProjects/data.js | 12 +- .../getResearchProjectsFunding.js | 2 +- .../HeroFooter/HeroFooter.jsx | 27 ++- .../ProjectSummaryCard/ProjectSummaryCard.jsx | 5 +- frontend/src/components/UI/Alert/Alert.jsx | 6 +- .../src/components/UI/Alert/Alert.test.js | 4 +- .../src/components/UI/Alert/SimpleAlert.jsx | 6 +- .../src/components/UI/Alert/alertSlice.js | 6 +- .../CurrencySummaryCard.jsx | 6 +- .../CurrencySummaryCard.test.js | 5 +- .../CurrencyWithSmallCents.jsx | 7 +- .../UI/FilterButton/FilterButton.jsx | 15 +- .../components/UI/FilterTags/FilterTags.jsx | 5 +- .../src/components/UI/FilterTags/utils.js | 4 +- .../components/UI/FiscalYear/FiscalYear.jsx | 10 +- .../src/components/UI/Footer/AgencyInfo.jsx | 6 +- frontend/src/components/UI/Footer/Footer.jsx | 7 +- .../src/components/UI/Footer/Identifier.jsx | 51 ++++- .../src/components/UI/Footer/NavFooter.jsx | 25 ++- .../UI/Form/AgreementReasonSelect.jsx | 19 +- .../Form/AgreementSelect/AgreementSelect.jsx | 59 ++++-- .../AgreementSelect/AgreementSelect.test.js | 23 ++- .../UI/Form/AgreementTypeSelect.jsx | 14 +- .../BLIStatusComboBox/BLIStatusComboBox.jsx | 21 ++- .../BLIStatusComboBox.test.js | 30 ++- .../UI/Form/CanSelect/CanSelect.jsx | 37 +++- .../components/UI/Form/ComboBox/ComboBox.jsx | 26 +-- .../UI/Form/ComboBox/ComboBox.test.js | 2 +- .../CreateBudgetLinesForm.jsx | 10 +- .../CreateBudgetLinesForm/CurrencyInput.jsx | 12 +- .../UI/Form/DesiredAwardDate/DayInput.jsx | 7 +- .../DesiredAwardDate/DesiredAwardDate.jsx | 9 +- .../DesiredAwardDate/DesiredAwardDate.test.js | 2 +- .../UI/Form/DesiredAwardDate/MonthSelect.jsx | 7 +- .../UI/Form/DesiredAwardDate/YearInput.jsx | 7 +- .../FiscalYearComboBox/FiscalYearComboBox.jsx | 9 +- .../FiscalYearComboBox.test.js | 26 ++- .../src/components/UI/Form/Input/Input.jsx | 10 +- .../PortfoliosComboBox/PortfoliosComboBox.jsx | 9 +- .../PortfoliosComboBox.test.js | 28 ++- .../ProcurementShopSelect.jsx | 16 +- .../ProcurementShopSelect.test.js | 30 ++- .../ProcurementShopSelectWithFee.jsx | 7 +- .../ProcurementShopSelectWithFee.test.js | 22 ++- .../UI/Form/ProductServiceCodeSelect.jsx | 17 +- .../UI/Form/ProductServiceCodeSummaryBox.jsx | 4 +- .../ProjectAgreementSummaryCard.jsx | 22 ++- .../ProjectAgreementSummaryCard.test.js | 2 +- .../Form/ProjectComboBox/ProjectComboBox.jsx | 9 +- .../ProjectComboBox/ProjectComboBox.test.js | 2 +- .../UI/Form/ProjectOfficerComboBox.jsx | 8 +- .../ProjectSelectWithSummaryCard.jsx | 13 +- .../ProjectSelectWithSummaryCard.test.js | 2 +- .../ProjectTypeSelect/ProjectTypeSelect.jsx | 18 +- .../components/UI/Form/TeamMemberComboBox.jsx | 10 +- .../src/components/UI/Form/TeamMemberList.jsx | 5 +- .../components/UI/Form/TextArea/TextArea.jsx | 23 ++- .../src/components/UI/Header/Breadcrumb.jsx | 17 +- .../src/components/UI/Header/GovBanner.jsx | 15 +- frontend/src/components/UI/Header/Header.jsx | 33 +++- frontend/src/components/UI/Header/Menu.jsx | 10 +- frontend/src/components/UI/Header/Search.jsx | 29 ++- frontend/src/components/UI/Header/User.jsx | 5 +- frontend/src/components/UI/Hero/Hero.jsx | 10 +- .../UI/HeroDescription/HeroDescription.jsx | 24 ++- .../UI/Layouts/DefaultLayout/DefaultLayout.js | 7 +- frontend/src/components/UI/LogItem/LogItem.js | 7 +- .../UI/Modals/ConfirmationModal.jsx | 16 +- .../components/UI/Modals/ContainerModal.jsx | 16 +- .../NotificationCenter/NotificationCenter.jsx | 7 +- .../UI/PaginationNav/PaginationNav.jsx | 22 ++- .../CustomLayerComponent.jsx | 2 +- .../ResponsiveDonutWithInnerPercent.jsx | 2 +- .../components/UI/RoundedBox/RoundedBox.jsx | 7 +- .../UI/RoundedBox/RoundedBox.test.js | 5 +- .../UI/StepIndicator/StepIndicator.jsx | 7 +- .../UI/StepIndicator/StepIndicator.test.js | 42 ++++- .../components/UI/SummaryCard/SummaryCard.js | 2 +- frontend/src/components/UI/Table/Table.js | 8 +- .../TableRowExpandable/TableRowExpandable.js | 12 +- .../src/components/UI/TableTag/TableTag.js | 7 +- frontend/src/components/UI/Tag/StatusTag.js | 15 +- frontend/src/components/UI/Tag/Tag.jsx | 7 +- .../components/UI/TeamLeaders/TeamLeaders.jsx | 5 +- .../StepCreateBudgetLines.jsx | 52 +++--- .../StepCreateBudgetLines/context.js | 24 +-- .../StepCreateBudgetLines/index.js | 2 +- .../Users/UserInfoForm/EditUserForm.jsx | 45 ++++- .../Users/UserInfoForm/UserInfoForm.jsx | 2 +- frontend/src/constants.js | 14 +- frontend/src/helpers/backend.js | 10 +- frontend/src/helpers/mocks.js | 6 +- frontend/src/helpers/test.js | 6 +- frontend/src/helpers/use-alert.js | 2 +- frontend/src/helpers/useSortableData.test.js | 14 +- frontend/src/helpers/utils.js | 16 +- frontend/src/index.jsx | 135 ++++++++++---- frontend/src/pages/ErrorPage.jsx | 10 +- frontend/src/pages/Login.jsx | 26 ++- .../pages/agreements/CreateAgreementFlow.jsx | 4 +- .../pages/agreements/CreateEditAgreement.jsx | 14 +- .../src/pages/agreements/EditAgreement.js | 20 +- .../pages/agreements/StepCreateAgreement.js | 13 +- .../pages/agreements/StepSelectProject.jsx | 9 +- .../src/pages/agreements/details/Agreement.js | 10 +- .../details/AgreementBudgetLines.js | 11 +- .../details/AgreementDetailHeader.js | 2 +- .../agreements/details/AgreementDetails.js | 7 +- .../details/AgreementDetails.test.js | 127 ++++++------- .../details/AgreementDetailsEdit.js | 7 +- .../details/AgreementDetailsEdit.test.js | 35 ++-- .../details/AgreementDetailsView.js | 32 +++- frontend/src/pages/agreements/details/data.js | 20 +- .../agreements/list/AgreementTableRow.jsx | 95 +++++++--- .../agreements/list/AgreementTableRow.test.js | 25 +-- .../list/AgreementsFilterButton.jsx | 98 +++++++--- .../agreements/list/AgreementsFilterTags.jsx | 45 ++--- .../pages/agreements/list/AgreementsList.jsx | 20 +- .../pages/agreements/list/AgreementsTable.jsx | 20 +- .../agreements/list/AgreementsTable.test.js | 18 +- .../pages/agreements/list/AgreementsTabs.jsx | 13 +- frontend/src/pages/agreements/list/utils.js | 4 +- .../src/pages/agreements/list/utils.test.js | 76 ++++---- .../agreements/review/ReviewAgreement.jsx | 40 ++-- .../src/pages/agreements/review/Terms.jsx | 5 +- .../pages/budgetLines/CreateBudgetLine.jsx | 2 +- .../budgetLines/CreateBudgetLineFlow.jsx | 4 +- .../StepSelectProjectAndAgreement.jsx | 17 +- .../src/pages/budgetLines/StepSuccess.jsx | 2 +- .../pages/budgetLines/budgetLineContext.js | 2 +- .../budgetLines/list/BLIFilterButton.jsx | 29 ++- .../pages/budgetLines/list/BLIFilterTags.jsx | 11 +- .../budgetLines/list/BudgetLineItemList.jsx | 20 +- .../src/pages/cans/detail/canDetailSlice.js | 6 +- frontend/src/pages/cans/detail/getCan.test.js | 2 +- frontend/src/pages/cans/list/canListSlice.js | 6 +- .../src/pages/cans/list/getCanList.test.js | 8 +- .../portfolios/detail/PortfolioDetail.jsx | 10 +- .../portfolios/detail/getPortfolio.test.js | 6 +- .../portfolios/detail/getResearchProjects.js | 2 +- .../detail/getResearchProjects.test.js | 4 +- .../pages/portfolios/detail/portfolioSlice.js | 8 +- .../portfolios/list/getPortfolioList.test.js | 8 +- .../portfolios/list/portfolioListSlice.js | 6 +- frontend/src/pages/projects/CreateProject.jsx | 20 +- .../detail/getResearchProject.test.js | 6 +- .../detail/researchProjectSlice.js | 6 +- frontend/src/pages/users/detail/userSlice.js | 6 +- frontend/src/pages/users/edit/EditUser.jsx | 2 +- frontend/src/pages/users/edit/userSlice.js | 6 +- frontend/src/store.js | 6 +- frontend/yarn.lock | 175 ++++++++++++++++-- 218 files changed, 2486 insertions(+), 1088 deletions(-) diff --git a/README.md b/README.md index 2c529d0adb..6307d52826 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # OPRE OPS [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)](https://github.com/pre-commit/pre-commit) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) +[![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier) This is the OPRE's Research Portfolio Management System, or OPS. The finished product will replace OPRE's prior system, [MAPS](https://github.com/HHS/MAPS-app). The purpose of OPS can be found on diff --git a/frontend/.prettierrc.json b/frontend/.prettierrc.json index e8980d15c5..438e35166b 100644 --- a/frontend/.prettierrc.json +++ b/frontend/.prettierrc.json @@ -1,4 +1,6 @@ { "printWidth": 120, - "tabWidth": 4 + "tabWidth": 4, + "trailingComma": "none", + "singleAttributePerLine": true } diff --git a/frontend/package.json b/frontend/package.json index b64417e1d7..4bfb0ae193 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -48,14 +48,14 @@ "eslint-config-prettier": "9.0.0", "eslint-plugin-cypress": "2.14.0", "eslint-plugin-import": "2.28.1", - "eslint-plugin-prettier": "4.2.1", + "eslint-plugin-prettier": "5.0.0", "eslint-plugin-react": "7.33.2", "eslint-plugin-react-hooks": "4.6.0", "eslint-plugin-react-refresh": "0.4.3", "history": "5.3.0", "jose": "4.14.6", "msw": "1.3.1", - "prettier": "2.8.8", + "prettier": "3.0.3", "redux-mock-store": "1.5.4" }, "scripts": { diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index b80a135057..1864c54aac 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -11,6 +11,6 @@ const App = ({ children, breadCrumbName }) => { .callBackend(`/api/${api_version}/auth/login/`, "post", { callbackUrl: window.location.href, code: authCode, - provider: provider, + provider: provider }); return responseData; }; diff --git a/frontend/src/api/apiLogin.test.js b/frontend/src/api/apiLogin.test.js index cc571b1390..00a3ce6cab 100644 --- a/frontend/src/api/apiLogin.test.js +++ b/frontend/src/api/apiLogin.test.js @@ -5,7 +5,7 @@ test("successfully gets an access_token and refresh_token from the backend", asy const auth_code = 99999999; const mockBackendResponse = { access_token: "super-secure-token", - refresh_token: "also-super-secure", + refresh_token: "also-super-secure" }; TestApplicationContext.helpers().callBackend.mockImplementation(async () => { return [mockBackendResponse]; diff --git a/frontend/src/api/getCANFundingSummary.test.js b/frontend/src/api/getCANFundingSummary.test.js index 7b1e27b6c5..54952102e2 100644 --- a/frontend/src/api/getCANFundingSummary.test.js +++ b/frontend/src/api/getCANFundingSummary.test.js @@ -13,7 +13,7 @@ test("successfully gets the can funding details from the backend", async () => { obligated_funding: 6, in_execution_funding: 7, available_funding: 8, - expiration_date: "01/01/2023", + expiration_date: "01/01/2023" }; TestApplicationContext.helpers().callBackend.mockImplementation(async () => { diff --git a/frontend/src/api/getCanFiscalYear.test.js b/frontend/src/api/getCanFiscalYear.test.js index 7d105fbfa2..e8e2a1ef7b 100644 --- a/frontend/src/api/getCanFiscalYear.test.js +++ b/frontend/src/api/getCanFiscalYear.test.js @@ -8,7 +8,7 @@ test("successfully gets the CFY from the backend by can_id and fiscal_year", asy fiscal_year: 2022, otherStuff: "DogCow", total_fiscal_year_funding: 10, - amount_available: 5, + amount_available: 5 }; TestApplicationContext.helpers().callBackend.mockImplementation(async () => { return [mockBackendResponse]; diff --git a/frontend/src/api/getProcurementShopList.test.js b/frontend/src/api/getProcurementShopList.test.js index 67c4434e8d..d9e7c3c960 100644 --- a/frontend/src/api/getProcurementShopList.test.js +++ b/frontend/src/api/getProcurementShopList.test.js @@ -7,12 +7,12 @@ describe("getProcurementShopList", () => { const mockProcurementShopList = [ { id: 1, - name: "Procurement Shop 1", + name: "Procurement Shop 1" }, { id: 2, - name: "Procurement Shop 2", - }, + name: "Procurement Shop 2" + } ]; TestApplicationContext.helpers().callBackend.mockImplementation(async () => { return mockProcurementShopList; diff --git a/frontend/src/api/getResearchProjects.test.js b/frontend/src/api/getResearchProjects.test.js index 409aa1f71c..34164a4237 100644 --- a/frontend/src/api/getResearchProjects.test.js +++ b/frontend/src/api/getResearchProjects.test.js @@ -6,18 +6,18 @@ const mockBackendResponse = [ { id: 1, name: "Test Research Project 1", - description: "A test research project 1.", + description: "A test research project 1." }, { id: 2, name: "Test Research Project 2", - description: "A test research project 2.", + description: "A test research project 2." }, { id: 3, name: "Test Research Project 3", - description: "A test research project 3.", - }, + description: "A test research project 3." + } ]; test("successfully gets a research project by ID", async () => { diff --git a/frontend/src/api/getUser.test.js b/frontend/src/api/getUser.test.js index c79abe7a16..1ac907c9b1 100644 --- a/frontend/src/api/getUser.test.js +++ b/frontend/src/api/getUser.test.js @@ -7,7 +7,7 @@ test("successfully gets a User from the backend by user_id", async () => { id: userId, oidc_id: "00000000-0000-1111-a111-000000000004", email: "username@domain.com", - first_name: "User", + first_name: "User" }; TestApplicationContext.helpers().callBackend.mockImplementation(async () => { return [mockBackendResponse]; @@ -23,7 +23,7 @@ test("successfully gets a User from the backend by user_oidc_id", async () => { id: oidcId, oidc_id: "00000000-0000-1111-a111-000000000004", email: "username@domain.com", - first_name: "User", + first_name: "User" }; TestApplicationContext.helpers().callBackend.mockImplementation(async () => { return [mockBackendResponse]; diff --git a/frontend/src/api/opsAPI.js b/frontend/src/api/opsAPI.js index 7e954dc20a..d480f0cdd1 100644 --- a/frontend/src/api/opsAPI.js +++ b/frontend/src/api/opsAPI.js @@ -12,7 +12,7 @@ export const opsApi = createApi({ "AgreementReasons", "ProcurementShops", "BudgetLineItems", - "AgreementHistory", + "AgreementHistory" ], baseQuery: fetchBaseQuery({ baseUrl: `${BACKEND_DOMAIN}/api/v1/`, @@ -24,16 +24,16 @@ export const opsApi = createApi({ } return headers; - }, + } }), endpoints: (builder) => ({ getAgreements: builder.query({ query: () => `/agreements/`, - providesTags: ["Agreements", "BudgetLineItems"], + providesTags: ["Agreements", "BudgetLineItems"] }), getAgreementById: builder.query({ query: (id) => `/agreements/${id}`, - providesTags: ["Agreements"], + providesTags: ["Agreements"] }), addAgreement: builder.mutation({ query: (data) => { @@ -41,10 +41,10 @@ export const opsApi = createApi({ url: `/agreements/`, method: "POST", headers: { "Content-Type": "application/json" }, - body: data, + body: data }; }, - invalidatesTags: ["Agreements", "BudgetLineItems", "AgreementHistory"], + invalidatesTags: ["Agreements", "BudgetLineItems", "AgreementHistory"] }), updateAgreement: builder.mutation({ query: ({ id, data }) => { @@ -52,21 +52,21 @@ export const opsApi = createApi({ url: `/agreements/${id}`, method: "PATCH", headers: { "Content-Type": "application/json" }, - body: data, + body: data }; }, - invalidatesTags: ["Agreements", "BudgetLineItems", "AgreementHistory"], + invalidatesTags: ["Agreements", "BudgetLineItems", "AgreementHistory"] }), deleteAgreement: builder.mutation({ query: (id) => ({ url: `/agreements/${id}`, - method: "DELETE", + method: "DELETE" }), - invalidatesTags: ["Agreements", "BudgetLineItems", "AgreementHistory"], + invalidatesTags: ["Agreements", "BudgetLineItems", "AgreementHistory"] }), getBudgetLineItems: builder.query({ query: () => `/budget-line-items/`, - providesTags: ["BudgetLineItems"], + providesTags: ["BudgetLineItems"] }), addBudgetLineItem: builder.mutation({ query: (data) => { @@ -74,10 +74,10 @@ export const opsApi = createApi({ url: `/budget-line-items/`, method: "POST", headers: { "Content-Type": "application/json" }, - body: data, + body: data }; }, - invalidatesTags: ["Agreements", "BudgetLineItems", "AgreementHistory"], + invalidatesTags: ["Agreements", "BudgetLineItems", "AgreementHistory"] }), updateBudgetLineItem: builder.mutation({ query: ({ id, data }) => { @@ -85,112 +85,112 @@ export const opsApi = createApi({ url: `/budget-line-items/${id}`, method: "PATCH", headers: { "Content-Type": "application/json" }, - body: data, + body: data }; }, - invalidatesTags: ["Agreements", "BudgetLineItems", "AgreementHistory"], + invalidatesTags: ["Agreements", "BudgetLineItems", "AgreementHistory"] }), getAgreementsByResearchProjectFilter: builder.query({ query: (id) => `/agreements/?research_project_id=${id}`, - providesTags: ["Agreements", "FilterAgreements"], + providesTags: ["Agreements", "FilterAgreements"] }), getUserById: builder.query({ query: (id) => `/users/${id}`, - providesTags: ["Users"], + providesTags: ["Users"] }), getUserByOIDCId: builder.query({ query: (id) => `/users/?oidc_id=${id}`, - providesTags: ["Users"], + providesTags: ["Users"] }), getResearchProjects: builder.query({ query: () => `/research-projects/`, - providesTags: ["ResearchProjects"], + providesTags: ["ResearchProjects"] }), addResearchProjects: builder.mutation({ query: (body) => ({ url: `/research-projects/`, method: "POST", - body, + body }), - invalidatesTags: ["ResearchProjects"], + invalidatesTags: ["ResearchProjects"] }), updateBudgetLineItemStatus: builder.mutation({ query: ({ id, status }) => ({ url: `/budget-line-items/${id}`, method: "PATCH", headers: { "Content-Type": "application/json" }, - body: { status }, + body: { status } }), - invalidatesTags: ["Agreements", "BudgetLineItems"], + invalidatesTags: ["Agreements", "BudgetLineItems"] }), getAgreementTypes: builder.query({ query: () => `/agreement-types/`, - providesTags: ["AgreementTypes"], + providesTags: ["AgreementTypes"] }), getProductServiceCodes: builder.query({ query: () => `/product-service-codes/`, - providesTags: ["ProductServiceCodes"], + providesTags: ["ProductServiceCodes"] }), getProcurementShops: builder.query({ query: () => `/procurement-shops/`, - providesTags: ["ProcurementShops"], + providesTags: ["ProcurementShops"] }), getAgreementReasons: builder.query({ query: () => `/agreement-reasons/`, - providesTags: ["AgreementReasons"], + providesTags: ["AgreementReasons"] }), getUsers: builder.query({ query: () => `/users/`, - providesTags: ["Users"], + providesTags: ["Users"] }), getUser: builder.query({ query: (id) => `/users/${id}`, - providesTags: ["User"], + providesTags: ["User"] }), getUserByOidc: builder.query({ query: (oidc_id) => `/users/?oidc_id=${oidc_id}`, - providesTags: ["User"], + providesTags: ["User"] }), addUser: builder.mutation({ query: (body) => ({ url: `/users/`, method: "POST", headers: { "Content-Type": "application/json" }, - body, + body }), - invalidatesTags: ["User"], + invalidatesTags: ["User"] }), updateUser: builder.mutation({ query: ({ id, body }) => ({ url: `/users/${id}`, method: "PUT", headers: { "Content-Type": "application/json" }, - body, + body }), - invalidatesTags: ["User"], + invalidatesTags: ["User"] }), getCans: builder.query({ query: () => `/cans/`, - providesTags: ["Cans"], + providesTags: ["Cans"] }), getNotificationsByUserId: builder.query({ query: (id) => `/notifications/?oidc_id=${id}`, - providesTags: ["Notifications"], + providesTags: ["Notifications"] }), dismissNotification: builder.mutation({ query: (id) => ({ url: `/notifications/${id}`, method: "PATCH", headers: { "Content-Type": "application/json" }, - body: { is_read: true }, + body: { is_read: true } }), - invalidatesTags: ["Notifications"], + invalidatesTags: ["Notifications"] }), getPortfolios: builder.query({ query: () => `/portfolios/`, - providesTags: ["Portfolios"], - }), - }), + providesTags: ["Portfolios"] + }) + }) }); export const { @@ -220,5 +220,5 @@ export const { useGetCansQuery, useGetNotificationsByUserIdQuery, useDismissNotificationMutation, - useGetPortfoliosQuery, + useGetPortfoliosQuery } = opsApi; diff --git a/frontend/src/api/opsAPI.test.js b/frontend/src/api/opsAPI.test.js index f8386479fc..3dcd912da3 100644 --- a/frontend/src/api/opsAPI.test.js +++ b/frontend/src/api/opsAPI.test.js @@ -19,7 +19,7 @@ describe("opsApi", () => { // Mock response for getAgreements endpoint const mockData = [ { id: 1, name: "Agreement 5" }, - { id: 2, name: "Agreement 6" }, + { id: 2, name: "Agreement 6" } ]; // This will override any API qury performed, for all endpoints, diff --git a/frontend/src/api/patchAgreements.test.js b/frontend/src/api/patchAgreements.test.js index 8adc38f0ca..bf00f93f21 100644 --- a/frontend/src/api/patchAgreements.test.js +++ b/frontend/src/api/patchAgreements.test.js @@ -5,7 +5,7 @@ describe("patchAgreement function", () => { const agreementId = 1; const mockAgreement = { description: "PATCH Description", - notes: "PATCH Notes", + notes: "PATCH Notes" }; const mockApiResponse = { id: 1, message: "Agreement Updated" }; diff --git a/frontend/src/api/postAgreements.js b/frontend/src/api/postAgreements.js index 3c91f1d31c..9c60a79222 100644 --- a/frontend/src/api/postAgreements.js +++ b/frontend/src/api/postAgreements.js @@ -6,7 +6,7 @@ export const postAgreement = async (item) => { const data = { ...item }; const newAgreement = { ...data, - number: "", + number: "" }; delete newAgreement.id; @@ -43,6 +43,6 @@ export const formatTeamMember = (team_member) => { return { id: team_member.id, full_name: team_member.full_name, - email: team_member.email, + email: team_member.email }; }; diff --git a/frontend/src/api/postAgreements.test.js b/frontend/src/api/postAgreements.test.js index d8d816826a..f34ed53ace 100644 --- a/frontend/src/api/postAgreements.test.js +++ b/frontend/src/api/postAgreements.test.js @@ -23,7 +23,7 @@ describe("postAgreement function", () => { last_name: "Martinez-Beck", oidc_id: "00000000-0000-1111-a111-000000000003", updated: null, - updated_on: "2023-04-25T17:22:11.766571", + updated_on: "2023-04-25T17:22:11.766571" }, { created_by: null, @@ -37,10 +37,10 @@ describe("postAgreement function", () => { last_name: "Brown", oidc_id: "00000000-0000-1111-a111-000000000005", updated: null, - updated_on: "2023-04-25T17:22:11.766571", - }, + updated_on: "2023-04-25T17:22:11.766571" + } ], - notes: "New Agreement for purpose X", + notes: "New Agreement for purpose X" }; const emptyMockAgreement = { @@ -52,7 +52,7 @@ describe("postAgreement function", () => { incumbent: null, project_officer: null, team_members: [], - notes: "", + notes: "" }; const mockApiResponse = { id: 1, message: "Agreement created" }; @@ -94,13 +94,13 @@ describe("formatTeamMember function", () => { last_name: "Member A", oidc_id: "00000000-0000-1111-a111-000000000002", updated: null, - updated_on: "2023-04-24T18:14:38.156209", + updated_on: "2023-04-24T18:14:38.156209" }; const expectedOutput = { id: 123, full_name: "Team Member A", - email: "tm_a@test.com", + email: "tm_a@test.com" }; const output = formatTeamMember(input); diff --git a/frontend/src/applicationContext/DeployedApplicationContext.js b/frontend/src/applicationContext/DeployedApplicationContext.js index 577e6eee49..c784636be8 100644 --- a/frontend/src/applicationContext/DeployedApplicationContext.js +++ b/frontend/src/applicationContext/DeployedApplicationContext.js @@ -5,7 +5,7 @@ class DeployedApplicationContext { return { callBackend, authConfig, - backEndConfig, + backEndConfig }; } } diff --git a/frontend/src/applicationContext/TestApplicationContext.js b/frontend/src/applicationContext/TestApplicationContext.js index 9e5f966cf1..71f4df0a64 100644 --- a/frontend/src/applicationContext/TestApplicationContext.js +++ b/frontend/src/applicationContext/TestApplicationContext.js @@ -5,7 +5,7 @@ class TestApplicationContext { static #helpers = { callBackend: jest.fn(), authConfig, - backEndConfig, + backEndConfig }; static helpers() { diff --git a/frontend/src/components/Agreements/AgreementDetails/AgreementHistoryList.js b/frontend/src/components/Agreements/AgreementDetails/AgreementHistoryList.js index cdbed3acfd..7aeeff06e9 100644 --- a/frontend/src/components/Agreements/AgreementDetails/AgreementHistoryList.js +++ b/frontend/src/components/Agreements/AgreementDetails/AgreementHistoryList.js @@ -61,47 +61,47 @@ const ChangesDetails = ({ historyItem }) => { propertyLabel: getPropertyLabel(historyItem.class_name, key), isCollection: true, added: added, - deleted: deleted, + deleted: deleted }; } else if (key === "procurement_shop_id") { const new_val = historyItem.event_details?.procurement_shop?.name; return { key: key, propertyLabel: getPropertyLabel(historyItem.class_name, "procurement_shop"), - to: new_val, + to: new_val }; } else if (key === "product_service_code_id") { const new_val = historyItem.event_details?.product_service_code?.name; return { key: key, propertyLabel: getPropertyLabel(historyItem.class_name, "product_service_code"), - to: new_val, + to: new_val }; } else if (key === "project_officer") { return { key: key, - propertyLabel: getPropertyLabel(historyItem.class_name, "project_officer"), + propertyLabel: getPropertyLabel(historyItem.class_name, "project_officer") }; } else if (key === "research_project_id") { const new_val = historyItem.event_details?.research_project?.title; return { key: key, propertyLabel: getPropertyLabel(historyItem.class_name, "research_project"), - to: new_val, + to: new_val }; } else if (key === "can_id") { const new_val = historyItem.event_details?.can?.number; return { key: key, propertyLabel: getPropertyLabel(historyItem.class_name, "can"), - to: new_val, + to: new_val }; } else return { key: key, propertyLabel: getPropertyLabel(historyItem.class_name, key), from: change.old, - to: change.new, + to: change.new }; }); @@ -134,7 +134,10 @@ const AgreementHistoryList = ({ agreementHistory }) => { return ( <> {agreementHistory && agreementHistory.length > 0 ? ( -
    +
      {agreementHistory.map((item, index) => ( { }; AgreementHistoryList.propTypes = { - agreementHistory: PropTypes.arrayOf(Object), + agreementHistory: PropTypes.arrayOf(Object) }; export default AgreementHistoryList; diff --git a/frontend/src/components/Agreements/AgreementDetails/AgreementHistoryPanel.jsx b/frontend/src/components/Agreements/AgreementDetails/AgreementHistoryPanel.jsx index 88afc6b422..42ef13a5f7 100644 --- a/frontend/src/components/Agreements/AgreementDetails/AgreementHistoryPanel.jsx +++ b/frontend/src/components/Agreements/AgreementDetails/AgreementHistoryPanel.jsx @@ -40,7 +40,12 @@ const AgreementHistoryPanel = ({ agreementId }) => { ) : ( <> - {!stopped && } + {!stopped && ( + + )} )}
@@ -48,7 +53,7 @@ const AgreementHistoryPanel = ({ agreementId }) => { }; AgreementHistoryPanel.propTypes = { - agreementId: PropTypes.number.isRequired, + agreementId: PropTypes.number.isRequired }; export default AgreementHistoryPanel; diff --git a/frontend/src/components/Agreements/AgreementDetails/InfiniteScroll.js b/frontend/src/components/Agreements/AgreementDetails/InfiniteScroll.js index 560ab1a2f5..6018e427f5 100644 --- a/frontend/src/components/Agreements/AgreementDetails/InfiniteScroll.js +++ b/frontend/src/components/Agreements/AgreementDetails/InfiniteScroll.js @@ -16,7 +16,7 @@ const InfiniteScroll = ({ fetchMoreData, isLoading }) => { useEffect(() => { const observer = new IntersectionObserver(handleIntersection, { - threshold: 0.1, + threshold: 0.1 }); if (observerRef.current) { @@ -31,7 +31,12 @@ const InfiniteScroll = ({ fetchMoreData, isLoading }) => { }; }, [observerRef, isLoading, fetchMoreData, isFetching]); // eslint-disable-line react-hooks/exhaustive-deps - return
; + return ( +
+ ); }; export default InfiniteScroll; diff --git a/frontend/src/components/Agreements/AgreementDetailsCards/AgreementTotalBudgetLinesCard.jsx b/frontend/src/components/Agreements/AgreementDetailsCards/AgreementTotalBudgetLinesCard.jsx index 2b20ee9727..d4e4a32537 100644 --- a/frontend/src/components/Agreements/AgreementDetailsCards/AgreementTotalBudgetLinesCard.jsx +++ b/frontend/src/components/Agreements/AgreementDetailsCards/AgreementTotalBudgetLinesCard.jsx @@ -18,7 +18,10 @@ const AgreementTotalBudgetLinesCard = ({ numberOfAgreements = 0, countsByStatus

{headerText}

- + {numberOfAgreements}
@@ -32,7 +35,7 @@ const AgreementTotalBudgetLinesCard = ({ numberOfAgreements = 0, countsByStatus AgreementTotalBudgetLinesCard.propTypes = { numberOfAgreements: PropTypes.number.isRequired, - countsByStatus: PropTypes.object.isRequired, + countsByStatus: PropTypes.object.isRequired }; export default AgreementTotalBudgetLinesCard; diff --git a/frontend/src/components/Agreements/AgreementDetailsCards/AgreementTotalBudgetLinesCard.test.js b/frontend/src/components/Agreements/AgreementDetailsCards/AgreementTotalBudgetLinesCard.test.js index 157e23c918..627852023a 100644 --- a/frontend/src/components/Agreements/AgreementDetailsCards/AgreementTotalBudgetLinesCard.test.js +++ b/frontend/src/components/Agreements/AgreementDetailsCards/AgreementTotalBudgetLinesCard.test.js @@ -4,7 +4,7 @@ import AgreementTotalBudgetLinesCard from "./AgreementTotalBudgetLinesCard"; jest.mock("react", () => ({ ...jest.requireActual("react"), - useState: () => [null, jest.fn()], + useState: () => [null, jest.fn()] })); // This will reset all mocks after each test @@ -17,9 +17,14 @@ describe("AgreementTotalBudgetLinesCard", () => { const countsByStatus = { IN_EXECUTION: 8, OBLIGATED: 1, - PLANNED: 7, + PLANNED: 7 }; - render(); + render( + + ); expect(screen.getByText("0 Draft")).toBeInTheDocument(); expect(screen.getByText("0 In Review")).toBeInTheDocument(); diff --git a/frontend/src/components/Agreements/AgreementDetailsCards/AgreementValuesCard.jsx b/frontend/src/components/Agreements/AgreementDetailsCards/AgreementValuesCard.jsx index 1bcfd26327..6c9eb2507f 100644 --- a/frontend/src/components/Agreements/AgreementDetailsCards/AgreementValuesCard.jsx +++ b/frontend/src/components/Agreements/AgreementDetailsCards/AgreementValuesCard.jsx @@ -37,14 +37,17 @@ const AgreementTotalBudgetLinesCard = ({ budgetLineItems }) => { return { FY: fyVal.fiscalYear, budget: fyVal.amount, - color: barChartColors[index].color, + color: barChartColors[index].color }; }) // sort by year descending .sort((a, b) => b.FY - a.FY); return ( - +

Budget Lines Over Next 3 FYs

@@ -60,7 +63,7 @@ const AgreementTotalBudgetLinesCard = ({ budgetLineItems }) => { colors={{ datum: "data.color" }} borderColor={{ from: "color", - modifiers: [["darker", 1.6]], + modifiers: [["darker", 1.6]] }} axisTop={null} axisRight={null} @@ -83,7 +86,7 @@ const AgreementTotalBudgetLinesCard = ({ budgetLineItems }) => { }; AgreementTotalBudgetLinesCard.propTypes = { - budgetLineItems: PropTypes.array.isRequired, + budgetLineItems: PropTypes.array.isRequired }; export default AgreementTotalBudgetLinesCard; diff --git a/frontend/src/components/Agreements/AgreementDetailsCards/AgreementValuesCard.test.js b/frontend/src/components/Agreements/AgreementDetailsCards/AgreementValuesCard.test.js index a468e45d32..f1ec503cdd 100644 --- a/frontend/src/components/Agreements/AgreementDetailsCards/AgreementValuesCard.test.js +++ b/frontend/src/components/Agreements/AgreementDetailsCards/AgreementValuesCard.test.js @@ -7,12 +7,12 @@ jest.mock("@nivo/bar", () => ({ __esModule: true, ResponsiveBar: () => { return
; - }, + } })); jest.mock("react", () => ({ ...jest.requireActual("react"), - useState: () => [null, jest.fn()], + useState: () => [null, jest.fn()] })); // This will reset all mocks after each test @@ -34,7 +34,7 @@ describe("AgreementValuesCard", () => { line_description: "Line Item 1", psc_fee_amount: 0.5, status: "PLANNED", - updated_on: "2023-07-26T16:22:35.470618", + updated_on: "2023-07-26T16:22:35.470618" }, { agreement_id: 2, @@ -48,7 +48,7 @@ describe("AgreementValuesCard", () => { line_description: "Line Item 2", psc_fee_amount: 0.5, status: "PLANNED", - updated_on: "2023-07-26T16:22:35.470618", + updated_on: "2023-07-26T16:22:35.470618" }, { agreement_id: 2, @@ -62,7 +62,7 @@ describe("AgreementValuesCard", () => { line_description: "Line Item 1", psc_fee_amount: 0.5, status: "PLANNED", - updated_on: "2023-07-26T16:22:35.470618", + updated_on: "2023-07-26T16:22:35.470618" }, { agreement_id: 2, @@ -76,7 +76,7 @@ describe("AgreementValuesCard", () => { line_description: "Line Item 2", psc_fee_amount: 0.5, status: "PLANNED", - updated_on: "2023-07-26T16:22:35.470618", + updated_on: "2023-07-26T16:22:35.470618" }, { agreement_id: 2, @@ -90,7 +90,7 @@ describe("AgreementValuesCard", () => { line_description: "Line Item 2", psc_fee_amount: 0.5, status: "IN_EXECUTION", - updated_on: "2023-07-26T16:22:35.470618", + updated_on: "2023-07-26T16:22:35.470618" }, { agreement_id: 2, @@ -104,8 +104,8 @@ describe("AgreementValuesCard", () => { line_description: "Line Item 2", psc_fee_amount: 0.5, status: "IN_EXECUTION", - updated_on: "2023-07-26T16:22:35.470618", - }, + updated_on: "2023-07-26T16:22:35.470618" + } ]; render(); diff --git a/frontend/src/components/Agreements/AgreementEditor/AgreementEditForm.jsx b/frontend/src/components/Agreements/AgreementEditor/AgreementEditForm.jsx index 2731ee8934..869dcaa30f 100644 --- a/frontend/src/components/Agreements/AgreementEditor/AgreementEditForm.jsx +++ b/frontend/src/components/Agreements/AgreementEditor/AgreementEditForm.jsx @@ -20,7 +20,7 @@ import TextArea from "../../UI/Form/TextArea/TextArea"; import { useAddAgreementMutation, useGetProductServiceCodesQuery, - useUpdateAgreementMutation, + useUpdateAgreementMutation } from "../../../api/opsAPI"; import ProjectOfficerComboBox from "../../UI/Form/ProjectOfficerComboBox"; import { getUser } from "../../../api/getUser"; @@ -70,7 +70,7 @@ export const AgreementEditForm = ({ goBack, goToNext, isReviewMode, isEditMode, agreement, selected_procurement_shop: selectedProcurementShop, selected_product_service_code: selectedProductServiceCode, - selected_project_officer: selectedProjectOfficer, + selected_project_officer: selectedProjectOfficer } = useEditAgreement(); const { notes: agreementNotes, @@ -79,7 +79,7 @@ export const AgreementEditForm = ({ goBack, goToNext, isReviewMode, isEditMode, name: agreementTitle, description: agreementDescription, agreement_reason: agreementReason, - team_members: selectedTeamMembers, + team_members: selectedTeamMembers } = agreement; // This is needed due to a caching issue with the React Context - for some reason selected_project_officer @@ -103,12 +103,12 @@ export const AgreementEditForm = ({ goBack, goToNext, isReviewMode, isEditMode, const { data: productServiceCodes, error: errorProductServiceCodes, - isLoading: isLoadingProductServiceCodes, + isLoading: isLoadingProductServiceCodes } = useGetProductServiceCodesQuery(); if (isReviewMode) { suite({ - ...agreement, + ...agreement }); } @@ -127,7 +127,7 @@ export const AgreementEditForm = ({ goBack, goToNext, isReviewMode, isEditMode, const cn = classnames(suite.get(), { invalid: "usa-form-group--error", valid: "success", - warning: "warning", + warning: "warning" }); const changeSelectedProductServiceCode = (selectedProductServiceCode) => { @@ -145,14 +145,14 @@ export const AgreementEditForm = ({ goBack, goToNext, isReviewMode, isEditMode, const setSelectedTeamMembers = (teamMember) => { dispatch({ type: "ADD_TEAM_MEMBER", - payload: teamMember, + payload: teamMember }); }; const removeTeamMember = (teamMember) => { dispatch({ type: "REMOVE_TEAM_MEMBER", - payload: teamMember, + payload: teamMember }); }; @@ -170,7 +170,7 @@ export const AgreementEditForm = ({ goBack, goToNext, isReviewMode, isEditMode, ...agreement, team_members: selectedTeamMembers.map((team_member) => { return formatTeamMember(team_member); - }), + }) }; const { id, cleanData } = cleanAgreementForApi(data); if (id) { @@ -181,7 +181,7 @@ export const AgreementEditForm = ({ goBack, goToNext, isReviewMode, isEditMode, setAlert({ type: "success", heading: "Agreement Edited", - message: `The agreement ${agreement.name} has been successfully updated.`, + message: `The agreement ${agreement.name} has been successfully updated.` }); }) .catch((rejected) => { @@ -190,7 +190,7 @@ export const AgreementEditForm = ({ goBack, goToNext, isReviewMode, isEditMode, type: "error", heading: "Error", message: "An error occurred while saving the agreement.", - redirectUrl: "/error", + redirectUrl: "/error" }); }); } else { @@ -205,7 +205,7 @@ export const AgreementEditForm = ({ goBack, goToNext, isReviewMode, isEditMode, setAlert({ type: "success", heading: "Agreement Draft Saved", - message: `The agreement ${agreement.name} has been successfully created.`, + message: `The agreement ${agreement.name} has been successfully created.` }); }) .catch((rejected) => { @@ -214,7 +214,7 @@ export const AgreementEditForm = ({ goBack, goToNext, isReviewMode, isEditMode, type: "error", heading: "Error", message: "An error occurred while creating the agreement.", - redirectUrl: "/error", + redirectUrl: "/error" }); }); } @@ -244,7 +244,7 @@ export const AgreementEditForm = ({ goBack, goToNext, isReviewMode, isEditMode, if (isEditMode && setIsEditMode) setIsEditMode(false); navigate(`/agreements/${agreement.id}`); } - }, + } }); }; @@ -257,7 +257,7 @@ export const AgreementEditForm = ({ goBack, goToNext, isReviewMode, isEditMode, suite( { ...agreement, - ...{ [name]: value }, + ...{ [name]: value } }, name ); @@ -398,7 +398,10 @@ export const AgreementEditForm = ({ goBack, goToNext, isReviewMode, isEditMode,

Team Members Added

- +