Skip to content

Commit

Permalink
Add magical auth command.
Browse files Browse the repository at this point in the history
Authentication is now remembered by the client and will be automatically sent to the server
on every connection, including any reconnections.
  • Loading branch information
mranney committed Feb 28, 2011
1 parent 2534f74 commit 1a14e24
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 40 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,13 @@ objects instead of JavaScript Strings.

`createClient()` returns a `RedisClient` object that is named `client` in all of the examples here.

## client.auth(password, callback)

When connecting to Redis servers that require authentication, the `AUTH` command must be sent as the
first command after connecting. This can be tricky to coordinate with reconnections, the ready check,
etc. To make this easier, `client.auth()` stashes `password` and will send it after each connection,
including reconnections. `callback` is invoked only once, after the response to the very first
`AUTH` command sent.

## client.end()

Expand Down
7 changes: 7 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
Changelog
=========

## v0.5.7 - February 27, 2011

Add magical auth command.

Authentication is now remembered by the client and will be automatically sent to the server
on every connection, including any reconnections.

## v0.5.6 - February 22, 2011

Fix bug in ready check with `return_buffers` set to `true`.
Expand Down
10 changes: 1 addition & 9 deletions examples/auth.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,5 @@
// Note - Eventually this functionality will be built in to the client library

var redis = require("redis"),
client = redis.createClient();

// whenever the client connects, make sure to auth
client.on("connect", function () {
client.auth("somepass", redis.print);
});

// This command is magical. Client stashes the password and will issue on every connect.
client.auth("somepass");

// then do whatever you want
3 changes: 1 addition & 2 deletions examples/pub_sub.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,9 @@ client1.on("message", function (channel, message) {
}
});

client1.incr("did a thing");

client1.on("ready", function () {
// if you need auth, do it here
client1.incr("did a thing");
client1.subscribe("a nice channel", "another one");
});

Expand Down
100 changes: 72 additions & 28 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ function RedisClient(stream, options) {
this.subscriptions = false;
this.closing = false;
this.server_info = {};
this.auth_pass = null;

var parser_module, self = this;

Expand Down Expand Up @@ -90,30 +91,9 @@ function RedisClient(stream, options) {
});

this.stream.on("connect", function () {
if (exports.debug_mode) {
console.log("Stream connected fd " + self.stream.fd);
}
self.connected = true;
self.ready = false;
self.connections += 1;
self.command_queue = new Queue();
self.emitted_end = false;

self.retry_timer = null;
self.retry_delay = 250;
self.stream.setNoDelay();
self.stream.setTimeout(0);

self.emit("connect");

if (self.options.no_ready_check) {
self.ready = true;
self.send_offline_queue();
} else {
self.ready_check();
}
self.on_connect();
});

