Skip to content

Commit 53329d8

Browse files
committed
feat(auth): add support for function for password
1 parent 40ae7ee commit 53329d8

File tree

6 files changed

+638
-365
lines changed

6 files changed

+638
-365
lines changed

lib/Redis.ts

Lines changed: 118 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -188,115 +188,122 @@ class Redis extends Commander implements DataHandledable {
188188

189189
const { options } = this;
190190

191-
this.condition = {
192-
select: options.db,
193-
auth: options.username
194-
? [options.username, options.password]
195-
: options.password,
196-
subscriber: false,
197-
};
198-
199-
const _this = this;
200-
asCallback(
201-
this.connector.connect(function (type, err) {
202-
_this.silentEmit(type, err);
203-
}) as Promise<NetStream>,
204-
function (err: Error | null, stream?: NetStream) {
205-
if (err) {
206-
_this.flushQueue(err);
207-
_this.silentEmit("error", err);
208-
reject(err);
209-
_this.setStatus("end");
210-
return;
211-
}
212-
let CONNECT_EVENT = options.tls ? "secureConnect" : "connect";
213-
if (
214-
"sentinels" in options &&
215-
options.sentinels &&
216-
!options.enableTLSForSentinelMode
217-
) {
218-
CONNECT_EVENT = "connect";
219-
}
191+
this.resolvePassword().then((resolvedPassword) => {
192+
this.condition = {
193+
select: options.db,
194+
auth: options.username
195+
? [options.username, resolvedPassword]
196+
: resolvedPassword,
197+
subscriber: false,
198+
};
199+
200+
const _this = this;
201+
asCallback(
202+
this.connector.connect(function (type, err) {
203+
_this.silentEmit(type, err);
204+
}) as Promise<NetStream>,
205+
function (err: Error | null, stream?: NetStream) {
206+
if (err) {
207+
_this.flushQueue(err);
208+
_this.silentEmit("error", err);
209+
reject(err);
210+
_this.setStatus("end");
211+
return;
212+
}
213+
let CONNECT_EVENT = options.tls ? "secureConnect" : "connect";
214+
if (
215+
"sentinels" in options &&
216+
options.sentinels &&
217+
!options.enableTLSForSentinelMode
218+
) {
219+
CONNECT_EVENT = "connect";
220+
}
220221

221-
_this.stream = stream;
222+
_this.stream = stream;
222223

223-
if (options.noDelay) {
224-
stream.setNoDelay(true);
225-
}
224+
if (options.noDelay) {
225+
stream.setNoDelay(true);
226+
}
226227

227-
// Node ignores setKeepAlive before connect, therefore we wait for the event:
228-
// https://github.com/nodejs/node/issues/31663
229-
if (typeof options.keepAlive === "number") {
230-
if (stream.connecting) {
231-
stream.once(CONNECT_EVENT, () => {
228+
// Node ignores setKeepAlive before connect, therefore we wait for the event:
229+
// https://github.com/nodejs/node/issues/31663
230+
if (typeof options.keepAlive === "number") {
231+
if (stream.connecting) {
232+
stream.once(CONNECT_EVENT, () => {
233+
stream.setKeepAlive(true, options.keepAlive);
234+
});
235+
} else {
232236
stream.setKeepAlive(true, options.keepAlive);
233-
});
234-
} else {
235-
stream.setKeepAlive(true, options.keepAlive);
237+
}
236238
}
237-
}
238239

239-
if (stream.connecting) {
240-
stream.once(CONNECT_EVENT, eventHandler.connectHandler(_this));
241-
242-
if (options.connectTimeout) {
243-
/*
244-
* Typically, Socket#setTimeout(0) will clear the timer
245-
* set before. However, in some platforms (Electron 3.x~4.x),
246-
* the timer will not be cleared. So we introduce a variable here.
247-
*
248-
* See https://github.com/electron/electron/issues/14915
249-
*/
250-
let connectTimeoutCleared = false;
251-
stream.setTimeout(options.connectTimeout, function () {
252-
if (connectTimeoutCleared) {
253-
return;
254-
}
255-
stream.setTimeout(0);
256-
stream.destroy();
257-
258-
const err = new Error("connect ETIMEDOUT");
259-
// @ts-expect-error
260-
err.errorno = "ETIMEDOUT";
261-
// @ts-expect-error
262-
err.code = "ETIMEDOUT";
263-
// @ts-expect-error
264-
err.syscall = "connect";
265-
eventHandler.errorHandler(_this)(err);
266-
});
267-
stream.once(CONNECT_EVENT, function () {
268-
connectTimeoutCleared = true;
269-
stream.setTimeout(0);
270-
});
240+
if (stream.connecting) {
241+
stream.once(CONNECT_EVENT, eventHandler.connectHandler(_this));
242+
243+
if (options.connectTimeout) {
244+
/*
245+
* Typically, Socket#setTimeout(0) will clear the timer
246+
* set before. However, in some platforms (Electron 3.x~4.x),
247+
* the timer will not be cleared. So we introduce a variable here.
248+
*
249+
* See https://github.com/electron/electron/issues/14915
250+
*/
251+
let connectTimeoutCleared = false;
252+
stream.setTimeout(options.connectTimeout, function () {
253+
if (connectTimeoutCleared) {
254+
return;
255+
}
256+
stream.setTimeout(0);
257+
stream.destroy();
258+
259+
const err = new Error("connect ETIMEDOUT");
260+
// @ts-expect-error
261+
err.errorno = "ETIMEDOUT";
262+
// @ts-expect-error
263+
err.code = "ETIMEDOUT";
264+
// @ts-expect-error
265+
err.syscall = "connect";
266+
eventHandler.errorHandler(_this)(err);
267+
});
268+
stream.once(CONNECT_EVENT, function () {
269+
connectTimeoutCleared = true;
270+
stream.setTimeout(0);
271+
});
272+
}
273+
} else if (stream.destroyed) {
274+
const firstError = _this.connector.firstError;
275+
if (firstError) {
276+
process.nextTick(() => {
277+
eventHandler.errorHandler(_this)(firstError);
278+
});
279+
}
280+
process.nextTick(eventHandler.closeHandler(_this));
281+
} else {
282+
process.nextTick(eventHandler.connectHandler(_this));
271283
}
272-
} else if (stream.destroyed) {
273-
const firstError = _this.connector.firstError;
274-
if (firstError) {
275-
process.nextTick(() => {
276-
eventHandler.errorHandler(_this)(firstError);
277-
});
284+
if (!stream.destroyed) {
285+
stream.once("error", eventHandler.errorHandler(_this));
286+
stream.once("close", eventHandler.closeHandler(_this));
278287
}
279-
process.nextTick(eventHandler.closeHandler(_this));
280-
} else {
281-
process.nextTick(eventHandler.connectHandler(_this));
282-
}
283-
if (!stream.destroyed) {
284-
stream.once("error", eventHandler.errorHandler(_this));
285-
stream.once("close", eventHandler.closeHandler(_this));
286-
}
287288

288-
const connectionReadyHandler = function () {
289-
_this.removeListener("close", connectionCloseHandler);
290-
resolve();
291-
};
292-
var connectionCloseHandler = function () {
293-
_this.removeListener("ready", connectionReadyHandler);
294-
reject(new Error(CONNECTION_CLOSED_ERROR_MSG));
295-
};
296-
_this.once("ready", connectionReadyHandler);
297-
_this.once("close", connectionCloseHandler);
298-
}
299-
);
289+
const connectionReadyHandler = function () {
290+
_this.removeListener("close", connectionCloseHandler);
291+
resolve();
292+
};
293+
var connectionCloseHandler = function () {
294+
_this.removeListener("ready", connectionReadyHandler);
295+
reject(new Error(CONNECTION_CLOSED_ERROR_MSG));
296+
};
297+
_this.once("ready", connectionReadyHandler);
298+
_this.once("close", connectionCloseHandler);
299+
}
300+
);
301+
}).catch((err) => {
302+
this.flushQueue(err);
303+
this.silentEmit("error", err);
304+
reject(err);
305+
this.setStatus("end");
306+
});
300307
});
301308

302309
return asCallback(promise, callback);
@@ -857,6 +864,17 @@ class Redis extends Commander implements DataHandledable {
857864
}
858865
}).catch(noop);
859866
}
867+
868+
private async resolvePassword(): Promise<string | null> {
869+
const { password } = this.options;
870+
if (!password) {
871+
return null;
872+
}
873+
if (typeof password === "function") {
874+
return await password();
875+
}
876+
return password;
877+
}
860878
}
861879

