Convert to TypeScript

This commit is contained in:
Jonathan Barrow
2023-08-17 22:32:40 -04:00
parent 3b5aeb222c
commit 38739fef7e
26 changed files with 4247 additions and 445 deletions

49
src/config-manager.ts Normal file
View File

@@ -0,0 +1,49 @@
import dotenv from 'dotenv';
import { md5 } from '@/util';
import { LOG_INFO, LOG_ERROR } from '@/logger';
import { Config } from '@/types/common/config';
dotenv.config();
const BOSS_WIIU_AES_KEY_HASH = Buffer.from('5202ce5099232c3d365e28379790a919', 'hex');
const BOSS_WIIU_HMAC_KEY_HASH = Buffer.from('b4482fef177b0100090ce0dbeb8ce977', 'hex');
const BOSS_3DS_AES_KEY_HASH = Buffer.from('86fbc2bb4cb703b2a4c6cc9961319926', 'hex');
LOG_INFO('Loading config');
export const config: Config = {
http: {
port: Number(process.env.PN_BOSS_CONFIG_HTTP_PORT || '')
},
crypto: {
wup: {
aes_key: Buffer.from(process.env.PN_BOSS_CONFIG_BOSS_WIIU_AES_KEY || ''),
hmac_key: Buffer.from(process.env.PN_BOSS_CONFIG_BOSS_WIIU_HMAC_KEY || ''),
},
ctr: {
aes_key: Buffer.from(process.env.PN_BOSS_CONFIG_BOSS_3DS_AES_KEY || '', 'hex')
}
}
};
LOG_INFO('Config loaded, checking integrity');
if (!config.http.port) {
LOG_ERROR('Failed to find HTTP port. Set the PN_BOSS_CONFIG_HTTP_PORT environment variable');
process.exit(0);
}
if (!BOSS_WIIU_AES_KEY_HASH.equals(md5(config.crypto.wup.aes_key))) {
LOG_ERROR('Invalid BOSS WiiU AES key. Set or correct the PN_BOSS_CONFIG_BOSS_WIIU_AES_KEY environment variable');
process.exit(0);
}
if (!BOSS_WIIU_HMAC_KEY_HASH.equals(md5(config.crypto.wup.hmac_key))) {
LOG_ERROR('Invalid BOSS WiiU HMAC key. Set or correct the PN_BOSS_CONFIG_BOSS_WIIU_HMAC_KEY environment variable');
process.exit(0);
}
if (!BOSS_3DS_AES_KEY_HASH.equals(md5(config.crypto.ctr.aes_key))) {
LOG_ERROR('Invalid BOSS 3DS AES key. Set or correct the PN_BOSS_CONFIG_BOSS_3DS_AES_KEY environment variable');
process.exit(0);
}

47
src/logger.ts Normal file
View File

@@ -0,0 +1,47 @@
import fs from 'fs-extra';
import colors from 'colors';
colors.enable();
const root = process.env.PN_BOSS_CONFIG_LOGGER_PATH ? process.env.PN_BOSS_CONFIG_LOGGER_PATH : `${__dirname}/..`;
fs.ensureDirSync(`${root}/logs`);
const streams = {
latest: fs.createWriteStream(`${root}/logs/latest.log`),
success: fs.createWriteStream(`${root}/logs/success.log`),
error: fs.createWriteStream(`${root}/logs/error.log`),
warn: fs.createWriteStream(`${root}/logs/warn.log`),
info: fs.createWriteStream(`${root}/logs/info.log`)
} as const;
export function LOG_SUCCESS(input: string): void {
const time = new Date();
input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [SUCCESS]: ${input}`;
streams.success.write(`${input}\n`);
console.log(`${input}`.green.bold);
}
export function LOG_ERROR(input: string): void {
const time = new Date();
input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [ERROR]: ${input}`;
streams.error.write(`${input}\n`);
console.log(`${input}`.red.bold);
}
export function LOG_WARN(input: string): void {
const time = new Date();
input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [WARN]: ${input}`;
streams.warn.write(`${input}\n`);
console.log(`${input}`.yellow.bold);
}
export function LOG_INFO(input: string): void {
const time = new Date();
input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [INFO]: ${input}`;
streams.info.write(`${input}\n`);
console.log(`${input}`.cyan.bold);
}

View File

@@ -1,60 +0,0 @@
process.title = 'Pretendo - BOSS';
const express = require('express');
const morgan = require('morgan');
const logger = require('../logger');
const config = require('../config.json');
const { http: { port } } = config;
const app = express();
const nptsService = require('./services/npts');
const npplService = require('./services/nppl');
const npdiService = require('./services/npdi');
// START APPLICATION
app.set('etag', false);
app.disable('x-powered-by');
// Create router
logger.info('Setting up Middleware');
app.use(morgan('dev'));
app.use(express.json());
app.use(express.urlencoded({
extended: true
}));
app.use(nptsService);
app.use(npplService);
app.use(npdiService);
// 404 handler
logger.info('Creating 404 status handler');
app.use((request, response) => {
response.status(404);
response.json({
app: 'api',
status: 404,
error: 'Route not found'
});
});
// non-404 error handler
logger.info('Creating non-404 status handler');
app.use((error, request, response) => {
const status = error.status || 500;
response.status(status);
response.json({
app: 'api',
status,
error: error.message
});
});
// Starts the server
logger.info('Starting server');
app.listen(port, () => {
logger.success(`Server started on port ${port}`);
});

53
src/server.ts Normal file
View File

