1
- import http , { IncomingMessage , ServerResponse , Server } from "node:http" ;
1
+ import { IncomingMessage , ServerResponse } from "node:http" ;
2
+ import https , { Server } from "node:https" ;
2
3
import { ApplicationContext } from "@spt/context/ApplicationContext" ;
3
4
import { ContextVariableType } from "@spt/context/ContextVariableType" ;
4
5
import { HttpServerHelper } from "@spt/helpers/HttpServerHelper" ;
@@ -9,12 +10,18 @@ import { ConfigServer } from "@spt/servers/ConfigServer";
9
10
import { WebSocketServer } from "@spt/servers/WebSocketServer" ;
10
11
import { IHttpListener } from "@spt/servers/http/IHttpListener" ;
11
12
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" ;
12
16
import { inject , injectAll , injectable } from "tsyringe" ;
13
17
14
18
@injectable ( )
15
19
export class HttpServer {
16
20
protected httpConfig : IHttpConfig ;
17
21
protected started = false ;
22
+ protected certPath : string ;
23
+ protected keyPath : string ;
24
+ protected fileSystem : FileSystem ;
18
25
19
26
constructor (
20
27
@inject ( "PrimaryLogger" ) protected logger : ILogger ,
@@ -24,33 +31,35 @@ export class HttpServer {
24
31
@inject ( "ConfigServer" ) protected configServer : ConfigServer ,
25
32
@inject ( "ApplicationContext" ) protected applicationContext : ApplicationContext ,
26
33
@inject ( "WebSocketServer" ) protected webSocketServer : WebSocketServer ,
34
+ @inject ( "FileSystem" ) fileSystem : FileSystem , // new dependency
27
35
) {
28
36
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" ;
29
40
}
30
41
31
42
/**
32
43
* Handle server loading event
33
44
*/
34
- public load ( ) : void {
45
+ public async load ( ) : Promise < void > {
46
+ // changed to async
35
47
this . started = false ;
36
48
37
- /* create server */
38
- const httpServer : Server = http . createServer ( ) ;
49
+ const httpsServer : Server = await this . createHttpsServer ( ) ;
39
50
40
- httpServer . on ( "request" , async ( req , res ) => {
51
+ httpsServer . on ( "request" , async ( req : IncomingMessage , res : ServerResponse ) => {
41
52
await this . handleRequest ( req , res ) ;
42
53
} ) ;
43
54
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 , ( ) => {
46
56
this . started = true ;
47
57
this . logger . success (
48
58
this . localisationService . getText ( "started_webserver_success" , this . httpServerHelper . getBackendUrl ( ) ) ,
49
59
) ;
50
60
} ) ;
51
61
52
- httpServer . on ( "error" , ( e : any ) => {
53
- /* server is already running or program using privileged port without root */
62
+ httpsServer . on ( "error" , ( e : any ) => {
54
63
if ( process . platform === "linux" && ! ( process . getuid && process . getuid ( ) === 0 ) && e . port < 1024 ) {
55
64
this . logger . error ( this . localisationService . getText ( "linux_use_priviledged_port_non_root" ) ) ;
56
65
} else {
@@ -59,8 +68,56 @@ export class HttpServer {
59
68
}
60
69
} ) ;
61
70
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 } ;
64
121
}
65
122
66
123
protected async handleRequest ( req : IncomingMessage , resp : ServerResponse ) : Promise < void > {
0 commit comments