Skip to content

Commit 95221fe

Browse files
fix: resolve issues #463-#466 - Zod type exports, CI workflow, dep checker
- Issue #463: users.validation.ts and invitations.validation.ts already had clean inferred type exports; no changes needed. - Issue #464: Update shipments.controller.ts to import and use all Zod-inferred types (CreateShipmentInput, ShipmentIdParam, ShipmentPatchBody, ShipmentStatusInput) from shipments.validation.ts across all 9 handlers. - Issue #465: .github/workflows/typecheck.yml and README.md badge already exist. - Issue #466: Fix duplicate check:deps key in package.json (JSON validity bug). - Bonus: Fix auth.routes.ts duplicate setup2faController import (TS2300). - Bonus: Migrate twoFactor.service.ts from otplib v12 authenticator API to v13 functional API (generateSecret, generateURI, verifySync).
1 parent 0e0bf82 commit 95221fe

4 files changed

Lines changed: 24 additions & 20 deletions

File tree

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
"start": "node dist/src/main.js",
99
"typecheck": "tsc -p tsconfig.json --noEmit",
1010
"check:deps": "node scripts/check-undeclared-deps.js",
11-
"check:deps": "node scripts/check-undeclared-deps.js",
1211
"lint": "eslint src --ext .ts",
1312
"lint:fix": "eslint src --ext .ts --fix",
1413
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",