@@ -0,0 +1,53 @@
process.title = 'Pretendo - BOSS';
import express from 'express';
import morgan from 'morgan';
import { LOG_INFO, LOG_SUCCESS } from '@/logger';
import { config } from '@/config-manager';
import nppl from '@/services/nppl';
import npts from '@/services/npts';
import npdi from '@/services/npdi';
const app = express();
app.set('etag', false);
app.disable('x-powered-by');
LOG_INFO('Setting up Middleware');
app.use(morgan('dev'));
app.use(express.json());
app.use(express.urlencoded({
extended: true
}));
app.use(nppl);
app.use(npts);
app.use(npdi);
LOG_INFO('Creating 404 status handler');
app.use((_request, response) => {
response.status(404);
response.json({
app: 'api',
status: 404,
error: 'Route not found'
});
});
LOG_INFO('Creating non-404 status handler');
app.use((error: any, _request: express.Request, response: express.Response, _next: express.NextFunction) => {
const status: number = error.status || 500;
response.status(status);
response.json({
app: 'boss',
status: status,
error: error
});
});
LOG_INFO('Starting server');
app.listen(config.http.port, () => {
LOG_SUCCESS(`Server started on port ${config.http.port}`);
});

View File

@@ -1,12 +1,10 @@
const path = require('path');
const fs = require('fs-extra');
const express = require('express');
const subdomain = require('express-subdomain');
import path from 'node:path';
import fs from 'fs-extra';
import express from 'express';
import subdomain from 'express-subdomain';
// Router to handle the subdomain restriction
const npdi = express.Router();
// Setup routes
npdi.get('/p01/data/1/:titleHash/:dataID/:fileHash', (request, response) => {
const { titleHash, fileHash } = request.params;
const contentPath = path.normalize(`${__dirname}/../../cdn/content/encrypted/${titleHash}/${fileHash}`);
@@ -22,10 +20,8 @@ npdi.get('/p01/data/1/:titleHash/:dataID/:fileHash', (request, response) => {
}
});
// Main router for endpoints
const router = express.Router();
// Create subdomain
router.use(subdomain('npdi.cdn', npdi));
module.exports = router;
export default router;

View File

@@ -1,28 +0,0 @@
const path = require('path');
const fs = require('fs-extra');
const express = require('express');
const subdomain = require('express-subdomain');
// Router to handle the subdomain restriction
const npts = express.Router();
// Setup routes
npts.get('/p01/policylist/1/1/:region', (request, response) => {
//const { region } = request.params;
const policylistPath = path.normalize(`${__dirname}/../../cdn/policylist/policylist.xml`);
if (fs.existsSync(policylistPath)) {
response.set('Content-Type', 'application/xml; charset=utf-8');
response.sendFile(policylistPath);
} else {
response.sendStatus(404);
}
});
// Main router for endpoints
const router = express.Router();
// Create subdomain
router.use(subdomain('nppl.app', npts));
module.exports = router;

23
src/services/nppl.ts Normal file
View File

@@ -0,0 +1,23 @@
import path from 'node:path';
import fs from 'fs-extra';
import express from 'express';
import subdomain from 'express-subdomain';
const nppl = express.Router();
nppl.get('/p01/policylist/1/1/:region', (_request, response) => {
const policylistPath = path.normalize(`${__dirname}/../../cdn/policylist/policylist.xml`);
if (fs.existsSync(policylistPath)) {
response.set('Content-Type', 'application/xml; charset=utf-8');
response.sendFile(policylistPath);
} else {
response.sendStatus(404);
}
});
const router = express.Router();
router.use(subdomain('nppl.app', nppl));
export default router;

View File

@@ -1,12 +1,10 @@
const path = require('path');
const fs = require('fs-extra');
const express = require('express');
const subdomain = require('express-subdomain');
import path from 'node:path';
import fs from 'fs-extra';
import express from 'express';
import subdomain from 'express-subdomain';
// Router to handle the subdomain restriction
const npts = express.Router();
// Setup routes
npts.get('/p01/tasksheet/:id/:hash/:fileName', (request, response) => {
const { id, hash, fileName } = request.params;
const tasksheetPath = path.normalize(`${__dirname}/../../cdn/tasksheet/${id}/${hash}/${fileName}`);
@@ -31,10 +29,8 @@ npts.get('/p01/tasksheet/:id/:hash/:subfolder/:fileName', (request, response) =>
}
});
// Main router for endpoints
const router = express.Router();
// Create subdomain
router.use(subdomain('npts.app', npts));
module.exports = router;
export default router;

View File

@@ -0,0 +1,14 @@
export interface Config {
http: {
port: number;
};
crypto: {
wup: {
aes_key: Buffer;
hmac_key: Buffer;
};
ctr: {
aes_key: Buffer;
};
};
}

16
src/types/express-subdomain.d.ts vendored Normal file
View File

@@ -0,0 +1,16 @@
// * Credit to https://github.com/bmullan91/express-subdomain/pull/61 for the types!
declare module 'express-subdomain'{
import type { Request, Response, Router } from 'express';
/**
* @description The subdomain function.
* @param subdomain The subdomain to listen on.
* @param fn The listener function, takes a response and request.
* @returns A function call to the value passed as FN, or void (the next function).
*/
export default function subdomain(
subdomain: string,
fn: Router | ((req: Request, res: Response) => void | any)
): (req: Request, res: Response, next: () => void) => void | typeof fn;
}

5
src/util.ts Normal file
View File

@@ -0,0 +1,5 @@
import crypto from 'node:crypto';
export function md5(input: crypto.BinaryLike): Buffer {
return crypto.createHash('md5').update(input).digest();
}