Skip to content

Commit dd95295

Browse files
committed
Enable strict mode in TS and fix most issues with it
1 parent e508dd9 commit dd95295

30 files changed

+7389
-4172
lines changed

.eslintignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
dist
1+
dist
2+
*.js

package-lock.json

+7,105-4,040
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
"dotenv": "^16.0.3",
3434
"email-validator": "^2.0.4",
3535
"express": "^4.17.1",
36-
"express-rate-limit": "^5.3.0",
36+
"express-rate-limit": "^6.7.0",
3737
"express-subdomain": "^1.0.5",
3838
"fs-extra": "^8.1.0",
3939
"got": "^11.8.2",
@@ -55,13 +55,17 @@
5555
},
5656
"devDependencies": {
5757
"@hcaptcha/types": "^1.0.3",
58+
"@types/bcrypt": "^5.0.0",
59+
"@types/cors": "^2.8.13",
5860
"@types/dicer": "^0.2.2",
5961
"@types/express": "^4.17.17",
6062
"@types/fs-extra": "^11.0.1",
63+
"@types/mongoose-unique-validator": "^1.0.7",
6164
"@types/morgan": "^1.9.4",
6265
"@types/node": "^18.14.4",
6366
"@types/node-rsa": "^1.1.1",
6467
"@types/nodemailer": "^6.4.7",
68+
"@types/validator": "^13.7.14",
6569
"@typescript-eslint/eslint-plugin": "^5.54.1",
6670
"@typescript-eslint/parser": "^5.54.1",
6771
"eslint": "^8.35.0",

src/cache.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@ export async function setCachedFile(fileName: string, value: Buffer): Promise<vo
2828
}
2929

