Skip to content

Commit a13ecb9

Browse files
slukesPavelPashov
authored andcommitted
feat(auth): add support for function for password
1 parent 541b15d commit a13ecb9

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
@@ -189,115 +189,122 @@ class Redis extends Commander implements DataHandledable {
189189

190190
const { options } = this;
191191

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

222-
_this.stream = stream;
223+
_this.stream = stream;
223224

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

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, () => {
229+
// Node ignores setKeepAlive before connect, therefore we wait for the event:
230+
// https://github.com/nodejs/node/issues/31663
231+
if (typeof options.keepAlive === "number") {
232+
if (stream.connecting) {
233+
stream.once(CONNECT_EVENT, () => {
234+
stream.setKeepAlive(true, options.keepAlive);
235+
});
236+
} else {
233237
stream.setKeepAlive(true, options.keepAlive);
234-
});
235-
} else {
236-
stream.setKeepAlive(true, options.keepAlive);
238+
}
237239
}
238-
}
239240

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

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

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

863881
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)