Skip to content

Commit 1bd00e6

Browse files
refringeCWXDEV
andauthored
HTTPS Support (#1118)
This is the beginning of HTTPS support. Creates a self-signed certificate for use with the node:https module. --------- Co-authored-by: CWX <cwxdev@outlook.com>
1 parent c6eade7 commit 1bd00e6

File tree

6 files changed

+78
-19
lines changed

6 files changed

+78
-19
lines changed

project/package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,15 @@
4343
"logform": "2.7.0",
4444
"mongoid-js": "1.3.0",
4545
"reflect-metadata": "0.2.2",
46+
"selfsigned": "^2.4.1",
4647
"semver": "7.6.3",
4748
"source-map-support": "0.5.21",
4849
"string-similarity-js": "2.1.4",
4950
"tsyringe": "4.8.0",
51+
"typescript": "5.7.3",
5052
"winston": "3.17.0",
5153
"winston-daily-rotate-file": "5.0.0",
52-
"ws": "8.18.0",
53-
"typescript": "5.7.3"
54+
"ws": "8.18.0"
5455
},
5556
"devDependencies": {
5657
"@biomejs/biome": "1.9.4",

project/src/callbacks/HttpCallbacks.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export class HttpCallbacks implements OnLoad {
77
constructor(@inject("HttpServer") protected httpServer: HttpServer) {}
88

99
public async onLoad(): Promise<void> {
10-
this.httpServer.load();
10+
await this.httpServer.load();
1111
}
1212

1313
public getRoute(): string {

project/src/helpers/HttpServerHelper.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,12 @@ export class HttpServerHelper {
4040
* @returns URI
4141
*/
4242
public getBackendUrl(): string {
43-
return `http://${this.buildUrl()}`;
43+
return `https://${this.buildUrl()}`;
4444
}
4545

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

5151
public sendTextJson(resp: any, output: any): void {

project/src/servers/HttpServer.ts

+68-11
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import http, { IncomingMessage, ServerResponse, Server } from "node:http";
1+
import { IncomingMessage, ServerResponse } from "node:http";
2+
import https, { Server } from "node:https";
23
import { ApplicationContext } from "@spt/context/ApplicationContext";
34
import { ContextVariableType } from "@spt/context/ContextVariableType";
45
import { HttpServerHelper } from "@spt/helpers/HttpServerHelper";
@@ -9,12 +10,18 @@ import { ConfigServer } from "@spt/servers/ConfigServer";
910
import { WebSocketServer } from "@spt/servers/WebSocketServer";
1011
import { IHttpListener } from "@spt/servers/http/IHttpListener";
1112
import { LocalisationService } from "@spt/services/LocalisationService";
13+
import { FileSystem } from "@spt/utils/FileSystem";
14+
import { Timer } from "@spt/utils/Timer";
15+
import { generate } from "selfsigned";
1216
import { inject, injectAll, injectable } from "tsyringe";
1317

1418
@injectable()
1519
export class HttpServer {
1620
protected httpConfig: IHttpConfig;
1721
protected started = false;
22+
protected certPath: string;
23+
protected keyPath: string;
24+
protected fileSystem: FileSystem;
1825

1926
constructor(
2027
@inject("PrimaryLogger") protected logger: ILogger,
@@ -24,33 +31,35 @@ export class HttpServer {
2431
@inject("ConfigServer") protected configServer: ConfigServer,
2532
@inject("ApplicationContext") protected applicationContext: ApplicationContext,
2633
@inject("WebSocketServer") protected webSocketServer: WebSocketServer,
34+
@inject("FileSystem") fileSystem: FileSystem, // new dependency
2735
) {
2836
this.httpConfig = this.configServer.getConfig(ConfigTypes.HTTP);
37+
this.fileSystem = fileSystem;
38+
this.certPath = "./user/certs/localhost.crt";
39+
this.keyPath = "./user/certs/localhost.key";
2940
}
3041

3142
/**
3243
* Handle server loading event
3344
*/
34-
public load(): void {
45+
public async load(): Promise<void> {
46+
// changed to async
3547
this.started = false;
3648

37-
/* create server */
38-
const httpServer: Server = http.createServer();
49+
const httpsServer: Server = await this.createHttpsServer();
3950

40-
httpServer.on("request", async (req, res) => {
51+
httpsServer.on("request", async (req: IncomingMessage, res: ServerResponse) => {
4152
await this.handleRequest(req, res);
4253
});
4354

44-
/* Config server to listen on a port */
45-
httpServer.listen(this.httpConfig.port, this.httpConfig.ip, () => {
55+
httpsServer.listen(this.httpConfig.port, this.httpConfig.ip, () => {
4656
this.started = true;
4757
this.logger.success(
4858
this.localisationService.getText("started_webserver_success", this.httpServerHelper.getBackendUrl()),
4959
);
5060
});
5161

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

62-
// Setting up websocket
63-
this.webSocketServer.setupWebSocket(httpServer);
71+
// Setting up WebSocket using our https server
72+
this.webSocketServer.setupWebSocket(httpsServer);
73+
}
74+
75+
/**
76+
* Creates an HTTPS server using the stored certificate and key.
77+
*/
78+
protected async createHttpsServer(): Promise<Server> {
79+
let credentials: { cert: string; key: string };
80+
try {
81+
credentials = {
82+
cert: await this.fileSystem.read(this.certPath),
83+
key: await this.fileSystem.read(this.keyPath),
84+
};
85+
} catch (err: unknown) {
86+
const timer = new Timer();
87+
credentials = await this.generateSelfSignedCertificate();
88+
this.logger.debug(`Generating self-signed SSL certificate took ${timer.getTime("sec")}s`);
89+
}
90+
return https.createServer(credentials);
91+
}
92+
93+
/**
94+
* Generates a self-signed certificate and returns an object with the cert and key.
95+
*/
96+
protected async generateSelfSignedCertificate(): Promise<{ cert: string; key: string }> {
97+
const attrs = [{ name: "commonName", value: "localhost" }];
98+
const pems = generate(attrs, {
99+
keySize: 4096,
100+
days: 3653, // Ten years
101+
algorithm: "sha256",
102+
extensions: [
103+
{
104+
name: "subjectAltName",
105+
altNames: [
106+
{ type: 2, value: "localhost" }, // DNS
107+
{ type: 7, ip: "127.0.0.1" }, // Resolving IP
108+
],
109+
},
110+
],
111+
});
112+
113+
try {
114+
await this.fileSystem.write(this.certPath, pems.cert);
115+
await this.fileSystem.write(this.keyPath, pems.private);
116+
} catch (err: unknown) {
117+
this.logger.error(`There was an error writing the certificate or key to disk: ${err}`);
118+
}
119+
120+
return { cert: pems.cert, key: pems.private };
64121
}
65122

66123
protected async handleRequest(req: IncomingMessage, resp: ServerResponse): Promise<void> {

project/src/servers/WebSocketServer.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import http, { IncomingMessage } from "node:http";
1+
import { IncomingMessage } from "node:http";
2+
import https from "node:https";
23
import { ProgramStatics } from "@spt/ProgramStatics";
34
import { HttpServerHelper } from "@spt/helpers/HttpServerHelper";
45
import type { ILogger } from "@spt/models/spt/utils/ILogger";
@@ -27,7 +28,7 @@ export class WebSocketServer {
2728
return this.webSocketServer;
2829
}
2930

30-
public setupWebSocket(httpServer: http.Server): void {
31+
public setupWebSocket(httpServer: https.Server): void {
3132
this.webSocketServer = new Server({ server: httpServer, WebSocket: SPTWebSocket });
3233

3334
this.webSocketServer.addListener("listening", () => {

project/src/utils/DatabaseImporter.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { HashUtil } from "@spt/utils/HashUtil";
1515
import { ImporterUtil } from "@spt/utils/ImporterUtil";
1616
import { JsonUtil } from "@spt/utils/JsonUtil";
1717
import { inject, injectable } from "tsyringe";
18-
import { Timer } from "./Timer";
18+
import { Timer } from "@spt/utils/Timer";
1919

2020
@injectable()
2121
export class DatabaseImporter implements OnLoad {

0 commit comments

Comments
 (0)