862880
interface Redis extends EventEmitter {

lib/cluster/util.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export interface RedisOptions {
99
port: number;
1010
host: string;
1111
username?: string;
12-
password?: string;
12+
password?: string | (() => Promise<string> | string);
1313
[key: string]: any;
1414
}
1515

lib/connectors/SentinelConnector/index.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export interface SentinelConnectionOptions {
4242
role?: "master" | "slave";
4343
tls?: ConnectionOptions;
4444
sentinelUsername?: string;
45-
sentinelPassword?: string;
45+
sentinelPassword?: string | (() => Promise<string> | string);
4646
sentinels?: Array<Partial<SentinelAddress>>;
4747
sentinelRetryStrategy?: (retryAttempts: number) => number | void | null;
4848
sentinelReconnectStrategy?: (retryAttempts: number) => number | void | null;
@@ -294,15 +294,27 @@ export default class SentinelConnector extends AbstractConnector {
294294
return result;
295295
}
296296

297-
private connectToSentinel(
297+
private async resolveSentinelPassword(): Promise<string | null> {
298+
const { sentinelPassword } = this.options;
299+
if (!sentinelPassword) {
300+
return null;
301+
}
302+
if (typeof sentinelPassword === "function") {
303+
return await sentinelPassword();
304+
}
305+
return sentinelPassword;
306+
}
307+
308+
private async connectToSentinel(
298309
endpoint: Partial<SentinelAddress>,
299310
options?: Partial<RedisOptions>
300-
): RedisClient {
311+
): Promise<RedisClient> {
312+
const resolvedPassword = await this.resolveSentinelPassword();
301313
const redis = new Redis({
302314
port: endpoint.port || 26379,
303315
host: endpoint.host,
304316
username: this.options.sentinelUsername || null,
305-
password: this.options.sentinelPassword || null,
317+
password: resolvedPassword,
306318
family:
307319
endpoint.family ||
308320
// @ts-expect-error
@@ -324,7 +336,7 @@ export default class SentinelConnector extends AbstractConnector {
324336
private async resolve(
325337
endpoint: Partial<SentinelAddress>
326338
): Promise<TcpNetConnectOpts | null> {
327-
const client = this.connectToSentinel(endpoint);
339+
const client = await this.connectToSentinel(endpoint);
328340

329341
// ignore the errors since resolve* methods will handle them
330342
client.on("error", noop);
@@ -357,7 +369,7 @@ export default class SentinelConnector extends AbstractConnector {
357369
break;
358370
}
359371

360-
const client = this.connectToSentinel(value, {
372+
const client = await this.connectToSentinel(value, {
361373
lazyConnect: true,
362374
retryStrategy: this.options.sentinelReconnectStrategy,
363375
});

lib/redis/RedisOptions.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,9 @@ export interface CommonRedisOptions extends CommanderOptions {
5252

5353
/**
5454
* If set, client will send AUTH command with the value of this option when connected.
55+
* Can be a string or a function that returns a string or Promise<string>.
5556
*/
56-
password?: string;
57+
password?: string | (() => Promise<string> | string);
5758

5859
/**
5960
* Database index to use.

0 commit comments

Comments
 (0)