this.stream.on("data", function (buffer_from_socket) {
self.on_data(buffer_from_socket);
});
Expand Down Expand Up @@ -164,6 +144,55 @@ function RedisClient(stream, options) {
util.inherits(RedisClient, events.EventEmitter);
exports.RedisClient = RedisClient;

RedisClient.prototype.on_connect = function () {
if (exports.debug_mode) {
console.log("Stream connected " + this.host + ":" + this.port + " fd " + this.stream.fd);
}
var self = this;

this.connected = true;
this.ready = false;
this.connections += 1;
this.command_queue = new Queue();
this.emitted_end = false;
this.retry_timer = null;
this.retry_delay = 250;
this.stream.setNoDelay();
this.stream.setTimeout(0);

if (this.auth_pass) {
if (exports.debug_mode) {
console.log("Sending auth to " + this.host + ":" + this.port + " fd " + this.stream.fd);
}
self.send_anyway = true;
self.send_command("auth", this.auth_pass, function (err, res) {
if (err) {
return self.emit("error", "Auth error: " + err);
}
if (res.toString() !== "OK") {
return self.emit("error", "Auth failed: " + res.toString());
}
if (exports.debug_mode) {
console.log("Auth succeeded " + self.host + ":" + self.port + " fd " + self.stream.fd);
}
if (self.auth_callback) {
self.auth_callback(err, res);
self.auth_callback = null;
}
});
self.send_anyway = false;
}

this.emit("connect");

if (this.options.no_ready_check) {
this.ready = true;
this.send_offline_queue();
} else {
this.ready_check();
}
};

RedisClient.prototype.ready_check = function () {
var self = this;

Expand All @@ -175,8 +204,7 @@ RedisClient.prototype.ready_check = function () {
self.send_anyway = true; // secret flag to send_command to send something even if not "ready"
self.info(function (err, res) {
if (err) {
self.emit("error", "Ready check failed: " + err);
return;
return self.emit("error", "Ready check failed: " + err);
}

var lines = res.toString().split("\r\n"), obj = {}, retry_time;
Expand Down Expand Up @@ -291,7 +319,7 @@ RedisClient.prototype.connection_gone = function (why) {

RedisClient.prototype.on_data = function (data) {
if (exports.debug_mode) {
console.log("net read fd " + this.stream.fd + ": " + data.toString());
console.log("net read " + this.host + ":" + this.port + " fd " + this.stream.fd + ": " + data.toString());
}

try {
Expand Down Expand Up @@ -480,7 +508,7 @@ RedisClient.prototype.send_command = function () {
command_str += "$" + Buffer.byteLength(arg) + "\r\n" + arg + "\r\n";
}
if (exports.debug_mode) {
console.log("send fd " + this.stream.fd + ": " + command_str);
console.log("send " + this.host + ":" + this.port + " fd " + this.stream.fd + ": " + command_str);
}
stream.write(command_str);
} else {
Expand Down Expand Up @@ -547,7 +575,7 @@ function Multi(client, args) {
//bit commands
"getbit", "setbit", "getrange", "setrange",
// misc
"getset", "mset", "msetnx", "randomkey", "select", "move", "rename", "renamenx", "expire", "expireat", "keys", "dbsize", "auth", "ping", "echo",
"getset", "mset", "msetnx", "randomkey", "select", "move", "rename", "renamenx", "expire", "expireat", "keys", "dbsize", "ping", "echo",
"save", "bgsave", "bgwriteaof", "shutdown", "lastsave", "type", "sync", "flushdb", "flushall", "sort", "info",
"monitor", "ttl", "persist", "slaveof", "debug", "config", "subscribe", "unsubscribe", "psubscribe", "punsubscribe", "publish", "watch", "unwatch",
"quit"
Expand All @@ -568,6 +596,22 @@ function Multi(client, args) {
Multi.prototype[command.toUpperCase()] = Multi.prototype[command];
});

// Stash auth for connect and reconnect. Send immediately if already connected.
RedisClient.prototype.auth = function () {
var args = to_array(arguments);
this.auth_pass = args[0];
this.auth_callback = args[1];
if (exports.debug_mode) {
console.log("Saving auth as " + this.auth_pass);
}

if (this.connected) {
args.unshift("auth");
this.send_command.apply(this, args);
}
};
RedisClient.prototype.AUTH = RedisClient.prototype.auth;

RedisClient.prototype.hmset = function () {
var args = to_array(arguments), tmp_args;
if (args.length >= 2 && typeof args[0] === "string" && typeof args[1] === "object") {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{ "name" : "redis",
"version" : "0.5.6",
"version" : "0.5.7",
"description" : "Redis client library",
"author": "Matt Ranney <[email protected]>",
"contributors": [
Expand Down
10 changes: 10 additions & 0 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ var redis = require("./index"),
client = redis.createClient(),
client2 = redis.createClient(),
client3 = redis.createClient(),
client4 = redis.createClient(9006, "filefish.redistogo.com"),
assert = require("assert"),
util = require("./lib/util").util,
test_db_num = 15, // this DB will be flushed and used for testing
Expand Down Expand Up @@ -1049,6 +1050,7 @@ function run_next_test() {
console.log('\n completed \x1b[32m%d\x1b[0m tests in \x1b[33m%d\x1b[0m ms\n', test_count, new Date() - all_start);
client.quit();
client2.quit();
client4.quit();
}
}

Expand All @@ -1066,6 +1068,14 @@ client.on('end', function () {
ended = true;
});

// TODO - need a better way to test auth, maybe auto-config a local Redis server?
client4.auth("664b1b6aaf134e1ec281945a8de702a9", function (err, res) {
if (err) {
assert.fail(err, name);
}
assert.strictEqual("OK", res.toString(), "auth");
});

// Exit immediately on connection failure, which triggers "exit", below, which fails the test
client.on("error", function (err) {
console.error("client: " + err.stack);
Expand Down

0 comments on commit 1a14e24

Please sign in to comment.