fix: handle empty password, missing port, and TLS in Redis URL parsing (#2021)

* fix: handle empty password, missing port, and TLS in Redis URL parsing
---------

Co-authored-by: Philipinho <16838612+Philipinho@users.noreply.github.com>
This commit is contained in:
Michael Lohr
2026-08-19 21:05:44 +01:00
committed by GitHub
co-authored by Philipinho
parent 8913d20aa0
commit 8c2c49ea6d
7 changed files with 33 additions and 7 deletions
+13 -2
View File
@@ -22,7 +22,8 @@ import { TelemetryModule } from './integrations/telemetry/telemetry.module';
import { RedisModule } from '@nestjs-labs/nestjs-ioredis'; import { RedisModule } from '@nestjs-labs/nestjs-ioredis';
import { RedisConfigService } from './integrations/redis/redis-config.service'; import { RedisConfigService } from './integrations/redis/redis-config.service';
import { CacheModule } from '@nestjs/cache-manager'; import { CacheModule } from '@nestjs/cache-manager';
import KeyvRedis from '@keyv/redis'; import KeyvRedis, { defaultReconnectStrategy } from '@keyv/redis';
import { parseRedisUrl } from './common/helpers';
import { LoggerModule } from './common/logger/logger.module'; import { LoggerModule } from './common/logger/logger.module';
import { ClsModule } from 'nestjs-cls'; import { ClsModule } from 'nestjs-cls';
import { NoopAuditModule } from './integrations/audit/audit.module'; import { NoopAuditModule } from './integrations/audit/audit.module';
@@ -62,10 +63,20 @@ try {
isGlobal: true, isGlobal: true,
useFactory: async (environmentService: EnvironmentService) => { useFactory: async (environmentService: EnvironmentService) => {
const redisUrl = environmentService.getRedisUrl(); const redisUrl = environmentService.getRedisUrl();
const { family, tls } = parseRedisUrl(redisUrl);
return { return {
ttl: 5 * 1000, ttl: 5 * 1000,
stores: [new KeyvRedis(redisUrl)], stores: [
new KeyvRedis({
url: redisUrl,
socket: {
family,
reconnectStrategy: defaultReconnectStrategy,
...tls,
},
}),
],
}; };
}, },
inject: [EnvironmentService], inject: [EnvironmentService],
@@ -66,6 +66,7 @@ export class CollaborationGateway {
password: this.redisConfig.password, password: this.redisConfig.password,
db: this.redisConfig.db, db: this.redisConfig.db,
family: this.redisConfig.family, family: this.redisConfig.family,
tls: this.redisConfig.tls,
retryStrategy: createRetryStrategy(), retryStrategy: createRetryStrategy(),
}), }),
serverId: `collab-${os?.hostname()}-${nanoid(10)}`, serverId: `collab-${os?.hostname()}-${nanoid(10)}`,
+12 -4
View File
@@ -30,13 +30,14 @@ export type RedisConfig = {
db: number; db: number;
password?: string; password?: string;
family?: number; family?: number;
tls?: { rejectUnauthorized?: boolean };
}; };
export function parseRedisUrl(redisUrl: string): RedisConfig { export function parseRedisUrl(redisUrl: string): RedisConfig {
// format - redis[s]://[[username][:password]@][host][:port][/db-number][?family=4|6] // format - redis[s]://[[username][:password]@][host][:port][/db-number][?family=4|6][&rejectUnauthorized=false]
const url = new URL(redisUrl); const url = new URL(redisUrl);
const { hostname, port, password, pathname, searchParams } = url; const { hostname, port, password, pathname, protocol, searchParams } = url;
const portInt = parseInt(port, 10); const portInt = port ? parseInt(port, 10) : 6379;
let db: number = 0; let db: number = 0;
// extract db value if present // extract db value if present
@@ -54,7 +55,14 @@ export function parseRedisUrl(redisUrl: string): RedisConfig {
family = parseInt(familyParam, 10); family = parseInt(familyParam, 10);
} }
return { host: hostname, port: portInt, password, db, family }; const tls =
protocol === 'rediss:'
? searchParams.get('rejectUnauthorized') === 'false'
? { rejectUnauthorized: false }
: {}
: undefined;
return { host: hostname, port: portInt, password: password || undefined, db, family, tls };
} }
export function createRetryStrategy() { export function createRetryStrategy() {
@@ -5,6 +5,7 @@ import {
import { Injectable, Logger } from '@nestjs/common'; import { Injectable, Logger } from '@nestjs/common';
import { EnvironmentService } from '../environment/environment.service'; import { EnvironmentService } from '../environment/environment.service';
import { Redis } from 'ioredis'; import { Redis } from 'ioredis';
import { parseRedisUrl } from '../../common/helpers';
@Injectable() @Injectable()
export class RedisHealthIndicator { export class RedisHealthIndicator {
@@ -19,8 +20,10 @@ export class RedisHealthIndicator {
const indicator = this.healthIndicatorService.check(key); const indicator = this.healthIndicatorService.check(key);
try { try {
const redis = new Redis(this.environmentService.getRedisUrl(), { const redisUrl = this.environmentService.getRedisUrl();
const redis = new Redis(redisUrl, {
maxRetriesPerRequest: 15, maxRetriesPerRequest: 15,
tls: parseRedisUrl(redisUrl).tls,
}); });
await redis.ping(); await redis.ping();
@@ -18,6 +18,7 @@ import { GeneralQueueProcessor } from './processors/general-queue.processor';
password: redisConfig.password, password: redisConfig.password,
db: redisConfig.db, db: redisConfig.db,
family: redisConfig.family, family: redisConfig.family,
tls: redisConfig.tls,
retryStrategy: createRetryStrategy(), retryStrategy: createRetryStrategy(),
}, },
defaultJobOptions: { defaultJobOptions: {
@@ -19,6 +19,7 @@ export class RedisConfigService implements RedisOptionsFactory {
password: redisConfig.password, password: redisConfig.password,
db: redisConfig.db, db: redisConfig.db,
family: redisConfig.family, family: redisConfig.family,
tls: redisConfig.tls,
retryStrategy: createRetryStrategy(), retryStrategy: createRetryStrategy(),
}, },
}; };
@@ -17,6 +17,7 @@ export class WsRedisIoAdapter extends IoAdapter {
const options: RedisOptions = { const options: RedisOptions = {
family: this.redisConfig.family, family: this.redisConfig.family,
tls: this.redisConfig.tls,
retryStrategy: createRetryStrategy(), retryStrategy: createRetryStrategy(),
}; };