From 3ce594eca66299098d817529b3d0824e33695b04 Mon Sep 17 00:00:00 2001 From: ariskemper Date: Mon, 23 Mar 2026 10:43:48 +0100 Subject: [PATCH 1/2] fix: add TLS and authentication support to Redis connections - Support rediss:// URLs for automatic TLS detection - Add tls, password, username options to RedisClientOptions - Support REDIS_PASSWORD and REDIS_USERNAME environment variables - Log warning when connecting without TLS in production - Add TLS/auth fields to RedisCacheOptions type --- src/proxy/cache/types.ts | 3 +++ src/utils/redis-client.ts | 34 ++++++++++++++++++++++++++++++---- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/proxy/cache/types.ts b/src/proxy/cache/types.ts index ce34ceb5d3..28f4fc68f6 100644 --- a/src/proxy/cache/types.ts +++ b/src/proxy/cache/types.ts @@ -38,6 +38,9 @@ export interface RedisCacheOptions { url: string; prefix?: string; connectTimeout?: number; + tls?: boolean; + password?: string; + username?: string; } export type CacheOptions = diff --git a/src/utils/redis-client.ts b/src/utils/redis-client.ts index ce7742a1a0..7013b416a7 100644 --- a/src/utils/redis-client.ts +++ b/src/utils/redis-client.ts @@ -19,10 +19,13 @@ export interface RedisClient { isOpen?: boolean; } -interface RedisClientOptions { +export interface RedisClientOptions { url?: string; connectTimeout?: number; autoReconnect?: boolean; + tls?: boolean; + password?: string; + username?: string; } let sharedClient: RedisClient | null = null; @@ -64,12 +67,14 @@ export async function getRedisClient(options: RedisClientOptions = {}): Promise< } async function createClient(options: RedisClientOptions): Promise { - let createClientFn: ((opts: { url?: string }) => RedisClient) | undefined; + // deno-lint-ignore no-explicit-any + let createClientFn: ((opts: Record) => RedisClient) | undefined; try { const redisClientModule = "npm:@redis/client@1.5.8"; const mod = await import(redisClientModule); - createClientFn = mod.createClient as (opts: { url?: string }) => RedisClient; + // deno-lint-ignore no-explicit-any + createClientFn = mod.createClient as (opts: Record) => RedisClient; } catch (error) { logger.debug("Failed to load @redis/client module", { error }); throw DEPENDENCY_MISSING.create({ @@ -78,7 +83,28 @@ async function createClient(options: RedisClientOptions): Promise { }); } - const client = createClientFn({ url: options.url ?? getEnv("REDIS_URL") }); + const url = options.url ?? getEnv("REDIS_URL"); + const useTls = options.tls ?? url?.startsWith("rediss://") ?? false; + + if (!useTls && getEnv("NODE_ENV") === "production") { + logger.warn( + "Redis connection without TLS in production. Set REDIS_URL to rediss:// or pass tls: true.", + ); + } + + // deno-lint-ignore no-explicit-any + const clientOpts: Record = { url }; + if (useTls) { + clientOpts.socket = { tls: true }; + } + if (options.password ?? getEnv("REDIS_PASSWORD")) { + clientOpts.password = options.password ?? getEnv("REDIS_PASSWORD"); + } + if (options.username ?? getEnv("REDIS_USERNAME")) { + clientOpts.username = options.username ?? getEnv("REDIS_USERNAME"); + } + + const client = createClientFn(clientOpts); if (typeof client.on === "function") { client.on("error", (err: unknown) => { From fd37520fcb423397180d054fc1d20a6efb3ca0e0 Mon Sep 17 00:00:00 2001 From: ariskemper Date: Mon, 23 Mar 2026 13:02:53 +0100 Subject: [PATCH 2/2] fix: wire TLS/auth options through RedisCache and eliminate duplicate env lookups - RedisCache.ensureConnected() now passes tls, password, username to createClient - Auto-detect TLS from rediss:// URLs in RedisCacheOptions - Store env var lookups in variables to avoid duplicate getEnv() calls --- src/proxy/cache/redis-cache.ts | 18 +++++++++++++++--- src/utils/redis-client.ts | 10 ++++++---- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/proxy/cache/redis-cache.ts b/src/proxy/cache/redis-cache.ts index fe758a8d05..83347397d0 100644 --- a/src/proxy/cache/redis-cache.ts +++ b/src/proxy/cache/redis-cache.ts @@ -16,6 +16,9 @@ export class RedisCache implements TokenCache { private readonly prefix: string; private readonly url: string; private readonly connectTimeout: number; + private readonly tls: boolean; + private readonly password?: string; + private readonly username?: string; private hits = 0; private misses = 0; private connected = false; @@ -24,6 +27,9 @@ export class RedisCache implements TokenCache { this.url = options.url; this.prefix = options.prefix ?? DEFAULT_PREFIX; this.connectTimeout = options.connectTimeout ?? DEFAULT_CONNECT_TIMEOUT_MS; + this.tls = options.tls ?? options.url.startsWith("rediss://"); + this.password = options.password; + this.username = options.username; } private key(k: string): string { @@ -212,18 +218,24 @@ export class RedisCache implements TokenCache { return withSpan("cache.redis.connect", async () => { if (this.connected && this.client) return; - const client = createClient({ + // deno-lint-ignore no-explicit-any + const clientOpts: Record = { url: this.url, socket: { connectTimeout: this.connectTimeout, - reconnectStrategy: (retries) => { + tls: this.tls || undefined, + reconnectStrategy: (retries: number) => { if (retries > MAX_RECONNECT_RETRIES) { return new Error("Max reconnection attempts reached"); } return Math.min(retries * RECONNECT_BACKOFF_BASE_MS, RECONNECT_BACKOFF_MAX_MS); }, }, - }); + }; + if (this.password) clientOpts.password = this.password; + if (this.username) clientOpts.username = this.username; + + const client = createClient(clientOpts); client.on("error", (err) => { logger.error("[RedisCache] Client error", { diff --git a/src/utils/redis-client.ts b/src/utils/redis-client.ts index 7013b416a7..cc393e8938 100644 --- a/src/utils/redis-client.ts +++ b/src/utils/redis-client.ts @@ -97,11 +97,13 @@ async function createClient(options: RedisClientOptions): Promise { if (useTls) { clientOpts.socket = { tls: true }; } - if (options.password ?? getEnv("REDIS_PASSWORD")) { - clientOpts.password = options.password ?? getEnv("REDIS_PASSWORD"); + const password = options.password ?? getEnv("REDIS_PASSWORD"); + if (password) { + clientOpts.password = password; } - if (options.username ?? getEnv("REDIS_USERNAME")) { - clientOpts.username = options.username ?? getEnv("REDIS_USERNAME"); + const username = options.username ?? getEnv("REDIS_USERNAME"); + if (username) { + clientOpts.username = username; } const client = createClientFn(clientOpts);