diff --git a/app/server/app/middleware.js b/app/server/app/middleware.js index 7be03972..6c90b647 100644 --- a/app/server/app/middleware.js +++ b/app/server/app/middleware.js @@ -193,7 +193,7 @@ function checkClientRouteExists(req, res, next) { * @param {express.Response} res * @param {express.NextFunction} next */ -function storeBapComboKeys(req, res, next) { +function fetchBapComboKeys(req, res, next) { const { mail } = req.user; getBapComboKeys(req, mail) @@ -230,6 +230,6 @@ module.exports = { ensureHelpdesk, protectClientRoutes, checkClientRouteExists, - storeBapComboKeys, + fetchBapComboKeys, verifyMongoObjectId, }; diff --git a/app/server/app/routes/bap.js b/app/server/app/routes/bap.js index 9bca97b1..0f820b8f 100644 --- a/app/server/app/routes/bap.js +++ b/app/server/app/routes/bap.js @@ -1,11 +1,12 @@ const express = require("express"); // --- -const { ensureAuthenticated, storeBapComboKeys } = require("../middleware"); +const { ensureAuthenticated, fetchBapComboKeys } = require("../middleware"); const { // checkForBapDuplicates, getSamEntities, getBapFormSubmissionsStatuses, } = require("../utilities/bap"); +const { checkUserData } = require("../utilities/user"); const log = require("../utilities/logger"); const router = express.Router(); @@ -26,10 +27,9 @@ router.use(ensureAuthenticated); // --- get user's SAM.gov data from the BAP router.get("/sam", (req, res) => { - const { mail, memberof } = req.user; - const userRoles = memberof.split(","); - const adminOrHelpdeskUser = - userRoles.includes("csb_admin") || userRoles.includes("csb_helpdesk"); + const { mail } = req.user; + + const { adminOrHelpdeskUser } = checkUserData({ req }); if (!mail) { const logMessage = `User with no email address attempted to fetch SAM.gov records.`; @@ -64,22 +64,45 @@ router.get("/sam", (req, res) => { entities, }); }) - .catch((_error) => { - // NOTE: logged in bap verifyBapConnection + .catch((error) => { const errorStatus = 500; const errorMessage = `Error getting SAM.gov data from the BAP.`; + + log({ level: "error", message: errorMessage, req, otherInfo: error }); + return res.status(errorStatus).json({ message: errorMessage }); }); }); // --- get user's form submissions statuses from the BAP -router.get("/submissions", storeBapComboKeys, (req, res) => { +router.get("/submissions", fetchBapComboKeys, (req, res) => { + const { mail } = req.user; + + const { adminOrHelpdeskUser, noBapComboKeys } = checkUserData({ req }); + + if (noBapComboKeys) { + if (adminOrHelpdeskUser) { + return res.json([]); + } + + const logMessage = + `User with email '${mail}' attempted to fetch form submissions ` + + `from the BAP without any SAM.gov combo keys.`; + log({ level: "error", message: logMessage, req }); + + const errorStatus = 401; + const errorMessage = `Unauthorized.`; + return res.status(errorStatus).json({ message: errorMessage }); + } + return getBapFormSubmissionsStatuses(req) .then((submissions) => res.json(submissions)) - .catch((_error) => { - // NOTE: logged in bap verifyBapConnection + .catch((error) => { const errorStatus = 500; const errorMessage = `Error getting form submissions statuses from the BAP.`; + + log({ level: "error", message: errorMessage, req, otherInfo: error }); + return res.status(errorStatus).json({ message: errorMessage }); }); }); diff --git a/app/server/app/routes/formio2022.js b/app/server/app/routes/formio2022.js index f0459a9d..2316b65c 100644 --- a/app/server/app/routes/formio2022.js +++ b/app/server/app/routes/formio2022.js @@ -2,7 +2,7 @@ const express = require("express"); // --- const { ensureAuthenticated, - storeBapComboKeys, + fetchBapComboKeys, verifyMongoObjectId, } = require("../middleware"); const { @@ -34,7 +34,7 @@ router.use(ensureAuthenticated); // --- download Formio S3 file metadata router.get( "/s3/:formType/:mongoId/:comboKey/storage/s3", - storeBapComboKeys, + fetchBapComboKeys, (req, res) => { downloadS3FileMetadata({ rebateYear, req, res }); }, @@ -43,19 +43,19 @@ router.get( // --- upload Formio S3 file metadata router.post( "/s3/:formType/:mongoId/:comboKey/storage/s3", - storeBapComboKeys, + fetchBapComboKeys, (req, res) => { uploadS3FileMetadata({ rebateYear, req, res }); }, ); // --- get user's 2022 FRF submissions from Formio -router.get("/frf-submissions", storeBapComboKeys, (req, res) => { +router.get("/frf-submissions", fetchBapComboKeys, (req, res) => { fetchFRFSubmissions({ rebateYear, req, res }); }); // --- post a new 2022 FRF submission to Formio -router.post("/frf-submission", storeBapComboKeys, (req, res) => { +router.post("/frf-submission", fetchBapComboKeys, (req, res) => { createFRFSubmission({ rebateYear, req, res }); }); @@ -63,7 +63,7 @@ router.post("/frf-submission", storeBapComboKeys, (req, res) => { router.get( "/frf-submission/:mongoId", verifyMongoObjectId, - storeBapComboKeys, + fetchBapComboKeys, (req, res) => { fetchFRFSubmission({ rebateYear, req, res }); }, @@ -73,54 +73,54 @@ router.get( router.post( "/frf-submission/:mongoId", verifyMongoObjectId, - storeBapComboKeys, + fetchBapComboKeys, (req, res) => { updateFRFSubmission({ rebateYear, req, res }); }, ); // --- get user's 2022 PRF submissions from Formio -router.get("/prf-submissions", storeBapComboKeys, (req, res) => { +router.get("/prf-submissions", fetchBapComboKeys, (req, res) => { fetchPRFSubmissions({ rebateYear, req, res }); }); // --- post a new 2022 PRF submission to Formio -router.post("/prf-submission", storeBapComboKeys, (req, res) => { +router.post("/prf-submission", fetchBapComboKeys, (req, res) => { createPRFSubmission({ rebateYear, req, res }); }); // --- get an existing 2022 PRF's schema and submission data from Formio -router.get("/prf-submission/:rebateId", storeBapComboKeys, (req, res) => { +router.get("/prf-submission/:rebateId", fetchBapComboKeys, (req, res) => { fetchPRFSubmission({ rebateYear, req, res }); }); // --- post an update to an existing draft 2022 PRF submission to Formio -router.post("/prf-submission/:rebateId", storeBapComboKeys, (req, res) => { +router.post("/prf-submission/:rebateId", fetchBapComboKeys, (req, res) => { updatePRFSubmission({ rebateYear, req, res }); }); // --- delete an existing 2022 PRF submission from Formio -router.post("/delete-prf-submission", storeBapComboKeys, (req, res) => { +router.post("/delete-prf-submission", fetchBapComboKeys, (req, res) => { deletePRFSubmission({ rebateYear, req, res }); }); // --- get user's 2022 CRF submissions from Formio -router.get("/crf-submissions", storeBapComboKeys, (req, res) => { +router.get("/crf-submissions", fetchBapComboKeys, (req, res) => { fetchCRFSubmissions({ rebateYear, req, res }); }); // --- post a new 2022 CRF submission to Formio -router.post("/crf-submission", storeBapComboKeys, (req, res) => { +router.post("/crf-submission", fetchBapComboKeys, (req, res) => { createCRFSubmission({ rebateYear, req, res }); }); // --- get an existing 2022 CRF's schema and submission data from Formio -router.get("/crf-submission/:rebateId", storeBapComboKeys, (req, res) => { +router.get("/crf-submission/:rebateId", fetchBapComboKeys, (req, res) => { fetchCRFSubmission({ rebateYear, req, res }); }); // --- post an update to an existing draft 2022 CRF submission to Formio -router.post("/crf-submission/:rebateId", storeBapComboKeys, (req, res) => { +router.post("/crf-submission/:rebateId", fetchBapComboKeys, (req, res) => { updateCRFSubmission({ rebateYear, req, res }); }); diff --git a/app/server/app/routes/formio2023.js b/app/server/app/routes/formio2023.js index d5a226fb..dab98372 100644 --- a/app/server/app/routes/formio2023.js +++ b/app/server/app/routes/formio2023.js @@ -2,7 +2,7 @@ const express = require("express"); // --- const { ensureAuthenticated, - storeBapComboKeys, + fetchBapComboKeys, verifyMongoObjectId, } = require("../middleware"); const { @@ -46,7 +46,7 @@ router.get("/nces/:searchText?", (req, res) => { // --- download Formio S3 file metadata router.get( "/s3/:formType/:mongoId/:comboKey/storage/s3", - storeBapComboKeys, + fetchBapComboKeys, (req, res) => { downloadS3FileMetadata({ rebateYear, req, res }); }, @@ -55,19 +55,19 @@ router.get( // --- upload Formio S3 file metadata router.post( "/s3/:formType/:mongoId/:comboKey/storage/s3", - storeBapComboKeys, + fetchBapComboKeys, (req, res) => { uploadS3FileMetadata({ rebateYear, req, res }); }, ); // --- get user's 2023 FRF submissions from Formio -router.get("/frf-submissions", storeBapComboKeys, (req, res) => { +router.get("/frf-submissions", fetchBapComboKeys, (req, res) => { fetchFRFSubmissions({ rebateYear, req, res }); }); // --- post a new 2023 FRF submission to Formio -router.post("/frf-submission", storeBapComboKeys, (req, res) => { +router.post("/frf-submission", fetchBapComboKeys, (req, res) => { createFRFSubmission({ rebateYear, req, res }); }); @@ -75,7 +75,7 @@ router.post("/frf-submission", storeBapComboKeys, (req, res) => { router.get( "/frf-submission/:mongoId", verifyMongoObjectId, - storeBapComboKeys, + fetchBapComboKeys, (req, res) => { fetchFRFSubmission({ rebateYear, req, res }); }, @@ -85,74 +85,74 @@ router.get( router.post( "/frf-submission/:mongoId", verifyMongoObjectId, - storeBapComboKeys, + fetchBapComboKeys, (req, res) => { updateFRFSubmission({ rebateYear, req, res }); }, ); // --- get user's 2023 PRF submissions from Formio -router.get("/prf-submissions", storeBapComboKeys, (req, res) => { +router.get("/prf-submissions", fetchBapComboKeys, (req, res) => { fetchPRFSubmissions({ rebateYear, req, res }); }); // --- post a new 2023 PRF submission to Formio -router.post("/prf-submission", storeBapComboKeys, (req, res) => { +router.post("/prf-submission", fetchBapComboKeys, (req, res) => { createPRFSubmission({ rebateYear, req, res }); }); // --- get an existing 2023 PRF's schema and submission data from Formio -router.get("/prf-submission/:rebateId", storeBapComboKeys, (req, res) => { +router.get("/prf-submission/:rebateId", fetchBapComboKeys, (req, res) => { fetchPRFSubmission({ rebateYear, req, res }); }); // --- post an update to an existing draft 2023 PRF submission to Formio -router.post("/prf-submission/:rebateId", storeBapComboKeys, (req, res) => { +router.post("/prf-submission/:rebateId", fetchBapComboKeys, (req, res) => { updatePRFSubmission({ rebateYear, req, res }); }); // --- delete an existing 2023 PRF submission from Formio -router.post("/delete-prf-submission", storeBapComboKeys, (req, res) => { +router.post("/delete-prf-submission", fetchBapComboKeys, (req, res) => { deletePRFSubmission({ rebateYear, req, res }); }); // --- get user's 2023 CRF submissions from Formio -router.get("/crf-submissions", storeBapComboKeys, (req, res) => { +router.get("/crf-submissions", fetchBapComboKeys, (req, res) => { res.json([]); // TODO: replace with `fetchCRFSubmissions({ rebateYear, req, res })` when CRF is ready }); // --- post a new 2023 CRF submission to Formio -// router.post("/crf-submission", storeBapComboKeys, (req, res) => { +// router.post("/crf-submission", fetchBapComboKeys, (req, res) => { // createCRFSubmission({ rebateYear, req, res }); // }); // --- get an existing 2023 CRF's schema and submission data from Formio -// router.get("/crf-submission/:rebateId", storeBapComboKeys, (req, res) => { +// router.get("/crf-submission/:rebateId", fetchBapComboKeys, (req, res) => { // fetchCRFSubmission({ rebateYear, req, res }); // }); // --- post an update to an existing draft 2023 CRF submission to Formio -// router.post("/crf-submission/:rebateId", storeBapComboKeys, (req, res) => { +// router.post("/crf-submission/:rebateId", fetchBapComboKeys, (req, res) => { // updateCRFSubmission({ rebateYear, req, res }); // }); // --- get user's 2023 Change Request form submissions from Formio -router.get("/changes", storeBapComboKeys, (req, res) => { +router.get("/changes", fetchBapComboKeys, (req, res) => { fetchChangeRequests({ rebateYear, req, res }); }); // --- get the 2023 Change Request form's schema from Formio -router.get("/change", storeBapComboKeys, (req, res) => { +router.get("/change", fetchBapComboKeys, (req, res) => { fetchChangeRequestSchema({ rebateYear, req, res }); }); // --- post a new 2023 Change Request form submission to Formio -router.post("/change", storeBapComboKeys, (req, res) => { +router.post("/change", fetchBapComboKeys, (req, res) => { createChangeRequest({ rebateYear, req, res }); }); // --- get an existing 2023 Change Request form's schema and submission data from Formio -router.get("/change/:mongoId", storeBapComboKeys, (req, res) => { +router.get("/change/:mongoId", fetchBapComboKeys, (req, res) => { fetchChangeRequest({ rebateYear, req, res }); }); diff --git a/app/server/app/routes/formio2024.js b/app/server/app/routes/formio2024.js index e4ab6cf8..88ef5b22 100644 --- a/app/server/app/routes/formio2024.js +++ b/app/server/app/routes/formio2024.js @@ -2,7 +2,7 @@ const express = require("express"); // --- const { ensureAuthenticated, - storeBapComboKeys, + fetchBapComboKeys, verifyMongoObjectId, } = require("../middleware"); const { @@ -46,7 +46,7 @@ router.get("/nces/:searchText?", (req, res) => { // --- download Formio S3 file metadata router.get( "/s3/:formType/:mongoId/:comboKey/storage/s3", - storeBapComboKeys, + fetchBapComboKeys, (req, res) => { downloadS3FileMetadata({ rebateYear, req, res }); }, @@ -55,19 +55,19 @@ router.get( // --- upload Formio S3 file metadata router.post( "/s3/:formType/:mongoId/:comboKey/storage/s3", - storeBapComboKeys, + fetchBapComboKeys, (req, res) => { uploadS3FileMetadata({ rebateYear, req, res }); }, ); // --- get user's 2024 FRF submissions from Formio -router.get("/frf-submissions", storeBapComboKeys, (req, res) => { +router.get("/frf-submissions", fetchBapComboKeys, (req, res) => { fetchFRFSubmissions({ rebateYear, req, res }); }); // --- post a new 2024 FRF submission to Formio -router.post("/frf-submission", storeBapComboKeys, (req, res) => { +router.post("/frf-submission", fetchBapComboKeys, (req, res) => { createFRFSubmission({ rebateYear, req, res }); }); @@ -75,7 +75,7 @@ router.post("/frf-submission", storeBapComboKeys, (req, res) => { router.get( "/frf-submission/:mongoId", verifyMongoObjectId, - storeBapComboKeys, + fetchBapComboKeys, (req, res) => { fetchFRFSubmission({ rebateYear, req, res }); }, @@ -85,74 +85,74 @@ router.get( router.post( "/frf-submission/:mongoId", verifyMongoObjectId, - storeBapComboKeys, + fetchBapComboKeys, (req, res) => { updateFRFSubmission({ rebateYear, req, res }); }, ); // --- get user's 2024 PRF submissions from Formio -router.get("/prf-submissions", storeBapComboKeys, (req, res) => { +router.get("/prf-submissions", fetchBapComboKeys, (req, res) => { fetchPRFSubmissions({ rebateYear, req, res }); }); // --- post a new 2024 PRF submission to Formio -router.post("/prf-submission", storeBapComboKeys, (req, res) => { +router.post("/prf-submission", fetchBapComboKeys, (req, res) => { createPRFSubmission({ rebateYear, req, res }); }); // --- get an existing 2024 PRF's schema and submission data from Formio -router.get("/prf-submission/:rebateId", storeBapComboKeys, (req, res) => { +router.get("/prf-submission/:rebateId", fetchBapComboKeys, (req, res) => { fetchPRFSubmission({ rebateYear, req, res }); }); // --- post an update to an existing draft 2024 PRF submission to Formio -router.post("/prf-submission/:rebateId", storeBapComboKeys, (req, res) => { +router.post("/prf-submission/:rebateId", fetchBapComboKeys, (req, res) => { updatePRFSubmission({ rebateYear, req, res }); }); // --- delete an existing 2024 PRF submission from Formio -router.post("/delete-prf-submission", storeBapComboKeys, (req, res) => { +router.post("/delete-prf-submission", fetchBapComboKeys, (req, res) => { deletePRFSubmission({ rebateYear, req, res }); }); // --- get user's 2024 CRF submissions from Formio -router.get("/crf-submissions", storeBapComboKeys, (req, res) => { +router.get("/crf-submissions", fetchBapComboKeys, (req, res) => { res.json([]); // TODO: replace with `fetchCRFSubmissions({ rebateYear, req, res })` when CRF is ready }); // --- post a new 2024 CRF submission to Formio -// router.post("/crf-submission", storeBapComboKeys, (req, res) => { +// router.post("/crf-submission", fetchBapComboKeys, (req, res) => { // createCRFSubmission({ rebateYear, req, res }); // }); // --- get an existing 2024 CRF's schema and submission data from Formio -// router.get("/crf-submission/:rebateId", storeBapComboKeys, (req, res) => { +// router.get("/crf-submission/:rebateId", fetchBapComboKeys, (req, res) => { // fetchCRFSubmission({ rebateYear, req, res }); // }); // --- post an update to an existing draft 2024 CRF submission to Formio -// router.post("/crf-submission/:rebateId", storeBapComboKeys, (req, res) => { +// router.post("/crf-submission/:rebateId", fetchBapComboKeys, (req, res) => { // updateCRFSubmission({ rebateYear, req, res }); // }); // --- get user's 2024 Change Request form submissions from Formio -router.get("/changes", storeBapComboKeys, (req, res) => { +router.get("/changes", fetchBapComboKeys, (req, res) => { fetchChangeRequests({ rebateYear, req, res }); }); // --- get the 2024 Change Request form's schema from Formio -router.get("/change", storeBapComboKeys, (req, res) => { +router.get("/change", fetchBapComboKeys, (req, res) => { fetchChangeRequestSchema({ rebateYear, req, res }); }); // --- post a new 2024 Change Request form submission to Formio -router.post("/change", storeBapComboKeys, (req, res) => { +router.post("/change", fetchBapComboKeys, (req, res) => { createChangeRequest({ rebateYear, req, res }); }); // --- get an existing 2024 Change Request form's schema and submission data from Formio -router.get("/change/:mongoId", storeBapComboKeys, (req, res) => { +router.get("/change/:mongoId", fetchBapComboKeys, (req, res) => { fetchChangeRequest({ rebateYear, req, res }); }); diff --git a/app/server/app/utilities/formio.js b/app/server/app/utilities/formio.js index 4363d483..55d61930 100644 --- a/app/server/app/utilities/formio.js +++ b/app/server/app/utilities/formio.js @@ -19,7 +19,8 @@ const { getBapDataFor2022CRF, checkFormSubmissionPeriodAndBapStatus, } = require("../utilities/bap"); -const log = require("./logger"); +const { checkUserData } = require("../utilities/user"); +const log = require("../utilities/logger"); const { NODE_ENV } = process.env; @@ -1090,6 +1091,24 @@ function downloadS3FileMetadata({ rebateYear, req, res }) { */ function fetchFRFSubmissions({ rebateYear, req, res }) { const { bapComboKeys } = req; + const { mail } = req.user; + + const { adminOrHelpdeskUser, noBapComboKeys } = checkUserData({ req }); + + if (noBapComboKeys) { + if (adminOrHelpdeskUser) { + return res.json([]); + } + + const logMessage = + `User with email '${mail}' attempted to fetch ${rebateYear} FRF ` + + `submissions from Formio without any SAM.gov combo keys.`; + log({ level: "error", message: logMessage, req }); + + const errorStatus = 401; + const errorMessage = `Unauthorized.`; + return res.status(errorStatus).json({ message: errorMessage }); + } const comboKeyFieldName = getComboKeyFieldName({ rebateYear }); const comboKeySearchParam = `&data.${comboKeyFieldName}=`; @@ -1331,6 +1350,24 @@ function updateFRFSubmission({ rebateYear, req, res }) { */ function fetchPRFSubmissions({ rebateYear, req, res }) { const { bapComboKeys } = req; + const { mail } = req.user; + + const { adminOrHelpdeskUser, noBapComboKeys } = checkUserData({ req }); + + if (noBapComboKeys) { + if (adminOrHelpdeskUser) { + return res.json([]); + } + + const logMessage = + `User with email '${mail}' attempted to fetch ${rebateYear} PRF ` + + `submissions from Formio without any SAM.gov combo keys.`; + log({ level: "error", message: logMessage, req }); + + const errorStatus = 401; + const errorMessage = `Unauthorized.`; + return res.status(errorStatus).json({ message: errorMessage }); + } const comboKeyFieldName = getComboKeyFieldName({ rebateYear }); const comboKeySearchParam = `&data.${comboKeyFieldName}=`; @@ -1692,6 +1729,24 @@ function deletePRFSubmission({ rebateYear, req, res }) { */ function fetchCRFSubmissions({ rebateYear, req, res }) { const { bapComboKeys } = req; + const { mail } = req.user; + + const { adminOrHelpdeskUser, noBapComboKeys } = checkUserData({ req }); + + if (noBapComboKeys) { + if (adminOrHelpdeskUser) { + return res.json([]); + } + + const logMessage = + `User with email '${mail}' attempted to fetch ${rebateYear} CRF ` + + `submissions from Formio without any SAM.gov combo keys.`; + log({ level: "error", message: logMessage, req }); + + const errorStatus = 401; + const errorMessage = `Unauthorized.`; + return res.status(errorStatus).json({ message: errorMessage }); + } const comboKeyFieldName = getComboKeyFieldName({ rebateYear }); const comboKeySearchParam = `&data.${comboKeyFieldName}=`; @@ -1964,6 +2019,24 @@ function updateCRFSubmission({ rebateYear, req, res }) { */ function fetchChangeRequests({ rebateYear, req, res }) { const { bapComboKeys } = req; + const { mail } = req.user; + + const { adminOrHelpdeskUser, noBapComboKeys } = checkUserData({ req }); + + if (noBapComboKeys) { + if (adminOrHelpdeskUser) { + return res.json([]); + } + + const logMessage = + `User with email '${mail}' attempted to fetch ${rebateYear} Change ` + + `Request form submissions from Formio without any SAM.gov combo keys.`; + log({ level: "error", message: logMessage, req }); + + const errorStatus = 401; + const errorMessage = `Unauthorized.`; + return res.status(errorStatus).json({ message: errorMessage }); + } const comboKeyFieldName = getComboKeyFieldName({ rebateYear }); const comboKeySearchParam = `&data.${comboKeyFieldName}=`; diff --git a/app/server/app/utilities/user.js b/app/server/app/utilities/user.js new file mode 100644 index 00000000..0b7b0ece --- /dev/null +++ b/app/server/app/utilities/user.js @@ -0,0 +1,37 @@ +const express = require("express"); + +/** + * @typedef {Object} User + * @property {string} mail + * @property {string} memberof + * @property {string} nameID + * @property {string} nameIDFormat + * @property {string} spNameQualifier + * @property {string} sessionIndex + * @property {number} iat + * @property {number} exp + */ + +/** + * Determines if the user is an admin or helpdesk user and if they have any BAP + * combo keys. + * + * @param {Object} param + * @param {express.Request} param.req + */ +function checkUserData({ req }) { + /** @type {{ bapComboKeys: string[]; user: User }} */ + const { bapComboKeys, user } = req; + + const userRoles = user.memberof.split(","); + const adminOrHelpdeskUser = + userRoles.includes("csb_admin") || userRoles.includes("csb_helpdesk"); + + const noBapComboKeys = bapComboKeys?.length === 0; + + return { adminOrHelpdeskUser, noBapComboKeys }; +} + +module.exports = { + checkUserData, +};