From c9576cbf3afd6221c88c1316d9371dae0131f65e Mon Sep 17 00:00:00 2001 From: justxd22 Date: Mon, 20 Apr 2026 00:41:43 +0200 Subject: [PATCH 1/2] fix: skip Redis auth when credentials are unset with unit test (#541) --- .changeset/thirty-turtles-design.md | 5 +++ src/cache/client.ts | 31 +++++++++++++--- test/unit/cache/client.spec.ts | 57 +++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 6 deletions(-) create mode 100644 .changeset/thirty-turtles-design.md create mode 100644 test/unit/cache/client.spec.ts diff --git a/.changeset/thirty-turtles-design.md b/.changeset/thirty-turtles-design.md new file mode 100644 index 00000000..41a85fc7 --- /dev/null +++ b/.changeset/thirty-turtles-design.md @@ -0,0 +1,5 @@ +--- +"nostream": patch +--- + +Fix Redis cache connection config to skip AUTH when `REDIS_PASSWORD` is unset diff --git a/src/cache/client.ts b/src/cache/client.ts index d9064657..976dc5f7 100644 --- a/src/cache/client.ts +++ b/src/cache/client.ts @@ -4,12 +4,31 @@ import { createLogger } from '../factories/logger-factory' const logger = createLogger('cache-client') -export const getCacheConfig = (): RedisClientOptions => ({ - url: process.env.REDIS_URI - ? process.env.REDIS_URI - : `redis://${process.env.REDIS_USER}:${process.env.REDIS_PASSWORD}@${process.env.REDIS_HOST}:${process.env.REDIS_PORT}`, - password: process.env.REDIS_PASSWORD, -}) +export const getCacheConfig = (): RedisClientOptions => { + if (process.env.REDIS_URI) { + return { + url: process.env.REDIS_URI, + ...(process.env.REDIS_PASSWORD ? { password: process.env.REDIS_PASSWORD } : {}), + } + } + + const hasPassword = Boolean(process.env.REDIS_PASSWORD) + const host = process.env.REDIS_HOST + const port = process.env.REDIS_PORT + + if (hasPassword) { + const username = process.env.REDIS_USER ?? 'default' + + return { + url: `redis://${username}:${process.env.REDIS_PASSWORD}@${host}:${port}`, + password: process.env.REDIS_PASSWORD, + } + } + + return { + url: `redis://${host}:${port}`, + } +} let instance: CacheClient | undefined = undefined diff --git a/test/unit/cache/client.spec.ts b/test/unit/cache/client.spec.ts new file mode 100644 index 00000000..7ec852d8 --- /dev/null +++ b/test/unit/cache/client.spec.ts @@ -0,0 +1,57 @@ +import { expect } from 'chai' + +import { getCacheConfig } from '../../../src/cache/client' + +describe('getCacheConfig', () => { + const originalEnv = { ...process.env } + + beforeEach(() => { + process.env = { ...originalEnv } + delete process.env.REDIS_URI + delete process.env.REDIS_USER + delete process.env.REDIS_PASSWORD + delete process.env.REDIS_HOST + delete process.env.REDIS_PORT + }) + + after(() => { + process.env = originalEnv + }) + + it('builds unauthenticated redis url when REDIS_URI and REDIS_PASSWORD are unset', () => { + process.env.REDIS_HOST = 'localhost' + process.env.REDIS_PORT = '6379' + + const config = getCacheConfig() + + expect(config).to.deep.equal({ + url: 'redis://localhost:6379', + }) + }) + + it('builds authenticated redis url when REDIS_PASSWORD is set', () => { + process.env.REDIS_HOST = 'localhost' + process.env.REDIS_PORT = '6379' + process.env.REDIS_USER = 'default' + process.env.REDIS_PASSWORD = 'secret' + + const config = getCacheConfig() + + expect(config).to.deep.equal({ + url: 'redis://default:secret@localhost:6379', + password: 'secret', + }) + }) + + it('prefers REDIS_URI over host/port settings', () => { + process.env.REDIS_URI = 'redis://cache.internal:6380' + process.env.REDIS_PASSWORD = 'secret' + + const config = getCacheConfig() + + expect(config).to.deep.equal({ + url: 'redis://cache.internal:6380', + password: 'secret', + }) + }) +}) \ No newline at end of file From 8737afd49b297e7fa026d18bf9f85137b934d5c5 Mon Sep 17 00:00:00 2001 From: justxd22 Date: Mon, 20 Apr 2026 00:59:17 +0200 Subject: [PATCH 2/2] fix: harden Redis cache config and env test isolation (#541) --- src/cache/client.ts | 25 +++++++++++++++++++++++-- test/unit/cache/client.spec.ts | 23 +++++++++++++++++++---- 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/src/cache/client.ts b/src/cache/client.ts index 976dc5f7..de7d689b 100644 --- a/src/cache/client.ts +++ b/src/cache/client.ts @@ -4,6 +4,23 @@ import { createLogger } from '../factories/logger-factory' const logger = createLogger('cache-client') +const redactRedisUrlCredentials = (url: string): string => { + try { + const parsedUrl = new URL(url) + + if (!parsedUrl.username && !parsedUrl.password) { + return url + } + + parsedUrl.username = parsedUrl.username ? '***' : '' + parsedUrl.password = parsedUrl.password ? '***' : '' + + return parsedUrl.toString() + } catch { + return url + } +} + export const getCacheConfig = (): RedisClientOptions => { if (process.env.REDIS_URI) { return { @@ -20,7 +37,8 @@ export const getCacheConfig = (): RedisClientOptions => { const username = process.env.REDIS_USER ?? 'default' return { - url: `redis://${username}:${process.env.REDIS_PASSWORD}@${host}:${port}`, + url: `redis://${host}:${port}`, + username, password: process.env.REDIS_PASSWORD, } } @@ -37,7 +55,10 @@ export const getCacheClient = (): CacheClient => { const config = getCacheConfig() // eslint-disable-next-line @typescript-eslint/no-unused-vars const { password: _, ...loggableConfig } = config - logger('config: %o', loggableConfig) + logger('config: %o', { + ...loggableConfig, + ...(loggableConfig.url ? { url: redactRedisUrlCredentials(loggableConfig.url) } : {}), + }) instance = createClient(config) } diff --git a/test/unit/cache/client.spec.ts b/test/unit/cache/client.spec.ts index 7ec852d8..17564c1a 100644 --- a/test/unit/cache/client.spec.ts +++ b/test/unit/cache/client.spec.ts @@ -3,7 +3,7 @@ import { expect } from 'chai' import { getCacheConfig } from '../../../src/cache/client' describe('getCacheConfig', () => { - const originalEnv = { ...process.env } + const originalEnv = process.env beforeEach(() => { process.env = { ...originalEnv } @@ -14,7 +14,7 @@ describe('getCacheConfig', () => { delete process.env.REDIS_PORT }) - after(() => { + afterEach(() => { process.env = originalEnv }) @@ -29,7 +29,7 @@ describe('getCacheConfig', () => { }) }) - it('builds authenticated redis url when REDIS_PASSWORD is set', () => { + it('builds authenticated redis config when REDIS_PASSWORD is set', () => { process.env.REDIS_HOST = 'localhost' process.env.REDIS_PORT = '6379' process.env.REDIS_USER = 'default' @@ -38,7 +38,22 @@ describe('getCacheConfig', () => { const config = getCacheConfig() expect(config).to.deep.equal({ - url: 'redis://default:secret@localhost:6379', + url: 'redis://localhost:6379', + username: 'default', + password: 'secret', + }) + }) + + it('defaults REDIS_USER to default when REDIS_PASSWORD is set and REDIS_USER is unset', () => { + process.env.REDIS_HOST = 'localhost' + process.env.REDIS_PORT = '6379' + process.env.REDIS_PASSWORD = 'secret' + + const config = getCacheConfig() + + expect(config).to.deep.equal({ + url: 'redis://localhost:6379', + username: 'default', password: 'secret', }) })