Skip to content

Commit

Permalink
Minor changes and securing apop
Browse files Browse the repository at this point in the history
  • Loading branch information
datajohnson committed Dec 16, 2024
1 parent 4316fac commit 912f1e5
Show file tree
Hide file tree
Showing 10 changed files with 75 additions and 52 deletions.
5 changes: 3 additions & 2 deletions src/api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { API_PORT, AUTH0_DOMAIN, FRONTEND_URL } from "./config";
import { doHealthCheck } from "./utils/healthCheck";
import { adminParticipantRouter, adminSurveyRouter, integrationRouter, surveyRouter, userRouter } from "./routes";
import { checkJwt, loadUser } from "./middleware/authz.middleware";
import { requiresAdmin } from "./middleware";

const app = express();
app.use(express.json()); // for parsing application/json
Expand Down Expand Up @@ -46,8 +47,8 @@ app.use("/api/user", checkJwt, loadUser, userRouter);
app.use("/api/survey", surveyRouter);
app.use("/api/integration", integrationRouter);

app.use("/api/admin/survey", adminSurveyRouter);
app.use("/api/admin/participant", adminParticipantRouter);
app.use("/api/admin/survey", checkJwt, loadUser, requiresAdmin, adminSurveyRouter);
app.use("/api/admin/participant", checkJwt, loadUser, requiresAdmin, adminParticipantRouter);

// serves the static files generated by the front-end
app.use(express.static(path.join(__dirname, "web")));
Expand Down
17 changes: 12 additions & 5 deletions src/api/src/middleware/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,18 @@ import { NextFunction, Request, Response } from "express";
import { validationResult } from "express-validator";

export async function ReturnValidationErrors(req: Request, res: Response, next: NextFunction) {
const errors = validationResult(req);
const errors = validationResult(req);

if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}

next();
next();
}

export async function requiresAdmin(req: Request, res: Response, next: NextFunction) {
if (req.user && req.user.IS_ADMIN == "Y") return next();

res.status(403).send("Unauthorized - You must be an Admin to access this resource.");
return false;
}
2 changes: 0 additions & 2 deletions src/api/src/routes/admin-survey-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@ import { ReturnValidationErrors } from "../middleware";
import { param } from "express-validator";
import { EmailService, UserService } from "../services";
import { recordSentDate } from "./integration-router";
import { checkJwt, loadUser } from "../middleware/authz.middleware";
import { DB_SCHEMA } from "../config";
import { sortBy, uniq } from "lodash";

export const adminSurveyRouter = express.Router();
adminSurveyRouter.use(checkJwt, loadUser);

const userService = new UserService();

Expand Down
33 changes: 15 additions & 18 deletions src/api/src/routes/survey-router.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import express, { Request, Response } from "express";
import { ReturnValidationErrors } from "../middleware";
import { requiresAdmin, ReturnValidationErrors } from "../middleware";
import { param } from "express-validator";
import * as knex from "knex";
import { DB_CONFIG, DB_SCHEMA } from "../config";
import { makeToken } from "./admin-participant-router";
import { checkJwt, loadUser } from "../middleware/authz.middleware";

export const surveyRouter = express.Router();

