From 3240abc22e585345a45c472e93f187760b425408 Mon Sep 17 00:00:00 2001 From: Bruce Berrios <58147810+Bruception@users.noreply.github.com> Date: Mon, 7 Mar 2022 11:10:07 -0500 Subject: [PATCH] Enable strict null checks in backend (#2639) * Enable strict null checks in backend * Fix * Use non-null assertion * Add none --- backend/api/controllers/result.ts | 2 +- backend/api/routes/index.ts | 12 ++++++------ backend/api/routes/quotes.ts | 2 +- backend/dao/ape-keys.ts | 8 ++++---- backend/dao/quote-ratings.ts | 4 ++-- backend/init/db.ts | 12 ++++++------ backend/middlewares/api-utils.ts | 2 +- backend/middlewares/auth.ts | 9 +++++---- backend/middlewares/context.ts | 5 +++-- backend/server.ts | 2 +- backend/tsconfig.json | 3 ++- backend/types/types.d.ts | 6 +++--- backend/utils/error.ts | 7 +------ backend/utils/logger.ts | 4 ++-- backend/utils/pb.ts | 25 ++++++++++++------------- backend/utils/validation.ts | 3 ++- 16 files changed, 52 insertions(+), 54 deletions(-) diff --git a/backend/api/controllers/result.ts b/backend/api/controllers/result.ts index 395eb9056..6726b28f4 100644 --- a/backend/api/controllers/result.ts +++ b/backend/api/controllers/result.ts @@ -231,7 +231,7 @@ class ResultController { } let isPb = false; - let tagPbs = []; + let tagPbs: any[] = []; if (!result.bailedOut) { [isPb, tagPbs] = await Promise.all([ diff --git a/backend/api/routes/index.ts b/backend/api/routes/index.ts index d164bb494..dd7e7494a 100644 --- a/backend/api/routes/index.ts +++ b/backend/api/routes/index.ts @@ -1,3 +1,4 @@ +import _ from "lodash"; import users from "./users"; import configs from "./configs"; import results from "./results"; @@ -8,7 +9,7 @@ import quotes from "./quotes"; import apeKeys from "./ape-keys"; import { asyncHandler } from "../../middlewares/api-utils"; import { MonkeyResponse } from "../../utils/monkey-response"; -import { Application, NextFunction, Response } from "express"; +import { Application, NextFunction, Response, Router } from "express"; import swStats from "swagger-stats"; import SwaggerSpec from "../../swagger.json"; @@ -51,10 +52,10 @@ function addApiRoutes(app: Application): void { if (process.env.MODE === "dev") { return; } - const authHeader = rrr.http.request.headers.authorization ?? "None"; + const authHeader = rrr.http.request.headers?.authorization ?? "None"; const authType = authHeader.split(" "); - rrr.http.request.headers.authorization = authType[0]; - rrr.http.request.headers["x-forwarded-for"] = ""; + _.set(rrr.http.request, "headers.authorization", authType[0]); + _.set(rrr.http.request, "headers['x-forwarded-for']", ""); }, }) ); @@ -94,9 +95,8 @@ function addApiRoutes(app: Application): void { ]); }); - Object.keys(API_ROUTE_MAP).forEach((route) => { + _.each(API_ROUTE_MAP, (router: Router, route) => { const apiRoute = `${BASE_ROUTE}${route}`; - const router = API_ROUTE_MAP[route]; app.use(apiRoute, router); }); diff --git a/backend/api/routes/quotes.ts b/backend/api/routes/quotes.ts index 5fe0957d9..d8684ef0b 100644 --- a/backend/api/routes/quotes.ts +++ b/backend/api/routes/quotes.ts @@ -14,7 +14,7 @@ const quotesRouter = Router(); const checkIfUserIsQuoteMod = checkUserPermissions({ criteria: (user) => { - return user.quoteMod; + return !!user.quoteMod; }, }); diff --git a/backend/dao/ape-keys.ts b/backend/dao/ape-keys.ts index 20e948af2..dc21c5947 100644 --- a/backend/dao/ape-keys.ts +++ b/backend/dao/ape-keys.ts @@ -51,16 +51,16 @@ class ApeKeysDAO { const user = (await UsersDAO.getUser(uid)) as MonkeyTypes.User; checkIfKeyExists(user.apeKeys, keyId); - const apeKey = user.apeKeys[keyId]; + const apeKey = user.apeKeys![keyId]; - const updatedApeKey = { + const updatedApeKey: MonkeyTypes.ApeKey = { ...apeKey, modifiedOn: Date.now(), name: name ?? apeKey.name, enabled: _.isNil(enabled) ? apeKey.enabled : enabled, }; - user.apeKeys[keyId] = updatedApeKey; + user.apeKeys![keyId] = updatedApeKey; await UsersDAO.setApeKeys(uid, user.apeKeys); } @@ -79,7 +79,7 @@ class ApeKeysDAO { ): Promise { checkIfKeyExists(user.apeKeys, keyId); - user.apeKeys[keyId].lastUsedOn = Date.now(); + user.apeKeys![keyId].lastUsedOn = Date.now(); await UsersDAO.setApeKeys(user.uid, user.apeKeys); } } diff --git a/backend/dao/quote-ratings.ts b/backend/dao/quote-ratings.ts index 7c169d03a..ac5eb8807 100644 --- a/backend/dao/quote-ratings.ts +++ b/backend/dao/quote-ratings.ts @@ -28,7 +28,7 @@ class QuoteRatingsDAO { const quoteRating = await this.get(quoteId, language); const average = parseFloat( ( - Math.round((quoteRating.totalRating / quoteRating.ratings) * 10) / 10 + Math.round((quoteRating!.totalRating / quoteRating!.ratings) * 10) / 10 ).toFixed(1) ); @@ -40,7 +40,7 @@ class QuoteRatingsDAO { static async get( quoteId: number, language: string - ): Promise { + ): Promise { return await db .collection("quote-rating") .findOne({ quoteId, language }); diff --git a/backend/init/db.ts b/backend/init/db.ts index 8d1c81d25..a045d4d34 100644 --- a/backend/init/db.ts +++ b/backend/init/db.ts @@ -7,8 +7,8 @@ import { } from "mongodb"; class DatabaseClient { - static mongoClient: MongoClient = null; - static db: Db = null; + static mongoClient: MongoClient; + static db: Db; static collections: Record> = {}; static connected = false; @@ -22,6 +22,10 @@ class DatabaseClient { DB_NAME, } = process.env; + if (!DB_URI || !DB_NAME) { + throw new Error("No database configuration provided"); + } + const connectionOptions: MongoClientOptions = { connectTimeoutMS: 2000, serverSelectionTimeoutMS: 2000, @@ -64,10 +68,6 @@ class DatabaseClient { } static collection(collectionName: string): Collection { - if (!this.connected) { - return null; - } - if (!(collectionName in this.collections)) { this.collections[collectionName] = this.db.collection(collectionName); } diff --git a/backend/middlewares/api-utils.ts b/backend/middlewares/api-utils.ts index 227f6998e..315797267 100644 --- a/backend/middlewares/api-utils.ts +++ b/backend/middlewares/api-utils.ts @@ -132,7 +132,7 @@ function validateRequest(validationSchema: ValidationSchema): RequestHandler { throw new MonkeyError( 400, validationErrorMessage ?? - `${errorMessage} (${error.details[0].context.value})` + `${errorMessage} (${error.details[0]?.context?.value})` ); } } diff --git a/backend/middlewares/auth.ts b/backend/middlewares/auth.ts index 9e70990dd..6245992df 100644 --- a/backend/middlewares/auth.ts +++ b/backend/middlewares/auth.ts @@ -30,7 +30,7 @@ function authenticateRequest(authOptions = DEFAULT_OPTIONS): Handler { ): Promise => { try { const { authorization: authHeader } = req.headers; - let token: MonkeyTypes.DecodedToken = {}; + let token: MonkeyTypes.DecodedToken; if (authHeader) { token = await authenticateWithAuthHeader( @@ -77,6 +77,7 @@ function authenticateWithBody( return { type: "Bearer", uid, + email: "", }; } @@ -113,7 +114,7 @@ async function authenticateWithBearerToken( return { type: "Bearer", uid: decodedToken.uid, - email: decodedToken.email, + email: decodedToken.email ?? "", }; } catch (error) { console.log("-----------"); @@ -158,11 +159,11 @@ async function authenticateWithApeKey( const keyOwner = (await UsersDAO.getUser(uid)) as MonkeyTypes.User; const targetApeKey = _.get(keyOwner.apeKeys, keyId); - if (!targetApeKey.enabled) { + if (!targetApeKey?.enabled) { throw new MonkeyError(400, "ApeKey is disabled"); } - const isKeyValid = await compare(apeKey, targetApeKey?.hash); + const isKeyValid = await compare(apeKey, targetApeKey?.hash ?? ""); if (!isKeyValid) { throw new MonkeyError(400, "Invalid ApeKey"); diff --git a/backend/middlewares/context.ts b/backend/middlewares/context.ts index 7ebc48685..c05a9c9b2 100644 --- a/backend/middlewares/context.ts +++ b/backend/middlewares/context.ts @@ -11,8 +11,9 @@ async function contextMiddleware( req.ctx = { configuration, decodedToken: { - uid: null, - email: null, + type: "None", + uid: "", + email: "", }, }; diff --git a/backend/server.ts b/backend/server.ts index 3f878cd05..e1324c6a1 100644 --- a/backend/server.ts +++ b/backend/server.ts @@ -40,6 +40,6 @@ async function bootServer(port: number): Promise { }); } -const PORT = parseInt(process.env.PORT) || 5005; +const PORT = parseInt(process.env.PORT ?? "5005", 10); bootServer(PORT); diff --git a/backend/tsconfig.json b/backend/tsconfig.json index 14306d15f..8a2115bb4 100644 --- a/backend/tsconfig.json +++ b/backend/tsconfig.json @@ -10,7 +10,8 @@ "moduleResolution": "node", "resolveJsonModule": true, "allowSyntheticDefaultImports": true, - "esModuleInterop": true + "esModuleInterop": true, + "strictNullChecks": true }, "exclude": ["node_modules", "build"] } diff --git a/backend/types/types.d.ts b/backend/types/types.d.ts index 906ea829b..0a1a55006 100644 --- a/backend/types/types.d.ts +++ b/backend/types/types.d.ts @@ -27,9 +27,9 @@ declare namespace MonkeyTypes { } interface DecodedToken { - type?: "Bearer" | "ApeKey"; - uid?: string; - email?: string; + type: "Bearer" | "ApeKey" | "None"; + uid: string; + email: string; } interface Context { diff --git a/backend/utils/error.ts b/backend/utils/error.ts index 7bcba7248..02747a370 100644 --- a/backend/utils/error.ts +++ b/backend/utils/error.ts @@ -5,12 +5,7 @@ class MonkeyError extends Error { errorId: string; uid?: string; - constructor( - status: number, - message: string, - stack: string = null, - uid: string = null - ) { + constructor(status: number, message: string, stack?: string, uid?: string) { super(); this.status = status ?? 500; this.errorId = uuidv4(); diff --git a/backend/utils/logger.ts b/backend/utils/logger.ts index 834e77f93..58e2ee96e 100644 --- a/backend/utils/logger.ts +++ b/backend/utils/logger.ts @@ -12,9 +12,9 @@ export default { const logsCollection = db.collection("logs"); console.log(new Date(), "\t", event, "\t", uid, "\t", message); - await logsCollection.insertOne({ + logsCollection.insertOne({ timestamp: Date.now(), - uid, + uid: uid ?? "", event, message, }); diff --git a/backend/utils/pb.ts b/backend/utils/pb.ts index 0ff3af98e..e12e4d6d5 100644 --- a/backend/utils/pb.ts +++ b/backend/utils/pb.ts @@ -3,14 +3,14 @@ import _ from "lodash"; interface CheckAndUpdatePbResult { isPb: boolean; obj: object; - lbObj: object; + lbObj?: object; } type Result = MonkeyTypes.Result; export function checkAndUpdatePb( userPersonalBests: MonkeyTypes.User["personalBests"], - lbPersonalBests: MonkeyTypes.User["lbPersonalBests"], + lbPersonalBests: MonkeyTypes.User["lbPersonalBests"] | undefined, result: Result ): CheckAndUpdatePbResult { const { mode, mode2 } = result; @@ -34,7 +34,9 @@ export function checkAndUpdatePb( userPb[mode][mode2].push(buildPersonalBest(result)); } - updateLeaderboardPersonalBests(userPb, lbPersonalBests, result); + if (!_.isNil(lbPersonalBests)) { + updateLeaderboardPersonalBests(userPb, lbPersonalBests, result); + } return { isPb, @@ -69,8 +71,8 @@ function updatePersonalBest( personalBest.consistency = result.consistency; personalBest.difficulty = result.difficulty; personalBest.language = result.language; - personalBest.punctuation = result.punctuation; - personalBest.lazyMode = result.lazyMode; + personalBest.punctuation = result.punctuation ?? false; + personalBest.lazyMode = result.lazyMode ?? false; personalBest.raw = result.rawWpm; personalBest.wpm = result.wpm; personalBest.timestamp = Date.now(); @@ -83,9 +85,9 @@ function buildPersonalBest(result: Result): MonkeyTypes.PersonalBest { acc: result.acc, consistency: result.consistency, difficulty: result.difficulty, - lazyMode: result.lazyMode, + lazyMode: result.lazyMode ?? false, language: result.language, - punctuation: result.punctuation, + punctuation: result.punctuation ?? false, raw: result.rawWpm, wpm: result.wpm, timestamp: Date.now(), @@ -97,7 +99,7 @@ function updateLeaderboardPersonalBests( lbPersonalBests: MonkeyTypes.User["lbPersonalBests"], result: Result ): void { - if (!shouldUpdateLeaderboardPersonalBests(lbPersonalBests, result)) { + if (!shouldUpdateLeaderboardPersonalBests(result)) { return; } @@ -136,11 +138,8 @@ function updateLeaderboardPersonalBests( ); } -function shouldUpdateLeaderboardPersonalBests( - lbPersonalBests: MonkeyTypes.User["lbPersonalBests"], - result: Result -): boolean { +function shouldUpdateLeaderboardPersonalBests(result: Result): boolean { const isValidTimeMode = result.mode === "time" && (result.mode2 === "15" || result.mode2 === "60"); - return lbPersonalBests && isValidTimeMode && !result.lazyMode; + return isValidTimeMode && !result.lazyMode; } diff --git a/backend/utils/validation.ts b/backend/utils/validation.ts index e0d869926..e0970b274 100644 --- a/backend/utils/validation.ts +++ b/backend/utils/validation.ts @@ -53,7 +53,8 @@ export function isTestTooShort(result: MonkeyTypes.CompletedEvent): boolean { if (mode === "custom") { if (!customText) return true; const { isWordRandom, isTimeRandom, textLen, word, time } = customText; - const setTextTooShort = !isWordRandom && !isTimeRandom && textLen < 10; + const setTextTooShort = + !isWordRandom && !isTimeRandom && _.isNumber(textLen) && textLen < 10; const randomWordsTooShort = isWordRandom && !isTimeRandom && word < 10; const randomTimeTooShort = !isWordRandom && isTimeRandom && time < 15; return setTextTooShort || randomWordsTooShort || randomTimeTooShort;