1
0
mirror of https://github.com/redis/node-redis.git synced 2025-12-25 00:40:59 +03:00

fix: createClient url+tls invariant violation check (#2835)

This commit is contained in:
Igor Loskutov
2025-08-05 03:12:10 -04:00
committed by GitHub
parent 66638fc903
commit 0f709d01c5
2 changed files with 82 additions and 16 deletions

View File

@@ -64,7 +64,8 @@ describe('Client', () => {
const expected: RedisClientOptions = {
socket: {
host: 'localhost',
port: 6379
port: 6379,
tls: false
},
username: 'user',
password: 'secret',
@@ -161,12 +162,58 @@ describe('Client', () => {
{
socket: {
host: 'localhost',
tls: false
}
}
);
});
});
describe('parseOptions', () => {
it('should throw error if tls socket option is set to true and the url protocol is "redis:"', () => {
assert.throws(
() => RedisClient.parseOptions({
url: 'redis://localhost',
socket: {
tls: true
}
}),
TypeError
);
});
it('should throw error if tls socket option is set to false and the url protocol is "rediss:"', () => {
assert.throws(
() => RedisClient.parseOptions({
url: 'rediss://localhost',
socket: {
tls: false
}
}),
TypeError
);
});
it('should not throw when tls socket option and url protocol matches"', () => {
assert.equal(
RedisClient.parseOptions({
url: 'rediss://localhost',
socket: {
tls: true
}
}).socket.tls,
true
);
assert.equal(
RedisClient.parseOptions({
url: 'redis://localhost',
socket: {
tls: false
}
}).socket.tls,
false
);
});
});
describe('authentication', () => {
testUtils.testWithClient('Client should be authenticated', async client => {
assert.equal(

View File

@@ -315,21 +315,45 @@ export default class RedisClient<
return RedisClient.factory(options)(options);
}
static parseURL(url: string): RedisClientOptions {
static parseOptions<O extends RedisClientOptions>(options: O): O {
if (options?.url) {
const parsed = RedisClient.parseURL(options.url);
if (options.socket) {
if (options.socket.tls !== undefined && options.socket.tls !== parsed.socket.tls) {
throw new TypeError(`tls socket option is set to ${options.socket.tls} which is mismatch with protocol or the URL ${options.url} passed`)
}
parsed.socket = Object.assign(options.socket, parsed.socket);
}
Object.assign(options, parsed);
}
return options;
}
static parseURL(url: string): RedisClientOptions & {
socket: Exclude<RedisClientOptions['socket'], undefined> & {
tls: boolean
}
} {
// https://www.iana.org/assignments/uri-schemes/prov/redis
const { hostname, port, protocol, username, password, pathname } = new URL(url),
parsed: RedisClientOptions = {
parsed: RedisClientOptions & {
socket: Exclude<RedisClientOptions['socket'], undefined> & {
tls: boolean
}
} = {
socket: {
host: hostname
host: hostname,
tls: false
}
};
if (protocol === 'rediss:') {
parsed!.socket!.tls = true;
} else if (protocol !== 'redis:') {
if (protocol !== 'redis:' && protocol !== 'rediss:') {
throw new TypeError('Invalid protocol');
}
parsed.socket.tls = protocol === 'rediss:';
if (port) {
(parsed.socket as TcpSocketConnectOpts).port = Number(port);
}
@@ -464,15 +488,6 @@ export default class RedisClient<
};
}
if (options?.url) {
const parsed = RedisClient.parseURL(options.url);
if (options.socket) {
parsed.socket = Object.assign(options.socket, parsed.socket);
}
Object.assign(options, parsed);
}
if (options?.database) {
this._self.#selectedDB = options.database;
}
@@ -481,6 +496,10 @@ export default class RedisClient<
this._commandOptions = options.commandOptions;
}
if (options) {
return RedisClient.parseOptions(options);
}
return options;
}