Expand Down Expand Up @@ -53,12 +54,13 @@ surveyRouter.get(

surveyRouter.get(
"/:token/agent",
checkJwt,
loadUser,
[param("token").notEmpty()],
ReturnValidationErrors,
async (req: Request, res: Response) => {
const db = knex.knex(DB_CONFIG);
let { token } = req.params;

token = token.substring(0, 64);

let participant = await db("PARTICIPANT")
Expand Down Expand Up @@ -103,12 +105,13 @@ surveyRouter.get(

surveyRouter.get(
"/:token/manual",
checkJwt,
loadUser,
[param("token").notEmpty()],
ReturnValidationErrors,
async (req: Request, res: Response) => {
const db = knex.knex(DB_CONFIG);
let { token } = req.params;

token = token.substring(0, 64);

let participant = await db("PARTICIPANT")
Expand Down Expand Up @@ -149,6 +152,9 @@ surveyRouter.get(

surveyRouter.get(
"/:token/manual-entry",
checkJwt,
loadUser,
requiresAdmin,
[param("token").notEmpty()],
ReturnValidationErrors,
async (req: Request, res: Response) => {
Expand Down Expand Up @@ -213,6 +219,8 @@ surveyRouter.get(

surveyRouter.post(
"/:agentEmail/:token",
checkJwt,
loadUser,
[param("token").notEmpty()],
ReturnValidationErrors,
async (req: Request, res: Response) => {
Expand Down Expand Up @@ -280,29 +288,18 @@ surveyRouter.post(

surveyRouter.post(
"/:agentEmail/manual/:surveyId",
checkJwt,
loadUser,
requiresAdmin,
[param("surveyId").notEmpty()],
ReturnValidationErrors,
async (req: Request, res: Response) => {
const db = knex.knex(DB_CONFIG);
let { surveyId, agentEmail } = req.params;
let { questions, participant, demographics } = req.body;
let { questions, demographics } = req.body;

const token = makeToken("ME");

let existingAddresses = await db("PARTICIPANT_DATA")
.withSchema(DB_SCHEMA)
.innerJoin("PARTICIPANT", "PARTICIPANT_DATA.TOKEN", "PARTICIPANT.TOKEN")
.where("PARTICIPANT.SID", surveyId)
.whereNotNull("EMAIL")
.select("EMAIL");

let existingList = existingAddresses.map((e) => e.EMAIL);
if (existingList.includes(participant)) {
return res
.status(400)
.json({ messages: [{ variant: "error", text: "This email address has already been used for this survey." }] });
}

await db("PARTICIPANT").withSchema(DB_SCHEMA).insert({ TOKEN: token, SID: surveyId, CREATE_DATE: new Date() });
await db("PARTICIPANT_DATA").withSchema(DB_SCHEMA).insert({ TOKEN: token, EMAIL: null, RESPONSE_DATE: new Date() });

Expand Down
7 changes: 4 additions & 3 deletions src/api/src/routes/user-router.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import express, { Request, Response } from "express";
import { DirectoryService, UserService } from "../services";
import { UserStatus } from "../data/models";
import { ReturnValidationErrors } from "../middleware";
import { requiresAdmin, ReturnValidationErrors } from "../middleware";
import { param } from "express-validator";

export const userRouter = express.Router();
Expand All @@ -11,7 +11,7 @@ userRouter.get("/me", async (req: Request, res: Response) => {
return res.json({ data: { ...req.user, surveys: await db.getSurveysByEmail(req.user.EMAIL) } });
});

userRouter.get("/", async (req: Request, res: Response) => {
userRouter.get("/", requiresAdmin, async (req: Request, res: Response) => {
let list = await db.getAll();

for (let l of list) {
Expand All @@ -23,7 +23,7 @@ userRouter.get("/", async (req: Request, res: Response) => {
return res.json({ data: list });
});

userRouter.post("/", async (req: Request, res: Response) => {
userRouter.post("/", requiresAdmin, async (req: Request, res: Response) => {
let { user } = req.body;

if (user) {
Expand All @@ -49,6 +49,7 @@ userRouter.post("/", async (req: Request, res: Response) => {

userRouter.put(
"/:email",
requiresAdmin,
[param("email").notEmpty().isString()],
ReturnValidationErrors,
async (req: Request, res: Response) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -349,8 +349,18 @@ export default {
this.visible = false;
},
openManualEntry() {
console.log(this.batch.survey);
window.open(`/manual-entry/${this.batch.survey}`);
const token = this.makeToken(this.batch.survey);
window.open(`/manual-entry/${token}`);
},
makeToken(prefix: string) {
const chars = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz";
const randomArray = Array.from({ length: 64 }, (v, k) => chars[Math.floor(Math.random() * chars.length)]);
let location = Math.floor(Math.random() * 25);
let randomString = `${randomArray.join("")}`;
randomString = randomString.replace(/^_/, "");
return `${randomString.substring(0, location + 5)}${prefix}${randomString.substring(location + 6, 48)}`;
},
},
};
Expand Down
12 changes: 7 additions & 5 deletions src/web/src/store/SurveyStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const useSurveyStore = defineStore("survey", {
let api = useApiStore();

await api
.call("get", `${SURVEY_URL}/${id}/agent`)
.secureCall("get", `${SURVEY_URL}/${id}/agent`)
.then((resp) => {
this.setSurvey(resp.data);
})
Expand All @@ -44,7 +44,7 @@ export const useSurveyStore = defineStore("survey", {
let api = useApiStore();

await api
.call("get", `${SURVEY_URL}/${id}/manual`)
.secureCall("get", `${SURVEY_URL}/${id}/manual`)
.then((resp) => {
this.setSurvey(resp.data);
})
Expand All @@ -53,12 +53,14 @@ export const useSurveyStore = defineStore("survey", {
});
},

async loadFullManualSurvey(id: any) {
async loadFullManualSurvey(token: string) {
this.isLoading = true;
let api = useApiStore();

const id = token.replace(/[a-zA-Z]/g, "");

await api
.call("get", `${SURVEY_URL}/${id}/manual-entry`)
.secureCall("get", `${SURVEY_URL}/${id}/manual-entry`)
.then((resp) => {
this.setSurvey(resp.data);
})
Expand All @@ -72,7 +74,7 @@ export const useSurveyStore = defineStore("survey", {
let api = useApiStore();

await api
.call("get", `${SURVEY_URL}/${id}/preview`)
.secureCall("get", `${SURVEY_URL}/${id}/preview`)
.then((resp) => {
this.setSurvey(resp.data);
})
Expand Down
19 changes: 8 additions & 11 deletions src/web/src/views/AuthenticatedFullManualSurvey.vue
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
<template>
<div class="hello" v-if="survey && survey.survey">
<!-- SurveyHeader v-model="moveOn" /> -->
<h1>Survey Response Manual Entry</h1>
<v-card class="default">
<v-card-title>Participant Information</v-card-title>
<h1>{{ survey.survey.NAME }} : <small>Survey Response Manual Entry</small></h1>
<v-card v-if="survey.demographics && survey.demographics.length > 0" class="default">
<v-card-text>
<v-text-field label="Participant email" v-model="participantEmail" />

<div v-if="survey.demographics && survey.demographics.length > 0">
<div>
<h4 class="mb-3">Demographics</h4>

<div v-for="demographic of survey.demographics">
Expand Down Expand Up @@ -40,19 +37,19 @@ import { mapActions, mapState } from "pinia";
import { AuthHelper } from "@/plugins/auth";
import { useSurveyStore } from "@/store/SurveyStore";
import { SURVEY_URL } from "@/urls";
import { useApiStore } from "@/store/ApiStore";
export default {
name: "Login",
data: () => ({
surveyId: "",
allValid: false,
participantEmail: "",
}),
computed: {
...mapState(useSurveyStore, ["survey"]),
allValidAndEmail() {
return this.allValid && this.participantEmail;
return this.allValid;
},
},
mounted() {
Expand Down Expand Up @@ -83,10 +80,10 @@ export default {
let agentEmail = AuthHelper.user.value?.email;
axios
.post(`${SURVEY_URL}/${agentEmail}/manual/${this.surveyId}`, {
const api = useApiStore();
api
.secureCall("POST", `${SURVEY_URL}/${agentEmail}/manual/${this.surveyId}`, {
questions: qs,
participant: this.participantEmail,
demographics: this.survey.demographics,
})
.then(() => {
Expand Down
9 changes: 7 additions & 2 deletions src/web/src/views/AuthenticatedManualSurvey.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { mapActions, mapState } from "pinia";
import { AuthHelper } from "@/plugins/auth";
import { useSurveyStore } from "@/store/SurveyStore";
import { SURVEY_URL } from "@/urls";
import { useApiStore } from "@/store/ApiStore";
export default {
name: "Login",
Expand Down Expand Up @@ -65,8 +66,12 @@ export default {
let agentEmail = AuthHelper.user.value?.email;
axios
.post(`${SURVEY_URL}/${agentEmail}/${this.surveyId}`, { questions: qs, contact: this.contactMe })
const api = useApiStore();
api
.secureCall("POST", `${SURVEY_URL}/${agentEmail}/${this.surveyId}`, {
questions: qs,
contact: this.contactMe,
})
.then(() => {
this.$router.push("/survey/complete");
})
Expand Down
9 changes: 7 additions & 2 deletions src/web/src/views/AuthenticatedSurvey.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { mapActions, mapState } from "pinia";
import { useSurveyStore } from "@/store/SurveyStore";
import { SURVEY_URL } from "@/urls";
import { useApiStore } from "@/store/ApiStore";
export default {
name: "Login",
Expand Down Expand Up @@ -64,8 +65,12 @@ export default {
let agentEmail = AuthHelper.user.value?.email;
axios
.post(`${SURVEY_URL}/${agentEmail}/${this.surveyId}`, { questions: qs, contact: this.contactMe })
const api = useApiStore();
api
.secureCall("POST", `${SURVEY_URL}/${agentEmail}/${this.surveyId}`, {
questions: qs,
contact: this.contactMe,
})
.then(() => {
this.$router.push("/survey/complete");
})
Expand Down

0 comments on commit 912f1e5

Please sign in to comment.