Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/main/kotlin/dev/typetype/server/Application.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import dev.typetype.server.services.InternalHealthService
import dev.typetype.server.services.OidcAuthService
import dev.typetype.server.services.OidcConfigLoader
import dev.typetype.server.services.OkHttpYoutubeRemoteBrowserClient
import dev.typetype.server.services.SecretConfigReader
import dev.typetype.server.services.UserAdminService
import dev.typetype.server.services.YoutubeRemoteBrowserConfig
import dev.typetype.server.services.YoutubeRemoteBrowserService
Expand Down Expand Up @@ -47,7 +48,7 @@ fun Application.module() {
val activeSessionService = ActiveSessionService(adminSettingsService)
val restoreService = PipePipeBackupImporterService()
val downloaderServiceUrl = System.getenv("DOWNLOADER_SERVICE_URL") ?: "http://typetype-downloader:18093"
val youtubeSessionEncryptionKey = System.getenv("YOUTUBE_SESSION_ENCRYPTION_KEY")
val youtubeSessionEncryptionKey = SecretConfigReader.read("YOUTUBE_SESSION_ENCRYPTION_KEY")
val cacheUrl = System.getenv("DRAGONFLY_URL") ?: "redis://localhost:6379"
val cache = DragonflyService(cacheUrl)
val subtitleServiceUrl = System.getenv("SUBTITLE_SERVICE_URL") ?: "http://typetype-token:8081"
Expand Down
28 changes: 28 additions & 0 deletions src/main/kotlin/dev/typetype/server/services/SecretConfigReader.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package dev.typetype.server.services

import java.nio.file.Files
import java.nio.file.Path

object SecretConfigReader {
fun read(name: String): String? =
read(name, System::getenv)

internal fun read(name: String, env: (String) -> String?): String? =
envText(env(name)) ?: envText(env("${name}_FILE"))?.let(::readFile)

private fun readFile(path: String): String? =
runCatching { Files.readString(Path.of(path)).trim() }
.getOrNull()
?.takeIf { it.isNotEmpty() }

private fun envText(value: String?): String? =
value?.trim()?.takeIf { it.isNotEmpty() && it !in PLACEHOLDER_VALUES }

private val PLACEHOLDER_VALUES = setOf(
"SET_ME_SHARED_SECRET",
"SET_ME_YOUTUBE_REMOTE_LOGIN_INTERNAL_TOKEN",
"SET_ME_YOUTUBE_SESSION_ENCRYPTION_KEY",
"replace-with-shared-internal-token",
"replace-with-at-least-32-random-characters",
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ data class YoutubeRemoteBrowserConfig(
YoutubeRemoteBrowserConfig(
serviceUrl = envText("YOUTUBE_REMOTE_LOGIN_SERVICE_URL") ?: tokenServiceUrl,
callbackBaseUrl = envText("YOUTUBE_REMOTE_LOGIN_CALLBACK_BASE_URL") ?: "http://localhost:8080",
internalToken = envText("YOUTUBE_REMOTE_LOGIN_INTERNAL_TOKEN"),
internalToken = SecretConfigReader.read("YOUTUBE_REMOTE_LOGIN_INTERNAL_TOKEN"),
ttlMs = envLong("YOUTUBE_REMOTE_LOGIN_TTL_MS", DEFAULT_TTL_MS).coerceIn(60_000L, 10 * 60_000L),
maxGlobalSessions = envInt("YOUTUBE_REMOTE_LOGIN_MAX_SESSIONS", DEFAULT_MAX_GLOBAL_SESSIONS).coerceIn(1, 8),
maxFrameBytes = envInt("YOUTUBE_REMOTE_LOGIN_MAX_FRAME_BYTES", DEFAULT_MAX_FRAME_BYTES).coerceIn(64 * 1024, 2 * 1024 * 1024),
Expand Down
49 changes: 49 additions & 0 deletions src/test/kotlin/dev/typetype/server/SecretConfigReaderTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package dev.typetype.server

import dev.typetype.server.services.SecretConfigReader
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Test
import java.nio.file.Files

class SecretConfigReaderTest {
@Test
fun `read prefers direct environment value`() {
val env = mapOf(
"TYPETYPE_TEST_SECRET" to " direct ",
"TYPETYPE_TEST_SECRET_FILE" to "/missing",
)
assertEquals("direct", SecretConfigReader.read("TYPETYPE_TEST_SECRET", env::get))
}

@Test
fun `read loads file environment value`() {
val file = Files.createTempFile("typetype-secret-", ".txt")
Files.writeString(file, " file-secret \n")

val env = mapOf("TYPETYPE_TEST_SECRET_FILE" to file.toString())
assertEquals("file-secret", SecretConfigReader.read("TYPETYPE_TEST_SECRET", env::get))

Files.deleteIfExists(file)
}

@Test
fun `read ignores placeholder environment value`() {
val file = Files.createTempFile("typetype-secret-", ".txt")
Files.writeString(file, "generated-secret\n")
val env = mapOf(
"TYPETYPE_TEST_SECRET" to "SET_ME_YOUTUBE_REMOTE_LOGIN_INTERNAL_TOKEN",
"TYPETYPE_TEST_SECRET_FILE" to file.toString(),
)

assertEquals("generated-secret", SecretConfigReader.read("TYPETYPE_TEST_SECRET", env::get))

Files.deleteIfExists(file)
}

@Test
fun `read ignores missing secret file`() {
val env = mapOf("TYPETYPE_TEST_SECRET_FILE" to "/missing/secret")
assertNull(SecretConfigReader.read("TYPETYPE_TEST_SECRET", env::get))
}
}