3030
export async function getCachedFile(fileName: string, encoding?: BufferEncoding): Promise<Buffer> {
31-
let cachedFile: Buffer;
31+
let cachedFile: Buffer = Buffer.alloc(0);
3232

3333
if (disabledFeatures.redis) {
3434
cachedFile = memoryCache[fileName] || null;
3535
} else {
36-
const redisValue: string = await client.get(fileName);
36+
const redisValue: string | null = await client.get(fileName);
3737
if (redisValue) {
3838
cachedFile = Buffer.from(redisValue, encoding);
3939
}
@@ -177,7 +177,7 @@ export async function getLocalCDNFile(name: string, encoding?: BufferEncoding):
177177

178178
if (file === null) {
179179
if (await fs.pathExists(`${LOCAL_CDN_BASE}/${name}`)) {
180-
const fileBuffer: string = await fs.readFile(`${LOCAL_CDN_BASE}/${name}`, { encoding });
180+
const fileBuffer: string | Buffer = await fs.readFile(`${LOCAL_CDN_BASE}/${name}`, { encoding });
181181
file = Buffer.from(fileBuffer);
182182
await setLocalCDNFile(name, file);
183183
}

src/config-manager.ts

+18-18
Original file line numberDiff line numberDiff line change
@@ -15,49 +15,49 @@ export const disabledFeatures: DisabledFeatures = {
1515

1616
LOG_INFO('Loading config');
1717

18-
let mongooseConnectOptions: mongoose.ConnectOptions;
18+
let mongooseConnectOptions: mongoose.ConnectOptions = {};
1919

2020
if (process.env.PN_ACT_CONFIG_MONGOOSE_CONNECT_OPTIONS_PATH) {
2121
mongooseConnectOptions = fs.readJSONSync(process.env.PN_ACT_CONFIG_MONGOOSE_CONNECT_OPTIONS_PATH);
2222
}
2323

2424
export const config: Config = {
2525
http: {
26-
port: Number(process.env.PN_ACT_CONFIG_HTTP_PORT)
26+
port: Number(process.env.PN_ACT_CONFIG_HTTP_PORT || '')
2727
},
2828
mongoose: {
29-
connection_string: process.env.PN_ACT_CONFIG_MONGO_CONNECTION_STRING,
29+
connection_string: process.env.PN_ACT_CONFIG_MONGO_CONNECTION_STRING || '',
3030
options: mongooseConnectOptions
3131
},
3232
redis: {
3333
client: {
34-
url: process.env.PN_ACT_CONFIG_REDIS_URL
34+
url: process.env.PN_ACT_CONFIG_REDIS_URL || ''
3535
}
3636
},
3737
email: {
38-
host: process.env.PN_ACT_CONFIG_EMAIL_HOST,
39-
port: Number(process.env.PN_ACT_CONFIG_EMAIL_PORT),
40-
secure: Boolean(process.env.PN_ACT_CONFIG_EMAIL_SECURE),
38+
host: process.env.PN_ACT_CONFIG_EMAIL_HOST || '',
39+
port: Number(process.env.PN_ACT_CONFIG_EMAIL_PORT || ''),
40+
secure: Boolean(process.env.PN_ACT_CONFIG_EMAIL_SECURE || ''),
4141
auth: {
42-
user: process.env.PN_ACT_CONFIG_EMAIL_USERNAME,
43-
pass: process.env.PN_ACT_CONFIG_EMAIL_PASSWORD
42+
user: process.env.PN_ACT_CONFIG_EMAIL_USERNAME || '',
43+
pass: process.env.PN_ACT_CONFIG_EMAIL_PASSWORD || ''
4444
},
45-
from: process.env.PN_ACT_CONFIG_EMAIL_FROM
45+
from: process.env.PN_ACT_CONFIG_EMAIL_FROM || ''
4646
},
4747
s3: {
48-
endpoint: process.env.PN_ACT_CONFIG_S3_ENDPOINT,
49-
key: process.env.PN_ACT_CONFIG_S3_ACCESS_KEY,
50-
secret: process.env.PN_ACT_CONFIG_S3_ACCESS_SECRET
48+
endpoint: process.env.PN_ACT_CONFIG_S3_ENDPOINT || '',
49+
key: process.env.PN_ACT_CONFIG_S3_ACCESS_KEY || '',
50+
secret: process.env.PN_ACT_CONFIG_S3_ACCESS_SECRET || ''
5151
},
5252
hcaptcha: {
53-
secret: process.env.PN_ACT_CONFIG_HCAPTCHA_SECRET
53+
secret: process.env.PN_ACT_CONFIG_HCAPTCHA_SECRET || ''
5454
},
5555
cdn: {
56-
subdomain: process.env.PN_ACT_CONFIG_CDN_SUBDOMAIN,
57-
disk_path: process.env.PN_ACT_CONFIG_CDN_DISK_PATH,
58-
base_url: process.env.PN_ACT_CONFIG_CDN_BASE_URL
56+
subdomain: process.env.PN_ACT_CONFIG_CDN_SUBDOMAIN || '',
57+
disk_path: process.env.PN_ACT_CONFIG_CDN_DISK_PATH || '',
58+
base_url: process.env.PN_ACT_CONFIG_CDN_BASE_URL || ''
5959
},
60-
website_base: process.env.PN_ACT_CONFIG_WEBSITE_BASE
60+
website_base: process.env.PN_ACT_CONFIG_WEBSITE_BASE || ''
6161
};
6262

6363
LOG_INFO('Config loaded, checking integrity');

src/database.ts

+33-22
Original file line numberDiff line numberDiff line change
@@ -42,23 +42,23 @@ export function verifyConnected(): void {
4242
}
4343
}
4444

45-
export async function getUserByUsername(username: string): Promise<HydratedPNIDDocument> {
45+
export async function getUserByUsername(username: string): Promise<HydratedPNIDDocument | null> {
4646
verifyConnected();
4747

4848
return await PNID.findOne<HydratedPNIDDocument>({
4949
usernameLower: username.toLowerCase()
5050
});
5151
}
5252

53-
export async function getUserByPID(pid: number): Promise<HydratedPNIDDocument> {
53+
export async function getUserByPID(pid: number): Promise<HydratedPNIDDocument | null> {
5454
verifyConnected();
5555

5656
return await PNID.findOne<HydratedPNIDDocument>({
5757
pid
5858
});
5959
}
6060

61-
export async function getUserByEmailAddress(email: string): Promise<HydratedPNIDDocument> {
61+
export async function getUserByEmailAddress(email: string): Promise<HydratedPNIDDocument | null> {
6262
verifyConnected();
6363

6464
// TODO - Update documents to store email normalized
@@ -73,7 +73,7 @@ export async function doesUserExist(username: string): Promise<boolean> {
7373
return !!await getUserByUsername(username);
7474
}
7575

76-
export async function getUserBasic(token: string): Promise<HydratedPNIDDocument> {
76+
export async function getUserBasic(token: string): Promise<HydratedPNIDDocument | null> {
7777
verifyConnected();
7878

7979
// * Wii U sends Basic auth as `username password`, where the password may not have spaces
@@ -84,7 +84,7 @@ export async function getUserBasic(token: string): Promise<HydratedPNIDDocument>
8484
const username: string = parts[0];
8585
const password: string = parts[1];
8686

87-
const user: HydratedPNIDDocument = await getUserByUsername(username);
87+
const user: HydratedPNIDDocument | null = await getUserByUsername(username);
8888

8989
if (!user) {
9090
return null;
@@ -99,14 +99,14 @@ export async function getUserBasic(token: string): Promise<HydratedPNIDDocument>
9999
return user;
100100
}
101101

102-
export async function getUserBearer(token: string): Promise<HydratedPNIDDocument> {
102+
export async function getUserBearer(token: string): Promise<HydratedPNIDDocument | null> {
103103
verifyConnected();
104104

105105
try {
106106
const decryptedToken: Buffer = await decryptToken(Buffer.from(token, 'base64'));
107107
const unpackedToken: Token = unpackToken(decryptedToken);
108108

109-
const user: HydratedPNIDDocument = await getUserByPID(unpackedToken.pid);
109+
const user: HydratedPNIDDocument | null = await getUserByPID(unpackedToken.pid);
110110

111111
if (user) {
112112
const expireTime: number = Math.floor((Number(unpackedToken.expire_time) / 1000));
@@ -124,27 +124,38 @@ export async function getUserBearer(token: string): Promise<HydratedPNIDDocument
124124
}
125125
}
126126

127-
export async function getUserProfileJSONByPID(pid: number): Promise<PNIDProfile> {
127+
export async function getUserProfileJSONByPID(pid: number): Promise<PNIDProfile | null> {
128128
verifyConnected();
129129

130-
const user: HydratedPNIDDocument = await getUserByPID(pid);
130+
const user: HydratedPNIDDocument | null = await getUserByPID(pid);
131+
132+
if (!user) {
133+
return null;
134+
}
135+
131136
const device: HydratedDeviceDocument = user.get('devices')[0]; // * Just grab the first device
132-
let device_attributes: [{
137+
let device_attributes: {
133138
device_attribute: {
134139
name: string;
135140
value: string;
136141
created_date: string;
137142
};
138-
}];
143+
}[] = [];
139144

140145
if (device) {
141-
device_attributes = device.get('device_attributes').map(({name, value, created_date}) => ({
142-
device_attribute: {
143-
name,
144-
value,
145-
created_date: created_date ? created_date : ''
146-
}
147-
}));
146+
device_attributes = device.get('device_attributes').map((attribute: { name: string; value: string; created_date: string; }) => {
147+
const name: string = attribute.name;
148+
const value: string = attribute.value;
149+
const created_date: string = attribute.created_date;
150+
151+
return {
152+
device_attribute: {
153+
name,
154+
value,
155+
created_date: created_date ? created_date : ''
156+
}
157+
};
158+
});
148159
}
149160

150161
return {
@@ -196,21 +207,21 @@ export async function getUserProfileJSONByPID(pid: number): Promise<PNIDProfile>
196207
};
197208
}
198209

199-
export async function getServer(gameServerId: string, accessMode: string): Promise<HydratedServerDocument> {
210+
export async function getServer(gameServerId: string, accessMode: string): Promise<HydratedServerDocument | null> {
200211
return await Server.findOne({
201212
game_server_id: gameServerId,
202213
access_mode: accessMode
203214
});
204215
}
205216

206-
export async function getServerByTitleId(titleId: string, accessMode: string): Promise<HydratedServerDocument> {
217+
export async function getServerByTitleId(titleId: string, accessMode: string): Promise<HydratedServerDocument | null> {
207218
return await Server.findOne({
208219
title_ids: titleId,
209220
access_mode: accessMode
210221
});
211222
}
212223

213-
export async function addUserConnection(pnid: HydratedPNIDDocument, data: ConnectionData, type: string): Promise<ConnectionResponse> {
224+
export async function addUserConnection(pnid: HydratedPNIDDocument, data: ConnectionData, type: string): Promise<ConnectionResponse | undefined> {
214225
if (type === 'discord') {
215226
return await addUserConnectionDiscord(pnid, data);
216227
}
@@ -239,7 +250,7 @@ export async function addUserConnectionDiscord(pnid: HydratedPNIDDocument, data:
239250
};
240251
}
241252

242-
export async function removeUserConnection(pnid: HydratedPNIDDocument, type: string): Promise<ConnectionResponse> {
253+
export async function removeUserConnection(pnid: HydratedPNIDDocument, type: string): Promise<ConnectionResponse | undefined> {
243254
// * Add more connections later?
244255
if (type === 'discord') {
245256
return await removeUserConnectionDiscord(pnid);

src/mailer.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ export async function sendMail(options: MailerOptions): Promise<void> {
2020
let html: string = confirmation ? confirmationEmailTemplate : genericEmailTemplate;
2121

2222
html = html.replace(/{{username}}/g, username);
23-
html = html.replace(/{{paragraph}}/g, paragraph);
24-
html = html.replace(/{{preview}}/g, (preview || ''));
25-
html = html.replace(/{{confirmation-href}}/g, (confirmation?.href || ''));
26-
html = html.replace(/{{confirmation-code}}/g, (confirmation?.code || ''));
23+
html = html.replace(/{{paragraph}}/g, paragraph || '');
24+
html = html.replace(/{{preview}}/g, preview || '');
25+
html = html.replace(/{{confirmation-href}}/g, confirmation?.href || '');
26+
html = html.replace(/{{confirmation-code}}/g, confirmation?.code || '');
2727

2828
if (link) {
2929
const { href, text } = link;

src/middleware/api.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ import { getUserBearer } from '@/database';
33
import { HydratedPNIDDocument } from '@/types/mongoose/pnid';
44

55
async function APIMiddleware(request: express.Request, _response: express.Response, next: express.NextFunction): Promise<void> {
6-
const authHeader: string = request.headers.authorization;
6+
const authHeader: string | undefined = request.headers.authorization;
77

88
if (!authHeader || !(authHeader.startsWith('Bearer'))) {
99
return next();
1010
}
1111

1212
const token: string = authHeader.split(' ')[1];
13-
const user: HydratedPNIDDocument = await getUserBearer(token);
13+
const user: HydratedPNIDDocument | null = await getUserBearer(token);
1414

1515
request.pnid = user;
1616

src/middleware/nasc.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@ async function NASCMiddleware(request: express.Request, response: express.Respon
3535
const macAddressHash: string = crypto.createHash('sha256').update(macAddress).digest('base64');
3636
const fcdcertHash: string = crypto.createHash('sha256').update(fcdcert).digest('base64');
3737

38-
let pid: number;
39-
let pidHmac: string;
40-
let password: string;
38+
let pid: number = 0; // * Real PIDs are always positive and non-zero
39+
let pidHmac: string = '';
40+
let password: string = '';
4141

4242
if (requestParams.userid) {
4343
pid = Number(nintendoBase64Decode(requestParams.userid).toString());
@@ -68,7 +68,7 @@ async function NASCMiddleware(request: express.Request, response: express.Respon
6868
return;
6969
}
7070

71-
let model: string;
71+
let model: string = '';
7272
switch (serialNumber[0]) {
7373
case 'C':
7474
model = 'ctr';
@@ -95,7 +95,7 @@ async function NASCMiddleware(request: express.Request, response: express.Respon
9595
return;
9696
}
9797

98-
let device: HydratedDeviceDocument = await Device.findOne({
98+
let device: HydratedDeviceDocument | null = await Device.findOne({
9999
model,
100100
serial: serialNumber,
101101
environment,
@@ -174,7 +174,7 @@ async function NASCMiddleware(request: express.Request, response: express.Respon
174174
}
175175
}
176176

177-
const nexUser: HydratedNEXAccountDocument = await NEXAccount.findOne({ pid });
177+
const nexUser: HydratedNEXAccountDocument | null = await NEXAccount.findOne({ pid });
178178

179179
if (!nexUser || nexUser.get('access_level') < 0) {
180180
response.status(200).send(nascError('102'));

src/middleware/pnid.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { getUserBasic, getUserBearer } from '@/database';
44
import { HydratedPNIDDocument } from '@/types/mongoose/pnid';
55

66
async function PNIDMiddleware(request: express.Request, response: express.Response, next: express.NextFunction): Promise<void> {
7-
const authHeader: string = request.headers.authorization;
7+
const authHeader: string | undefined = request.headers.authorization;
88

99
if (!authHeader || !(authHeader.startsWith('Bearer') || authHeader.startsWith('Basic'))) {
1010
return next();
@@ -13,7 +13,7 @@ async function PNIDMiddleware(request: express.Request, response: express.Respon
1313
const parts: string[] = authHeader.split(' ');
1414
const type: string = parts[0];
1515
let token: string = parts[1];
16-
let user: HydratedPNIDDocument;
16+
let user: HydratedPNIDDocument | null;
1717

1818
if (request.isCemu) {
1919
token = Buffer.from(token, 'hex').toString('base64');

src/middleware/xml-parser.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import { document as xmlParser } from 'xmlbuilder2';
44

55
function XMLMiddleware(request: express.Request, response: express.Response, next: express.NextFunction): void {
66
if (request.method == 'POST' || request.method == 'PUT') {
7-
const contentType: string = request.headers['content-type'];
8-
const contentLength: string = request.headers['content-length'];
7+
const contentType: string | undefined = request.headers['content-type'];
8+
const contentLength: string | undefined = request.headers['content-length'];
99
let body: string = '';
1010

1111
if (

src/models/pnid.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ PNIDSchema.method('generatePID', async function generatePID(): Promise<void> {
121121

122122
const pid: number = Math.floor(Math.random() * (max - min + 1) + min);
123123

124-
const inuse: HydratedPNIDDocument = await PNID.findOne({
124+
const inuse: HydratedPNIDDocument | null = await PNID.findOne({
125125
pid
126126
});
127127

@@ -143,7 +143,7 @@ PNIDSchema.method('generateEmailValidationCode', async function generateEmailVal
143143
PNIDSchema.method('generateEmailValidationToken', async function generateEmailValidationToken(): Promise<void> {
144144
const token: string = crypto.randomBytes(32).toString('hex');
145145

146-
const inuse: HydratedPNIDDocument = await PNID.findOne({
146+
const inuse: HydratedPNIDDocument | null = await PNID.findOne({
147147
'identification.email_token': token
148148
});
149149

0 commit comments

Comments
 (0)