From ff18621b2dbe2b0ed7e60b03b0dc8a9261d468aa Mon Sep 17 00:00:00 2001 From: Priveetee Date: Fri, 19 Jun 2026 19:53:10 +0200 Subject: [PATCH] fix: support file-backed remote login secrets --- .../kotlin/dev/typetype/server/Application.kt | 3 +- .../server/services/SecretConfigReader.kt | 28 +++++++++++ .../services/YoutubeRemoteBrowserConfig.kt | 2 +- .../typetype/server/SecretConfigReaderTest.kt | 49 +++++++++++++++++++ 4 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 src/main/kotlin/dev/typetype/server/services/SecretConfigReader.kt create mode 100644 src/test/kotlin/dev/typetype/server/SecretConfigReaderTest.kt diff --git a/src/main/kotlin/dev/typetype/server/Application.kt b/src/main/kotlin/dev/typetype/server/Application.kt index 15eb95de..9f394907 100644 --- a/src/main/kotlin/dev/typetype/server/Application.kt +++ b/src/main/kotlin/dev/typetype/server/Application.kt @@ -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 @@ -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" diff --git a/src/main/kotlin/dev/typetype/server/services/SecretConfigReader.kt b/src/main/kotlin/dev/typetype/server/services/SecretConfigReader.kt new file mode 100644 index 00000000..c13f2807 --- /dev/null +++ b/src/main/kotlin/dev/typetype/server/services/SecretConfigReader.kt @@ -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", + ) +} diff --git a/src/main/kotlin/dev/typetype/server/services/YoutubeRemoteBrowserConfig.kt b/src/main/kotlin/dev/typetype/server/services/YoutubeRemoteBrowserConfig.kt index 190c9210..a7722126 100644 --- a/src/main/kotlin/dev/typetype/server/services/YoutubeRemoteBrowserConfig.kt +++ b/src/main/kotlin/dev/typetype/server/services/YoutubeRemoteBrowserConfig.kt @@ -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), diff --git a/src/test/kotlin/dev/typetype/server/SecretConfigReaderTest.kt b/src/test/kotlin/dev/typetype/server/SecretConfigReaderTest.kt new file mode 100644 index 00000000..98c1fe41 --- /dev/null +++ b/src/test/kotlin/dev/typetype/server/SecretConfigReaderTest.kt @@ -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)) + } +}