Skip to content

Commit 518aca3

Browse files
committed
validation of date only fields in backend to prevent misuse
1 parent aa95e31 commit 518aca3

8 files changed

Lines changed: 56 additions & 20 deletions

src/backend/src/routes/calendar.routes.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { body, param } from 'express-validator';
33
import {
44
intMinZero,
55
isDate,
6+
isDateOnly,
67
nonEmptyString,
78
validateInputs,
89
isEventStatus,
@@ -185,7 +186,7 @@ calendarRouter.post(
185186
body('availability').isArray(),
186187
body('availability.*.availability').isArray(),
187188
intMinZero(body('availability.*.availability.*')),
188-
isDate(body('availability.*.dateSet')),
189+
isDateOnly(body('availability.*.dateSet')),
189190
validateInputs,
190191
CalendarController.markUserConfirmed
191192
);

src/backend/src/routes/change-requests.routes.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,14 @@ changeRequestsRouter.post(
3838
intMinZero(body('wbsNum.projectNumber')),
3939
intMinZero(body('wbsNum.workPackageNumber')),
4040
body('type').custom((value) => value === ChangeRequestType.Activation),
41-
body('startDate').custom((value) => !isNaN(Date.parse(value))),
41+
body('startDate').custom((value) => {
42+
const parsed = Date.parse(value);
43+
if (isNaN(parsed)) return false;
44+
const date = new Date(parsed);
45+
return (
46+
date.getUTCHours() === 0 && date.getUTCMinutes() === 0 && date.getUTCSeconds() === 0 && date.getUTCMilliseconds() === 0
47+
);
48+
}),
4249
nonEmptyString(body('leadId')),
4350
nonEmptyString(body('managerId')),
4451
body('confirmDetails').isBoolean(),

src/backend/src/routes/recruitment.routes.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import express from 'express';
2-
import { isDate, nonEmptyString, validateInputs } from '../utils/validation.utils.js';
2+
import { isDateOnly, nonEmptyString, validateInputs } from '../utils/validation.utils.js';
33
import { body } from 'express-validator';
44
import RecruitmentController from '../controllers/recruitment.controllers.js';
55

@@ -12,7 +12,7 @@ recruitmentRouter.post(
1212
'/milestone/create',
1313
nonEmptyString(body('name')),
1414
nonEmptyString(body('description')),
15-
isDate(body('dateOfEvent')),
15+
isDateOnly(body('dateOfEvent')),
1616
validateInputs,
1717
RecruitmentController.createMilestone
1818
);
@@ -21,7 +21,7 @@ recruitmentRouter.post(
2121
'/milestone/:milestoneId/edit',
2222
nonEmptyString(body('name')),
2323
nonEmptyString(body('description')),
24-
isDate(body('dateOfEvent')),
24+
isDateOnly(body('dateOfEvent')),
2525
validateInputs,
2626
RecruitmentController.editMilestone
2727
);

src/backend/src/routes/reimbursement-requests.routes.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { body } from 'express-validator';
88
import {
99
intMinZero,
1010
isDate,
11-
isOptionalDate,
11+
isOptionalDateOnly,
1212
nonEmptyString,
1313
validateInputs,
1414
validateReimbursementProducts
@@ -115,7 +115,7 @@ reimbursementRequestsRouter.post('/:vendorId/vendors/delete', ReimbursementReque
115115

116116
reimbursementRequestsRouter.post(
117117
'/create',
118-
isOptionalDate(body('dateOfExpense')),
118+
isOptionalDateOnly(body('dateOfExpense')),
119119
nonEmptyString(body('vendorId')),
120120
nonEmptyString(body('indexCodeId')),
121121
nonEmptyString(body('accountCodeId')),
@@ -131,7 +131,7 @@ reimbursementRequestsRouter.get('/:requestId', ReimbursementRequestController.ge
131131

132132
reimbursementRequestsRouter.post(
133133
'/:requestId/edit',
134-
isOptionalDate(body('dateOfExpense')),
134+
isOptionalDateOnly(body('dateOfExpense')),
135135
nonEmptyString(body('vendorId')),
136136
nonEmptyString(body('indexCodeId')),
137137
body('receiptPictures').isArray(),

src/backend/src/routes/tasks.routes.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import express from 'express';
22
import { body } from 'express-validator';
33
import TasksController from '../controllers/tasks.controllers.js';
4-
import { nonEmptyString, isTaskPriority, isTaskStatus, validateInputs, isOptionalDate } from '../utils/validation.utils.js';
4+
import {
5+
nonEmptyString,
6+
isTaskPriority,
7+
isTaskStatus,
8+
validateInputs,
9+
isOptionalDateOnly
10+
} from '../utils/validation.utils.js';
511
import { isDate } from '../utils/validation.utils.js';
612

713
const tasksRouter = express.Router();
@@ -21,8 +27,8 @@ tasksRouter.post(
2127
tasksRouter.post(
2228
'/:wbsNum',
2329
nonEmptyString(body('title')),
24-
isOptionalDate(body('deadline')),
25-
isOptionalDate(body('startDate')),
30+
isOptionalDateOnly(body('deadline')),
31+
isOptionalDateOnly(body('startDate')),
2632
body('notes').isString(),
2733
isTaskPriority(body('priority')),
2834
isTaskStatus(body('status')),
@@ -36,8 +42,8 @@ tasksRouter.post(
3642
'/:taskId/edit',
3743
nonEmptyString(body('title')),
3844
nonEmptyString(body('notes')),
39-
isOptionalDate(body('deadline')),
40-
isOptionalDate(body('startDate')),
45+
isOptionalDateOnly(body('deadline')),
46+
isOptionalDateOnly(body('startDate')),
4147
isTaskPriority(body('priority')),
4248
TasksController.editTask
4349
);

src/backend/src/routes/users.routes.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Theme } from '@prisma/client';
22
import express from 'express';
33
import { body } from 'express-validator';
44
import UsersController from '../controllers/users.controllers.js';
5-
import { isRole, nonEmptyString, intMinZero, validateInputs, isDate } from '../utils/validation.utils.js';
5+
import { isRole, nonEmptyString, intMinZero, validateInputs, isDateOnly } from '../utils/validation.utils.js';
66

77
const userRouter = express.Router();
88

@@ -50,7 +50,7 @@ userRouter.post(
5050
body('availability').isArray(),
5151
body('availability.*.availability').isArray(),
5252
intMinZero(body('availability.*.availability.*')),
53-
isDate(body('availability.*.dateSet')),
53+
isDateOnly(body('availability.*.dateSet')),
5454
validateInputs,
5555
UsersController.setUserScheduleSettings
5656
);

src/backend/src/routes/work-packages.routes.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
blockedByValidators,
66
descriptionBulletsValidators,
77
intMinZero,
8-
isDate,
8+
isDateOnly,
99
isWorkPackageStageOrNone,
1010
nonEmptyString,
1111
validateInputs
@@ -35,7 +35,7 @@ workPackagesRouter.post(
3535
nonEmptyString(body('crId').optional()),
3636
nonEmptyString(body('name')),
3737
isWorkPackageStageOrNone(body('stage')),
38-
isDate(body('startDate')),
38+
isDateOnly(body('startDate')),
3939
intMinZero(body('duration')),
4040
intMinZero(body('projectWbsNum.carNumber')),
4141
intMinZero(body('projectWbsNum.projectNumber')),
@@ -51,7 +51,7 @@ workPackagesRouter.post(
5151
nonEmptyString(body('workPackageId')),
5252
nonEmptyString(body('crId')),
5353
nonEmptyString(body('name')),
54-
isDate(body('startDate')),
54+
isDateOnly(body('startDate')),
5555
intMinZero(body('duration')),
5656
isWorkPackageStageOrNone(body('stage')),
5757
...blockedByValidators,
@@ -65,7 +65,7 @@ workPackagesRouter.delete('/:wbsNum/delete', WorkPackagesController.deleteWorkPa
6565
workPackagesRouter.get('/:wbsNum/blocking', WorkPackagesController.getBlockingWorkPackages);
6666
workPackagesRouter.post(
6767
'/slack-upcoming-deadlines',
68-
isDate(body('deadline')),
68+
isDateOnly(body('deadline')),
6969
validateInputs,
7070
WorkPackagesController.slackMessageUpcomingDeadlines
7171
);

src/backend/src/utils/validation.utils.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,28 @@ export const isOptionalDate = (validationObject: ValidationChain): ValidationCha
8787
return validationObject.optional().custom((value) => !isNaN(Date.parse(value)));
8888
};
8989

90+
export const isDateOnly = (validationObject: ValidationChain): ValidationChain => {
91+
return validationObject.custom((value) => {
92+
const parsed = Date.parse(value);
93+
if (isNaN(parsed)) return false;
94+
const date = new Date(parsed);
95+
return (
96+
date.getUTCHours() === 0 && date.getUTCMinutes() === 0 && date.getUTCSeconds() === 0 && date.getUTCMilliseconds() === 0
97+
);
98+
});
99+
};
100+
101+
export const isOptionalDateOnly = (validationObject: ValidationChain): ValidationChain => {
102+
return validationObject.optional().custom((value) => {
103+
const parsed = Date.parse(value);
104+
if (isNaN(parsed)) return false;
105+
const date = new Date(parsed);
106+
return (
107+
date.getUTCHours() === 0 && date.getUTCMinutes() === 0 && date.getUTCSeconds() === 0 && date.getUTCMilliseconds() === 0
108+
);
109+
});
110+
};
111+
90112
export const validateReimbursementProducts = () => {
91113
return [
92114
body('otherReimbursementProducts').isArray(),
@@ -117,7 +139,7 @@ export const workPackageProposedChangesValidators = (base: string) => [
117139
nonEmptyString(body(`${base}.leadId`).optional()),
118140
nonEmptyString(body(`${base}.managerId`).optional()),
119141
isWorkPackageStageOrNone(workPackageProposedChangesExists(body(`${base}.stage`).optional())),
120-
isDate(workPackageProposedChangesExists(body(`${base}.startDate`))),
142+
isDateOnly(workPackageProposedChangesExists(body(`${base}.startDate`))),
121143
intMinZero(workPackageProposedChangesExists(body(`${base}.duration`))),
122144
workPackageProposedChangesExists(body(`${base}.blockedBy`)).isArray(),
123145
intMinZero(body(`${base}.blockedBy.*.carNumber`)),

0 commit comments

Comments
 (0)