forked from redis/node-redis
-
Notifications
You must be signed in to change notification settings - Fork 0
/
auth.spec.js
345 lines (301 loc) · 15.3 KB
/
auth.spec.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
'use strict';
var assert = require('assert');
var config = require('./lib/config');
var helper = require('./helper');
var redis = config.redis;
if (process.platform === 'win32') {
// TODO: Fix redis process spawn on windows
return;
}
describe('client authentication', function () {
before(function (done) {
helper.stopRedis(function () {
helper.startRedis('./conf/password.conf', done);
});
});
helper.allTests({
allConnections: true
}, function (parser, ip, args) {
describe('using ' + parser + ' and ' + ip, function () {
var auth = 'porkchopsandwiches';
var client = null;
beforeEach(function () {
client = null;
});
afterEach(function () {
// Explicitly ignore still running commands
// The ready command could still be running
client.end(false);
});
it("allows auth to be provided with 'auth' method", function (done) {
if (helper.redisProcess().spawnFailed()) this.skip();
client = redis.createClient.apply(null, args);
client.auth(auth, function (err, res) {
assert.strictEqual(null, err);
assert.strictEqual('OK', res.toString());
return done(err);
});
});
it('support redis 2.4 with retrying auth commands if still loading', function (done) {
if (helper.redisProcess().spawnFailed()) this.skip();
client = redis.createClient.apply(null, args);
var time = Date.now();
client.auth(auth, function (err, res) {
assert.strictEqual('retry worked', res);
var now = Date.now();
// Hint: setTimeout sometimes triggers early and therefore the value can be like one or two ms to early
assert(now - time >= 98, 'Time should be above 100 ms (the reconnect time) and is ' + (now - time));
assert(now - time < 225, 'Time should be below 255 ms (the reconnect should only take a bit above 100 ms) and is ' + (now - time));
done();
});
var tmp = client.command_queue.get(0).callback;
client.command_queue.get(0).callback = function (err, res) {
client.auth = function (pass, callback) {
callback(null, 'retry worked');
};
tmp(new Error('ERR redis is still LOADING'));
};
});
it('emits error when auth is bad without callback', function (done) {
if (helper.redisProcess().spawnFailed()) this.skip();
client = redis.createClient.apply(null, args);
client.once('error', function (err) {
assert.strictEqual(err.command, 'AUTH');
assert.ok(/ERR invalid password/.test(err.message));
return done();
});
client.auth(auth + 'bad');
});
it('returns an error when auth is bad (empty string) with a callback', function (done) {
if (helper.redisProcess().spawnFailed()) this.skip();
client = redis.createClient.apply(null, args);
client.auth('', function (err, res) {
assert.strictEqual(err.command, 'AUTH');
assert.ok(/ERR invalid password/.test(err.message));
done();
});
});
if (ip === 'IPv4') {
it('allows auth to be provided as part of redis url and do not fire commands before auth is done', function (done) {
if (helper.redisProcess().spawnFailed()) this.skip();
var end = helper.callFuncAfter(done, 2);
client = redis.createClient('redis://:' + auth + '@' + config.HOST[ip] + ':' + config.PORT);
client.on('ready', function () {
end();
});
// The info command may be used while loading but not if not yet authenticated
client.info(function (err, res) {
assert(!err);
end();
});
});
it('allows auth and database to be provided as part of redis url query parameter', function (done) {
if (helper.redisProcess().spawnFailed()) this.skip();
client = redis.createClient('redis://' + config.HOST[ip] + ':' + config.PORT + '?db=2&password=' + auth);
assert.strictEqual(client.options.db, '2');
assert.strictEqual(client.options.password, auth);
assert.strictEqual(client.auth_pass, auth);
client.on('ready', function () {
// Set a key so the used database is returned in the info command
client.set('foo', 'bar');
client.get('foo');
assert.strictEqual(client.server_info.db2, undefined);
// Using the info command should update the server_info
client.info(function (err, res) {
assert(typeof client.server_info.db2 === 'object');
});
client.flushdb(done);
});
});
}
it('allows auth to be provided as config option for client', function (done) {
if (helper.redisProcess().spawnFailed()) this.skip();
var args = config.configureClient(parser, ip, {
auth_pass: auth
});
client = redis.createClient.apply(null, args);
client.on('ready', done);
});
it('allows auth and no_ready_check to be provided as config option for client', function (done) {
if (helper.redisProcess().spawnFailed()) this.skip();
var args = config.configureClient(parser, ip, {
password: auth,
no_ready_check: true
});
client = redis.createClient.apply(null, args);
client.on('ready', done);
});
it('allows auth to be provided post-hoc with auth method', function (done) {
if (helper.redisProcess().spawnFailed()) this.skip();
var args = config.configureClient(parser, ip);
client = redis.createClient.apply(null, args);
client.auth(auth);
client.on('ready', done);
});
it('reconnects with appropriate authentication while offline commands are present', function (done) {
if (helper.redisProcess().spawnFailed()) this.skip();
client = redis.createClient.apply(null, args);
client.auth(auth);
client.on('ready', function () {
if (this.times_connected < 3) {
var interval = setInterval(function () {
if (client.commandQueueLength !== 0) {
return;
}
clearInterval(interval);
interval = null;
client.stream.destroy();
client.set('foo', 'bar');
client.get('foo'); // Errors would bubble
assert.strictEqual(client.offlineQueueLength, 2);
}, 1);
} else {
done();
}
});
client.on('reconnecting', function (params) {
assert.strictEqual(params.error, null);
});
});
it('should return an error if the password is not correct and a callback has been provided', function (done) {
if (helper.redisProcess().spawnFailed()) this.skip();
client = redis.createClient.apply(null, args);
var async = true;
client.auth(undefined, function (err, res) {
assert.strictEqual(err.message, 'ERR invalid password');
assert.strictEqual(err.command, 'AUTH');
assert.strictEqual(res, undefined);
async = false;
done();
});
assert(async);
});
it('should emit an error if the password is not correct and no callback has been provided', function (done) {
if (helper.redisProcess().spawnFailed()) this.skip();
client = redis.createClient.apply(null, args);
client.on('error', function (err) {
assert.strictEqual(err.message, 'ERR invalid password');
assert.strictEqual(err.command, 'AUTH');
done();
});
client.auth(234567);
});
it('allows auth to be provided post-hoc with auth method again', function (done) {
if (helper.redisProcess().spawnFailed()) this.skip();
var args = config.configureClient(parser, ip, {
auth_pass: auth
});
client = redis.createClient.apply(null, args);
client.on('ready', function () {
client.auth(auth, helper.isString('OK', done));
});
});
it('does not allow any commands to be processed if not authenticated using no_ready_check true', function (done) {
if (helper.redisProcess().spawnFailed()) this.skip();
var args = config.configureClient(parser, ip, {
no_ready_check: true
});
client = redis.createClient.apply(null, args);
client.on('ready', function () {
client.set('foo', 'bar', function (err, res) {
assert.equal(err.message, 'NOAUTH Authentication required.');
assert.equal(err.code, 'NOAUTH');
assert.equal(err.command, 'SET');
done();
});
});
});
it('does not allow auth to be provided post-hoc with auth method if not authenticated before', function (done) {
if (helper.redisProcess().spawnFailed()) this.skip();
client = redis.createClient.apply(null, args);
client.on('error', function (err) {
assert.equal(err.code, 'NOAUTH');
assert.equal(err.message, 'Ready check failed: NOAUTH Authentication required.');
assert.equal(err.command, 'INFO');
done();
});
});
it('should emit an error if the provided password is faulty', function (done) {
if (helper.redisProcess().spawnFailed()) this.skip();
client = redis.createClient({
password: 'wrong_password',
parser: parser
});
client.once('error', function (err) {
assert.strictEqual(err.message, 'ERR invalid password');
done();
});
});
it('pubsub working with auth', function (done) {
if (helper.redisProcess().spawnFailed()) this.skip();
var args = config.configureClient(parser, ip, {
password: auth
});
client = redis.createClient.apply(null, args);
client.set('foo', 'bar');
client.subscribe('somechannel', 'another channel', function (err, res) {
client.once('ready', function () {
assert.strictEqual(client.pub_sub_mode, 1);
client.get('foo', function (err, res) {
assert(/ERR only \(P\)SUBSCRIBE \/ \(P\)UNSUBSCRIBE/.test(err.message));
done();
});
});
});
client.once('ready', function () {
// Coherent behavior with all other offline commands fires commands before emitting but does not wait till they return
assert.strictEqual(client.pub_sub_mode, 2);
client.ping(function () { // Make sure all commands were properly processed already
client.stream.destroy();
});
});
});
it('individual commands work properly with batch', function (done) {
// quit => might return an error instead of "OK" in the exec callback... (if not connected)
// auth => might return an error instead of "OK" in the exec callback... (if no password is required / still loading on Redis <= 2.4)
// This could be fixed by checking the return value of the callback in the exec callback and
// returning the manipulated [error, result] from the callback.
// There should be a better solution though
var args = config.configureClient(parser, 'localhost', {
noReadyCheck: true
});
client = redis.createClient.apply(null, args);
assert.strictEqual(client.selected_db, undefined);
var end = helper.callFuncAfter(done, 8);
client.on('monitor', function () {
end(); // Should be called for each command after monitor
});
client.batch()
.auth(auth)
.SELECT(5, function (err, res) {
assert.strictEqual(client.selected_db, 5);
assert.strictEqual(res, 'OK');
assert.notDeepEqual(client.serverInfo.db5, { avg_ttl: 0, expires: 0, keys: 1 });
})
.monitor()
.set('foo', 'bar', helper.isString('OK'))
.INFO('stats', function (err, res) {
assert.strictEqual(res.indexOf('# Stats\r\n'), 0);
assert.strictEqual(client.serverInfo.sync_full, '0');
})
.get('foo', helper.isString('bar'))
.subscribe(['foo', 'bar'])
.unsubscribe('foo')
.SUBSCRIBE('/foo', helper.isString('/foo'))
.psubscribe('*')
.quit(helper.isString('OK')) // this might be interesting
.exec(function (err, res) {
res[4] = res[4].substr(0, 9);
assert.deepEqual(res, ['OK', 'OK', 'OK', 'OK', '# Stats\r\n', 'bar', 'bar', 'foo', '/foo', '*', 'OK']);
end();
});
});
});
});
after(function (done) {
if (helper.redisProcess().spawnFailed()) return done();
helper.stopRedis(function () {
helper.startRedis('./conf/redis.conf', done);
});
});
});