-
Notifications
You must be signed in to change notification settings - Fork 1.2k
feat(auth): add support for function for password #2039
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
a13ecb9
412eba8
6e70891
25fcf29
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -189,115 +189,127 @@ class Redis extends Commander implements DataHandledable { | |
|
|
||
| const { options } = this; | ||
|
|
||
| // Note that `this.condition` has to be set _before_ any asynchronous work | ||
| // takes place as the `select` value is required when queueing commands | ||
| // into the offline queue (see sendCommand) | ||
| this.condition = { | ||
| select: options.db, | ||
| auth: options.username | ||
| ? [options.username, options.password] | ||
| : options.password, | ||
| subscriber: false, | ||
| }; | ||
| this.resolvePassword((err, resolvedPassword) => { | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a big change from @slukes work. Rather than use an async promise here, the If this is changed into async logic, there are a few tests that fail because there seems to be an expectation that the it("connects successfully immediately after end", (done) => {
const redis = new Redis();
redis.once("end", async () => {
await redis.connect();
done();
});
redis.quit();
});The However, if All of that said, this test could be changed to wait until the client is ready. Would that be breaking behaviour? it("connects successfully immediately after end", (done) => {
const redis = new Redis();
redis.once("end", async () => {
await redis.connect();
done();
});
redis.on("ready", () => redis.quit());
}); |
||
| if (err) { | ||
| this.flushQueue(err); | ||
| this.silentEmit("error", err); | ||
| this.setStatus("end"); | ||
| reject(err); | ||
| return; | ||
| } | ||
| this.condition.auth = options.username | ||
| ? [options.username, resolvedPassword] | ||
| : resolvedPassword | ||
|
|
||
| const _this = this; | ||
| asCallback( | ||
| this.connector.connect(function (type, err) { | ||
| _this.silentEmit(type, err); | ||
| }) as Promise<NetStream>, | ||
| function (err: Error | null, stream?: NetStream) { | ||
| if (err) { | ||
| _this.flushQueue(err); | ||
| _this.silentEmit("error", err); | ||
| reject(err); | ||
| _this.setStatus("end"); | ||
| return; | ||
| } | ||
| let CONNECT_EVENT = options.tls ? "secureConnect" : "connect"; | ||
| if ( | ||
| "sentinels" in options && | ||
| options.sentinels && | ||
| !options.enableTLSForSentinelMode | ||
| ) { | ||
| CONNECT_EVENT = "connect"; | ||
| } | ||
|
|
||
| const _this = this; | ||
| asCallback( | ||
| this.connector.connect(function (type, err) { | ||
| _this.silentEmit(type, err); | ||
| }) as Promise<NetStream>, | ||
| function (err: Error | null, stream?: NetStream) { | ||
| if (err) { | ||
| _this.flushQueue(err); | ||
| _this.silentEmit("error", err); | ||
| reject(err); | ||
| _this.setStatus("end"); | ||
| return; | ||
| } | ||
| let CONNECT_EVENT = options.tls ? "secureConnect" : "connect"; | ||
| if ( | ||
| "sentinels" in options && | ||
| options.sentinels && | ||
| !options.enableTLSForSentinelMode | ||
| ) { | ||
| CONNECT_EVENT = "connect"; | ||
| } | ||
|
|
||
| _this.stream = stream; | ||
| _this.stream = stream; | ||
|
|
||
| if (options.noDelay) { | ||
| stream.setNoDelay(true); | ||
| } | ||
| if (options.noDelay) { | ||
| stream.setNoDelay(true); | ||
| } | ||
|
|
||
| // Node ignores setKeepAlive before connect, therefore we wait for the event: | ||
| // https://github.com/nodejs/node/issues/31663 | ||
| if (typeof options.keepAlive === "number") { | ||
| if (stream.connecting) { | ||
| stream.once(CONNECT_EVENT, () => { | ||
| // Node ignores setKeepAlive before connect, therefore we wait for the event: | ||
| // https://github.com/nodejs/node/issues/31663 | ||
| if (typeof options.keepAlive === "number") { | ||
| if (stream.connecting) { | ||
| stream.once(CONNECT_EVENT, () => { | ||
| stream.setKeepAlive(true, options.keepAlive); | ||
| }); | ||
| } else { | ||
| stream.setKeepAlive(true, options.keepAlive); | ||
| }); | ||
| } else { | ||
| stream.setKeepAlive(true, options.keepAlive); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if (stream.connecting) { | ||
| stream.once(CONNECT_EVENT, eventHandler.connectHandler(_this)); | ||
|
|
||
| if (options.connectTimeout) { | ||
| /* | ||
| * Typically, Socket#setTimeout(0) will clear the timer | ||
| * set before. However, in some platforms (Electron 3.x~4.x), | ||
| * the timer will not be cleared. So we introduce a variable here. | ||
| * | ||
| * See https://github.com/electron/electron/issues/14915 | ||
| */ | ||
| let connectTimeoutCleared = false; | ||
| stream.setTimeout(options.connectTimeout, function () { | ||
| if (connectTimeoutCleared) { | ||
| return; | ||
| } | ||
| stream.setTimeout(0); | ||
| stream.destroy(); | ||
|
|
||
| const err = new Error("connect ETIMEDOUT"); | ||
| // @ts-expect-error | ||
| err.errorno = "ETIMEDOUT"; | ||
| // @ts-expect-error | ||
| err.code = "ETIMEDOUT"; | ||
| // @ts-expect-error | ||
| err.syscall = "connect"; | ||
| eventHandler.errorHandler(_this)(err); | ||
| }); | ||
| stream.once(CONNECT_EVENT, function () { | ||
| connectTimeoutCleared = true; | ||
| stream.setTimeout(0); | ||
| }); | ||
| if (stream.connecting) { | ||
| stream.once(CONNECT_EVENT, eventHandler.connectHandler(_this)); | ||
|
|
||
| if (options.connectTimeout) { | ||
| /* | ||
| * Typically, Socket#setTimeout(0) will clear the timer | ||
| * set before. However, in some platforms (Electron 3.x~4.x), | ||
| * the timer will not be cleared. So we introduce a variable here. | ||
| * | ||
| * See https://github.com/electron/electron/issues/14915 | ||
| */ | ||
| let connectTimeoutCleared = false; | ||
| stream.setTimeout(options.connectTimeout, function () { | ||
| if (connectTimeoutCleared) { | ||
| return; | ||
| } | ||
| stream.setTimeout(0); | ||
| stream.destroy(); | ||
|
|
||
| const err = new Error("connect ETIMEDOUT"); | ||
| // @ts-expect-error | ||
| err.errorno = "ETIMEDOUT"; | ||
| // @ts-expect-error | ||
| err.code = "ETIMEDOUT"; | ||
| // @ts-expect-error | ||
| err.syscall = "connect"; | ||
| eventHandler.errorHandler(_this)(err); | ||
| }); | ||
| stream.once(CONNECT_EVENT, function () { | ||
| connectTimeoutCleared = true; | ||
| stream.setTimeout(0); | ||
| }); | ||
| } | ||
| } else if (stream.destroyed) { | ||
| const firstError = _this.connector.firstError; | ||
| if (firstError) { | ||
| process.nextTick(() => { | ||
| eventHandler.errorHandler(_this)(firstError); | ||
| }); | ||
| } | ||
| process.nextTick(eventHandler.closeHandler(_this)); | ||
| } else { | ||
| process.nextTick(eventHandler.connectHandler(_this)); | ||
| } | ||
| } else if (stream.destroyed) { | ||
| const firstError = _this.connector.firstError; | ||
| if (firstError) { | ||
| process.nextTick(() => { | ||
| eventHandler.errorHandler(_this)(firstError); | ||
| }); | ||
| if (!stream.destroyed) { | ||
| stream.once("error", eventHandler.errorHandler(_this)); | ||
| stream.once("close", eventHandler.closeHandler(_this)); | ||
| } | ||
| process.nextTick(eventHandler.closeHandler(_this)); | ||
| } else { | ||
| process.nextTick(eventHandler.connectHandler(_this)); | ||
| } | ||
| if (!stream.destroyed) { | ||
| stream.once("error", eventHandler.errorHandler(_this)); | ||
| stream.once("close", eventHandler.closeHandler(_this)); | ||
| } | ||
|
|
||
| const connectionReadyHandler = function () { | ||
| _this.removeListener("close", connectionCloseHandler); | ||
| resolve(); | ||
| }; | ||
| var connectionCloseHandler = function () { | ||
| _this.removeListener("ready", connectionReadyHandler); | ||
| reject(new Error(CONNECTION_CLOSED_ERROR_MSG)); | ||
| }; | ||
| _this.once("ready", connectionReadyHandler); | ||
| _this.once("close", connectionCloseHandler); | ||
| } | ||
| ); | ||
| const connectionReadyHandler = function () { | ||
| _this.removeListener("close", connectionCloseHandler); | ||
| resolve(); | ||
| }; | ||
| var connectionCloseHandler = function () { | ||
| _this.removeListener("ready", connectionReadyHandler); | ||
| reject(new Error(CONNECTION_CLOSED_ERROR_MSG)); | ||
| }; | ||
| _this.once("ready", connectionReadyHandler); | ||
| _this.once("close", connectionCloseHandler); | ||
| } | ||
| ); | ||
| }); | ||
| }); | ||
|
|
||
| return asCallback(promise, callback); | ||
|
|
@@ -862,6 +874,27 @@ class Redis extends Commander implements DataHandledable { | |
| } | ||
| }).catch(noop); | ||
| } | ||
|
|
||
| private resolvePassword(callback: (err: Error | null, password?: string | null) => void) { | ||
| const { password } = this.options; | ||
| if (!password) { | ||
| return callback(null, null); | ||
| } | ||
| if (typeof password === 'function') { | ||
| let p: ReturnType<typeof password> = null; | ||
| try { | ||
| p = password(); | ||
| } catch (err) { | ||
| return callback(err); | ||
| } | ||
| if (typeof p === 'string' || !p) { | ||
| return callback(null, p as string); | ||
| } | ||
| return p.then((pw) => callback(null, pw), callback); | ||
| } | ||
|
|
||
| return callback(null, password); | ||
| } | ||
| } | ||
|
|
||
| interface Redis extends EventEmitter { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is another change from @slukes work. This allows pipelining and the offline queue to function.