@@ -9,8 +9,8 @@ import com.sakethh.linkora.presentation.routing.configureRouting
99import com.sakethh.linkora.presentation.routing.websocket.configureEventsWebSocket
1010import com.sakethh.linkora.utils.SysEnvKey
1111import com.sakethh.linkora.utils.useSysEnvValues
12- import io.ktor.http.HttpHeaders
13- import io.ktor.http.HttpMethod
12+ import io.ktor.http.*
13+ import io.ktor.network.tls.certificates.*
1414import io.ktor.server.application.*
1515import io.ktor.server.engine.*
1616import io.ktor.server.netty.*
@@ -19,18 +19,42 @@ import io.ktor.server.websocket.*
1919import kotlinx.serialization.encodeToString
2020import kotlinx.serialization.json.Json
2121import java.awt.Desktop
22+ import java.io.FileInputStream
23+ import java.net.Inet4Address
2224import java.net.InetAddress
23- import java.net.URI
2425import java.nio.file.Files
2526import java.nio.file.Paths
2627import java.nio.file.StandardOpenOption
28+ import java.security.KeyStore
29+ import kotlin.io.path.exists
2730import kotlin.time.Duration.Companion.seconds
2831
2932fun main () {
3033 val serverConfig = ServerConfiguration .readConfig()
34+ val serverKeyStore = ServerConfiguration .createOrLoadServerKeystore(
35+ serverConfig
36+ )
37+ require(serverConfig.keyStorePassword != null && serverConfig.keyStorePassword.isNotBlank()) {
38+ " keyStorePassword value must be set in ServerConfig."
39+ }
3140 embeddedServer(
32- Netty , port = serverConfig.serverPort, host = serverConfig.hostAddress, module = Application ::module
33- ).start(wait = true )
41+ factory = Netty , configure = {
42+ sslConnector(builder = {
43+ this .port = serverConfig.httpsPort
44+ this .host = Inet4Address .getLocalHost().hostAddress
45+ enabledProtocols = listOf (" TLSv1.3" , " TLSv1.2" )
46+ }, keyStore = serverKeyStore, keyAlias = Constants .KEY_STORE_ALIAS , keyStorePassword = {
47+ serverConfig.keyStorePassword.toCharArray()
48+ }, privateKeyPassword = {
49+ serverConfig.keyStorePassword.toCharArray()
50+ })
51+
52+ // for http connections
53+ connector {
54+ this .port = serverConfig.httpPort
55+ this .host = serverConfig.hostAddress
56+ }
57+ }, module = Application ::module).start(wait = true )
3458}
3559
3660object ServerConfiguration {
@@ -73,7 +97,8 @@ object ServerConfiguration {
7397 databaseUrl = if (dataBaseUrl.endsWith(" /linkora" ).not ()) " $dataBaseUrl /linkora" else dataBaseUrl,
7498 databaseUser = dataBaseUserName,
7599 databasePassword = dataBasePassword,
76- serverAuthToken = serverAuthToken
100+ serverAuthToken = serverAuthToken,
101+ keyStorePassword = ServerConfig .generateAToken()
77102 )
78103 val jsonConfigString = json.encodeToString(serverConfig)
79104 println (jsonConfigString)
@@ -85,6 +110,14 @@ object ServerConfiguration {
85110 }
86111 }
87112
113+ fun createConfig (serverConfig : ServerConfig ) {
114+ require(serverConfig.keyStorePassword != null && serverConfig.keyStorePassword.isNotBlank()) {
115+ " keyStorePassword value must be set in ServerConfig."
116+ }
117+ val jsonConfigString = json.encodeToString(serverConfig)
118+ Files .writeString(configFilePath, jsonConfigString, StandardOpenOption .TRUNCATE_EXISTING )
119+ }
120+
88121 fun readConfig (): ServerConfig {
89122 return if (useSysEnvValues()) {
90123 ServerConfig (
@@ -97,19 +130,34 @@ object ServerConfiguration {
97130 } catch (_: Exception ) {
98131 InetAddress .getLocalHost().hostAddress
99132 },
100- serverPort = try {
133+ httpPort = try {
101134 System .getenv(SysEnvKey .LINKORA_SERVER_PORT .name).toInt()
102135 } catch (_: Exception ) {
103136 45454
104137 },
105- serverAuthToken = System .getenv(SysEnvKey .LINKORA_SERVER_AUTH_TOKEN .name)
138+ httpsPort = try {
139+ System .getenv(SysEnvKey .LINKORA_HTTPS_PORT .name).toInt()
140+ } catch (_: Exception ) {
141+ 54545
142+ },
143+ serverAuthToken = System .getenv(SysEnvKey .LINKORA_SERVER_AUTH_TOKEN .name),
144+ keyStorePassword = System .getenv(SysEnvKey .LINKORA_KEY_STORE_PASSWORD .name)
106145 )
107146 } else {
108147 createConfig(forceWrite = false )
109148 Files .readString(configFilePath).let {
110149 try {
111150 json.decodeFromString<ServerConfig >(it).let {
112- it.copy(databaseUrl = " jdbc:" + it.databaseUrl)
151+ val newKeyPassword = ServerConfig .generateAToken()
152+ it.run {
153+ if (keyStorePassword == null ) {
154+ createConfig(serverConfig = it.copy(keyStorePassword = newKeyPassword))
155+ }
156+ copy(
157+ databaseUrl = " jdbc:" + it.databaseUrl,
158+ keyStorePassword = keyStorePassword ? : newKeyPassword
159+ )
160+ }
113161 }
114162 } catch (_: Exception ) {
115163 println (" It seems you’ve manipulated `linkoraConfig.json` and messed things up a bit. No problemo, we’ll restart the configuration process to make sure things go smoothly." )
@@ -119,6 +167,37 @@ object ServerConfiguration {
119167 }
120168 }
121169 }
170+
171+ fun createOrLoadServerKeystore (serverConfig : ServerConfig , forceCreate : Boolean = false): KeyStore {
172+ require(serverConfig.keyStorePassword != null && serverConfig.keyStorePassword.isNotBlank()) {
173+ " keyStorePassword value must be set in ServerConfig."
174+ }
175+ val keyStore = buildKeyStore {
176+ this .certificate(
177+ alias = Constants .KEY_STORE_ALIAS , block = {
178+ this .password = serverConfig.keyStorePassword
179+ this .domains = listOf (Inet4Address .getLocalHost().hostAddress)
180+ this .ipAddresses = listOf (Inet4Address .getLocalHost())
181+ })
182+ }
183+ return jarDir.resolve(" linkoraServerCert.jks" ).run {
184+ if (forceCreate.not () && exists()) {
185+ FileInputStream (toFile()).use { keyStoreFile ->
186+ KeyStore .getInstance(KeyStore .getDefaultType()).also {
187+ println (" Loading existing keystore..." )
188+ it.load(keyStoreFile, serverConfig.keyStorePassword.toCharArray())
189+ }
190+ }
191+ } else {
192+ keyStore.also {
193+ println (" Creating new keystore..." )
194+ it.saveToFile(
195+ output = toFile(), password = serverConfig.keyStorePassword
196+ )
197+ }
198+ }
199+ }
200+ }
122201}
123202
124203fun Application.module () {
@@ -142,8 +221,7 @@ fun Application.module() {
142221 maxFrameSize = Long .MAX_VALUE
143222 }
144223 configureEventsWebSocket()
145- val serverConfiguredPage =
146- " http://" + serverConfig.hostAddress + " :" + serverConfig.serverPort + " /" + Route .Sync .SERVER_IS_CONFIGURED .name
224+ " http://" + serverConfig.hostAddress + " :" + serverConfig.httpPort + " /" + Route .Sync .SERVER_IS_CONFIGURED .name
147225 if (useSysEnvValues().not () && Desktop .isDesktopSupported() && Desktop .getDesktop()
148226 .isSupported(Desktop .Action .BROWSE )
149227 ) {
0 commit comments