src/modules/auth/auth.routes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import {
2222
resetPasswordController,
2323
refreshController,
2424
registerCompanyController,
25-
setup2faController,
2625
} from './auth.controller.js';
2726
import {
2827
createApiKeyController,
@@ -132,6 +131,7 @@ authRouter.post(
132131
'/2fa/backup-codes/regenerate',
133132
asyncHandler(requireAuth),
134133
asyncHandler(regenerateBackupCodesController)
134+
);
135135
// Session management routes (protected by JWT auth)
136136
authRouter.get('/sessions', asyncHandler(requireAuth), asyncHandler(listSessionsController));
137137
authRouter.delete(

src/modules/auth/twoFactor.service.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import bcrypt from 'bcrypt';
22
import { randomBytes } from 'crypto';
3-
import { authenticator } from 'otplib';
3+
import { generateSecret, generateURI, verifySync } from 'otplib';
44
import { AppError, ErrorCodes } from '../../shared/http/errors.js';
55
import { UserModel } from '../users/users.model.js';
66

@@ -52,12 +52,12 @@ export async function setup2fa(userId: string): Promise<{ otpauthUrl: string; se
5252
throw new AppError(409, '2FA is already enabled', ErrorCodes.TOTP_ALREADY_ENABLED);
5353
}
5454

55-
const secret = authenticator.generateSecret();
55+
const secret = generateSecret();
5656

5757
// Store the pending secret (not yet active — totpEnabled stays false)
5858
await UserModel.findByIdAndUpdate(userId, { totpSecret: secret });
5959

60-
const otpauthUrl = authenticator.keyuri(user.email as string, 'Navin', secret);
60+
const otpauthUrl = generateURI({ label: user.email as string, issuer: 'Navin', secret });
6161

6262
return { otpauthUrl, secret };
6363
}
@@ -96,8 +96,8 @@ export async function verify2fa(userId: string, code: string): Promise<{ backupC
9696
throw new AppError(409, '2FA is already enabled', ErrorCodes.TOTP_ALREADY_ENABLED);
9797
}
9898

99-
const isValid = authenticator.verify({ token: code, secret: user.totpSecret as string });
100-
if (!isValid) {
99+
const verifyResult = verifySync({ token: code, secret: user.totpSecret as string });
100+
if (!verifyResult.valid) {
101101
throw new AppError(400, 'Invalid TOTP code', ErrorCodes.TOTP_INVALID_CODE);
102102
}
103103

src/modules/shipments/shipments.controller.ts

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ import type {
2727
UploadDocumentBody,
2828
UploadPhotoBody,
2929
CreateDisputeBody,
30+
CreateShipmentInput,
31+
ShipmentIdParam,
32+
ShipmentPatchBody,
33+
ShipmentStatusInput,
3034
} from './shipments.validation.js';
3135
import { AppError, ErrorCodes } from '../../shared/http/errors.js';
3236

@@ -112,7 +116,7 @@ export const getShipments = async (req: Request, res: Response) => {
112116
* @throws {AppError} 400 VALIDATION_ERROR — when the id param is invalid.
113117
*/
114118
export const getShipmentById = async (req: Request, res: Response) => {
115-
const { id } = req.params;
119+
const { id } = req.params as unknown as ShipmentIdParam;
116120
const shipment = await getShipmentByIdService(id, {
117121
organizationId: req.user?.organizationId,
118122
role: req.user?.role,
@@ -134,7 +138,7 @@ export const getShipmentById = async (req: Request, res: Response) => {
134138
* @throws {AppError} 400 VALIDATION_ERROR — when params/query validation fails.
135139
*/
136140
export const getShipmentTimeline = async (req: Request, res: Response) => {
137-
const { id } = req.params;
141+
const { id } = req.params as unknown as ShipmentIdParam;
138142
const query = req.query as unknown as ShipmentTimelineQuery;
139143
const { cursor, limit = 20 } = query;
140144
const { data, nextCursor, hasMore } = await getShipmentTimelineService(id, {
@@ -161,7 +165,8 @@ export const getShipmentTimeline = async (req: Request, res: Response) => {
161165
* @throws {AppError} 400 VALIDATION_ERROR — when body validation fails.
162166
*/
163167
export const createShipment = async (req: Request, res: Response) => {
164-
const shipment = await createShipmentService({ ...req.body, actorUserId: req.user?.userId });
168+
const body = req.body as CreateShipmentInput;
169+
const shipment = await createShipmentService({ ...body, actorUserId: req.user?.userId });
165170
sendResponse(res, 201, true, 'Shipment created', shipment);
166171
};
167172

@@ -176,8 +181,8 @@ export const createShipment = async (req: Request, res: Response) => {
176181
* @throws {AppError} 400 VALIDATION_ERROR — when params/body validation fails.
177182
*/
178183
export const patchShipment = async (req: Request, res: Response) => {
179-
const { id } = req.params;
180-
const { offChainMetadata } = req.body;
184+
const { id } = req.params as unknown as ShipmentIdParam;
185+
const { offChainMetadata } = req.body as ShipmentPatchBody;
181186
const shipment = await patchShipmentService(id, offChainMetadata);
182187
if (!shipment) {
183188
sendResponse(res, 404, false, 'Shipment not found', null);
@@ -198,8 +203,8 @@ export const patchShipment = async (req: Request, res: Response) => {
198203
* @throws {AppError} 400 VALIDATION_ERROR — when params/body validation fails.
199204
*/
200205
export const patchShipmentStatus = async (req: Request, res: Response) => {
201-
const { id } = req.params;
202-
const { status } = req.body;
206+
const { id } = req.params as unknown as ShipmentIdParam;
207+
const { status } = req.body as ShipmentStatusInput;
203208

204209
if (!status || typeof status !== 'string') {
205210
sendResponse(res, 400, false, 'Missing status', null);
@@ -238,7 +243,7 @@ export const patchShipmentStatus = async (req: Request, res: Response) => {
238243
* @throws {AppError} 400 VALIDATION_ERROR — when params/body validation fails.
239244
*/
240245
export const uploadShipmentProof = async (req: Request, res: Response) => {
241-
const { id } = req.params;
246+
const { id } = req.params as unknown as ShipmentIdParam;
242247
const { recipientSignatureName, notes } = req.body as ShipmentProofBody;
243248
const file = req.file;
244249

@@ -272,7 +277,7 @@ export const uploadShipmentProof = async (req: Request, res: Response) => {
272277
* @throws {AppError} 400 VALIDATION_ERROR — when params/body validation fails.
273278
*/
274279
export const uploadShipmentDocument = async (req: Request, res: Response) => {
275-
const { id } = req.params;
280+
const { id } = req.params as unknown as ShipmentIdParam;
276281
const { type } = req.body as UploadDocumentBody;
277282
const file = req.file;
278283

@@ -318,7 +323,7 @@ export const uploadShipmentDocument = async (req: Request, res: Response) => {
318323
* @throws {AppError} 400 VALIDATION_ERROR — when params/body validation fails.
319324
*/
320325
export const uploadShipmentPhoto = async (req: Request, res: Response) => {
321-
const { id } = req.params;
326+
const { id } = req.params as unknown as ShipmentIdParam;
322327
const { caption } = req.body as UploadPhotoBody;
323328
const file = req.file;
324329

@@ -361,7 +366,7 @@ export const uploadShipmentPhoto = async (req: Request, res: Response) => {
361366
* @throws {AppError} 400 VALIDATION_ERROR — when params/body validation fails.
362367
*/
363368
export const createDispute = async (req: Request, res: Response) => {
364-
const { id } = req.params;
369+
const { id } = req.params as unknown as ShipmentIdParam;
365370
const { type, description } = req.body as CreateDisputeBody;
366371
const file = req.file;
367372

@@ -427,7 +432,7 @@ export const exportShipments = async (req: Request, res: Response) => {
427432
* @throws {AppError} 400 VALIDATION_ERROR — when the id param is invalid.
428433
*/
429434
export const deleteShipment = async (req: Request, res: Response) => {
430-
const { id } = req.params;
435+
const { id } = req.params as unknown as ShipmentIdParam;
431436
const shipment = await deleteShipmentService(id);
432437

433438
if (!shipment) {
@@ -451,7 +456,7 @@ export const deleteShipment = async (req: Request, res: Response) => {
451456
* @throws {AppError} 400 VALIDATION_ERROR — when the id param is invalid.
452457
*/
453458
export const getShipmentEta = async (req: Request, res: Response) => {
454-
const { id } = req.params;
459+
const { id } = req.params as unknown as ShipmentIdParam;
455460
const eta = await getShipmentEtaService(id);
456461
sendResponse(res, 200, true, 'Shipment ETA retrieved', eta);
457462
};

0 commit comments

Comments
 (0)