Skip to content

Commit

Permalink
Merge branch '3.11.0-dev' into safer-profile-saving
Browse files Browse the repository at this point in the history
  • Loading branch information
chompDev authored Feb 19, 2025
2 parents 08d49b3 + 1bd00e6 commit bb208af
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 19 deletions.
5 changes: 3 additions & 2 deletions project/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,15 @@
"logform": "2.7.0",
"mongoid-js": "1.3.0",
"reflect-metadata": "0.2.2",
"selfsigned": "^2.4.1",
"semver": "7.6.3",
"source-map-support": "0.5.21",
"string-similarity-js": "2.1.4",
"tsyringe": "4.8.0",
"typescript": "5.7.3",
"winston": "3.17.0",
"winston-daily-rotate-file": "5.0.0",
"ws": "8.18.0",
"typescript": "5.7.3"
"ws": "8.18.0"
},
"devDependencies": {
"@biomejs/biome": "1.9.4",
Expand Down
2 changes: 1 addition & 1 deletion project/src/callbacks/HttpCallbacks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export class HttpCallbacks implements OnLoad {
constructor(@inject("HttpServer") protected httpServer: HttpServer) {}

public async onLoad(): Promise<void> {
this.httpServer.load();
await this.httpServer.load();
}

public getRoute(): string {
Expand Down
4 changes: 2 additions & 2 deletions project/src/helpers/HttpServerHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,12 @@ export class HttpServerHelper {
* @returns URI
*/
public getBackendUrl(): string {
return `http://${this.buildUrl()}`;
return `https://${this.buildUrl()}`;
}

/** Get websocket url + port */
public getWebsocketUrl(): string {
return `ws://${this.buildUrl()}`;
return `wss://${this.buildUrl()}`;
}

public sendTextJson(resp: any, output: any): void {
Expand Down
79 changes: 68 additions & 11 deletions project/src/servers/HttpServer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import http, { IncomingMessage, ServerResponse, Server } from "node:http";
import { IncomingMessage, ServerResponse } from "node:http";
import https, { Server } from "node:https";
import { ApplicationContext } from "@spt/context/ApplicationContext";
import { ContextVariableType } from "@spt/context/ContextVariableType";
import { HttpServerHelper } from "@spt/helpers/HttpServerHelper";
Expand All @@ -9,12 +10,18 @@ import { ConfigServer } from "@spt/servers/ConfigServer";
import { WebSocketServer } from "@spt/servers/WebSocketServer";
import { IHttpListener } from "@spt/servers/http/IHttpListener";
import { LocalisationService } from "@spt/services/LocalisationService";
import { FileSystem } from "@spt/utils/FileSystem";
import { Timer } from "@spt/utils/Timer";
import { generate } from "selfsigned";
import { inject, injectAll, injectable } from "tsyringe";

@injectable()
export class HttpServer {
protected httpConfig: IHttpConfig;
protected started = false;
protected certPath: string;
protected keyPath: string;
protected fileSystem: FileSystem;

constructor(
@inject("PrimaryLogger") protected logger: ILogger,
Expand All @@ -24,33 +31,35 @@ export class HttpServer {
@inject("ConfigServer") protected configServer: ConfigServer,
@inject("ApplicationContext") protected applicationContext: ApplicationContext,
@inject("WebSocketServer") protected webSocketServer: WebSocketServer,
@inject("FileSystem") fileSystem: FileSystem, // new dependency
) {
this.httpConfig = this.configServer.getConfig(ConfigTypes.HTTP);
this.fileSystem = fileSystem;
this.certPath = "./user/certs/localhost.crt";
this.keyPath = "./user/certs/localhost.key";
}

/**
* Handle server loading event
*/
public load(): void {
public async load(): Promise<void> {
// changed to async
this.started = false;

/* create server */
const httpServer: Server = http.createServer();
const httpsServer: Server = await this.createHttpsServer();

httpServer.on("request", async (req, res) => {
httpsServer.on("request", async (req: IncomingMessage, res: ServerResponse) => {
await this.handleRequest(req, res);
});

/* Config server to listen on a port */
httpServer.listen(this.httpConfig.port, this.httpConfig.ip, () => {
httpsServer.listen(this.httpConfig.port, this.httpConfig.ip, () => {
this.started = true;
this.logger.success(
this.localisationService.getText("started_webserver_success", this.httpServerHelper.getBackendUrl()),
);
});

httpServer.on("error", (e: any) => {
/* server is already running or program using privileged port without root */
httpsServer.on("error", (e: any) => {
if (process.platform === "linux" && !(process.getuid && process.getuid() === 0) && e.port < 1024) {
this.logger.error(this.localisationService.getText("linux_use_priviledged_port_non_root"));
} else {
Expand All @@ -59,8 +68,56 @@ export class HttpServer {
}
});

// Setting up websocket
this.webSocketServer.setupWebSocket(httpServer);
// Setting up WebSocket using our https server
this.webSocketServer.setupWebSocket(httpsServer);
}

/**
* Creates an HTTPS server using the stored certificate and key.
*/
protected async createHttpsServer(): Promise<Server> {
let credentials: { cert: string; key: string };
try {
credentials = {
cert: await this.fileSystem.read(this.certPath),
key: await this.fileSystem.read(this.keyPath),
};
} catch (err: unknown) {
const timer = new Timer();
credentials = await this.generateSelfSignedCertificate();
this.logger.debug(`Generating self-signed SSL certificate took ${timer.getTime("sec")}s`);
}
return https.createServer(credentials);
}

/**
* Generates a self-signed certificate and returns an object with the cert and key.
*/
protected async generateSelfSignedCertificate(): Promise<{ cert: string; key: string }> {
const attrs = [{ name: "commonName", value: "localhost" }];
const pems = generate(attrs, {
keySize: 4096,
days: 3653, // Ten years
algorithm: "sha256",
extensions: [
{
name: "subjectAltName",
altNames: [
{ type: 2, value: "localhost" }, // DNS
{ type: 7, ip: "127.0.0.1" }, // Resolving IP
],
},
],
});

try {
await this.fileSystem.write(this.certPath, pems.cert);
await this.fileSystem.write(this.keyPath, pems.private);
} catch (err: unknown) {
this.logger.error(`There was an error writing the certificate or key to disk: ${err}`);
}

return { cert: pems.cert, key: pems.private };
}

protected async handleRequest(req: IncomingMessage, resp: ServerResponse): Promise<void> {
Expand Down
5 changes: 3 additions & 2 deletions project/src/servers/WebSocketServer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import http, { IncomingMessage } from "node:http";
import { IncomingMessage } from "node:http";
import https from "node:https";
import { ProgramStatics } from "@spt/ProgramStatics";
import { HttpServerHelper } from "@spt/helpers/HttpServerHelper";
import type { ILogger } from "@spt/models/spt/utils/ILogger";
Expand Down Expand Up @@ -27,7 +28,7 @@ export class WebSocketServer {
return this.webSocketServer;
}

public setupWebSocket(httpServer: http.Server): void {
public setupWebSocket(httpServer: https.Server): void {
this.webSocketServer = new Server({ server: httpServer, WebSocket: SPTWebSocket });

this.webSocketServer.addListener("listening", () => {
Expand Down
2 changes: 1 addition & 1 deletion project/src/utils/DatabaseImporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { HashUtil } from "@spt/utils/HashUtil";
import { ImporterUtil } from "@spt/utils/ImporterUtil";
import { JsonUtil } from "@spt/utils/JsonUtil";
import { inject, injectable } from "tsyringe";
import { Timer } from "./Timer";
import { Timer } from "@spt/utils/Timer";

@injectable()
export class DatabaseImporter implements OnLoad {
Expand Down

0 comments on commit bb208af

Please sign in to comment.