faye-authentication 0.4.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9a617e82b1d7184dc0ce7016b8736c6e412500f3
4
- data.tar.gz: 640960f50c1a742c704bd4ded7930817caf22ee4
3
+ metadata.gz: 9ced587e82c17d594caf427c14ea7d844ab085af
4
+ data.tar.gz: 55b71c17bb0bbafb9240b42fba0662cccb6f0064
5
5
  SHA512:
6
- metadata.gz: c130a9cef8388dcc2666842339aea83206c3500e155c45b67ab5751ba29a339a8786e95c54cf754aeb8362d78cca94d120f8d2a3151bece77ee44c47e4121609
7
- data.tar.gz: 09ef066a4f2cbaddb41ac92175a0dbe58e9c3d6e8e78cb21c775bf9300a0f8026eea4cf0d89dc0b5a5a09bbaba1aa7f0c7b1d91549ad5db5a3209d8f220dff25
6
+ metadata.gz: fb749a65cbfcf3afd65901272950028c3dee2605d28ec3d2ac6bacd9b6e53eaaf2c547f96d71670a62ad0147db8d00b021a7c38c1cec36922c5d347d7b713530
7
+ data.tar.gz: 9d708c3c18d56b1e42cba5fc83e300f18d0e7e8d49dad06cdebd531fd1c75b3f12e9f130974f665bd7aeda8c1347d559d19c6253a3cfffacde0356ab7b06762c
data/.drone.yml ADDED
@@ -0,0 +1,10 @@
1
+ image: ruby2.0.0
2
+ cache:
3
+ - /tmp/bundler
4
+ env:
5
+ - RAILS_ENV=test
6
+ script:
7
+ - gem install bundler
8
+ - sudo chown ubuntu:ubuntu /tmp/bundler
9
+ - bundle install --path /tmp/bundler
10
+ - bundle exec rake
data/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## 0.5.0
2
+ - Add support for faye 1.1 (unreleased for now)
3
+ - Drop support for faye < 1.1
4
+ - More extensibility regarding public channels, extensions now take an options
5
+ hash with a whitelist lambda / function that will be called with the channel
6
+ name so developers can implement their own logic
7
+
1
8
  ## 0.4.0
2
9
  - Channels beginning by ``/public/`` do not require authentication anymore,
3
10
  However, globbing with public channels still require authentication.
data/Gemfile CHANGED
@@ -1,7 +1,4 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in faye-authentication.gemspec
4
- group :development do
5
- gem 'rspec-eventmachine', '~> 0.1', github: 'jcoglan/rspec-eventmachine'
6
- end
7
4
  gemspec
data/README.md CHANGED
@@ -40,15 +40,8 @@ Or install it yourself as:
40
40
 
41
41
  ### Channels requiring authentication
42
42
 
43
- All channels require authentication, except channels beginning by ``/public/``
44
-
45
- However, globbing, even on ``/public/`` channels will require authentication.
46
-
47
- Example :
48
-
49
- - ``/public/foo`` does not require authentication
50
- - ``/public/bar/*`` requires authentication
51
-
43
+ All channels require authentication by default, however, it is possible to provide
44
+ a lambda to the faye extensions to let them know which channels are public.
52
45
 
53
46
  ### Authentication endpoint requirements
54
47
 
@@ -56,15 +49,14 @@ The endpoint will receive a POST request, and shall return a JSON hash with a ``
56
49
 
57
50
  The parameters sent to the endpoint are the following :
58
51
 
59
- ````
52
+ ```json
60
53
  {
61
- 'message' =>
62
- {
63
- 'channel' => '/foo/bar',
64
- 'clientId' => '123abc'
54
+ "message" : {
55
+ "channel": "/foo/bar",
56
+ "clientId": "123abc"
65
57
  }
66
58
  }
67
- ````
59
+ ```
68
60
 
69
61
  If the endpoint returns an error, the message won't be signed and the server will reject it.
70
62
 
@@ -72,7 +64,7 @@ You can use ``Faye::Authentication.sign`` to generate the signature from the mes
72
64
 
73
65
  Example (For a Rails application)
74
66
 
75
- ````ruby
67
+ ```ruby
76
68
  def auth
77
69
  if current_user.can?(:read, params[:message][:channel])
78
70
  render json: {signature: Faye::Authentication.sign(params[:message].slice(:channel,:clientId), 'your shared secret key')}
@@ -81,38 +73,53 @@ def auth
81
73
  end
82
74
  end
83
75
 
84
- ````
76
+ ```
85
77
 
86
78
  A Ruby HTTP Client is also available for publishing messages to your faye server
87
79
  without the hassle of using EventMachine :
88
80
 
89
- ````ruby
81
+ ```ruby
90
82
  Faye::Authentication::HTTPClient.publish('http://localhost:9290/faye', '/channel', 'data', 'your private key')
91
- ````
83
+ ```
92
84
  ### Javascript client extension
93
85
 
94
86
  Add the extension to your faye client :
95
87
 
96
- ````javascript
88
+ ```javascript
97
89
  var client = new Faye.Client('http://my.server/faye');
98
90
  client.addExtension(new FayeAuthentication(client));
99
- ````
91
+ ```
100
92
 
101
93
  By default, when sending a subscribe request or publishing a message, the extension
102
94
  will issue an AJAX request to ``/faye/auth``
103
95
 
104
96
  If you wish to change the endpoint, you can supply it as the second argument of the extension constructor, the first one being the client :
97
+ ````javascript
98
+ client.addExtension(new FayeAuthentication(client, '/my_custom_auth_endpoint'));
99
+ ````
100
+
101
+ If you want to specify some channels for which you don't want the extension to
102
+ call your endpoint, you can pass an options object with a ``whitelist`` key mapping
103
+ to a function :
104
+
105
+ ````javascript
106
+ function channelWhitelist(channel) {
107
+ // Allow channels beginning with /public but disallow globbing
108
+ return (channel.lastIndexOf('/public/', 0) === 0 && channel.indexOf('*') == -1);
109
+ }
110
+
111
+ client.addExtension(new FayeAuthentication(client, '/faye/auth', {whitelist: channelWhitelist}));
112
+ ````
105
113
 
106
- client.addExtension(new FayeAuthentication(client, '/my_custom_auth_endpoint'));
107
114
 
108
115
  ### Ruby Faye server extension
109
116
 
110
117
  Instanciate the extension with your secret key and add it to the server :
111
118
 
112
- ````ruby
119
+ ```ruby
113
120
  server = Faye::RackAdapter.new(:mount => '/faye', :timeout => 15)
114
121
  server.add_extension Faye::Authentication::ServerExtension.new('your shared secret key')
115
- ````
122
+ ```
116
123
 
117
124
  Faye::Authentication::ServerExtension expect that :
118
125
  - a ``signature`` is present in the message for publish/subscribe request
@@ -121,14 +128,28 @@ Faye::Authentication::ServerExtension expect that :
121
128
 
122
129
  Otherwise Faye Server will refuse the message.
123
130
 
131
+ If you want to specify some channels for which you don't want the extension require
132
+ authentication, you can pass an options hash with a ``whitelist`` key mapping
133
+ to a lambda :
134
+
135
+ ````ruby
136
+ channel_whitelist = lambda do |channel|
137
+ # Allow channels beginning with /public but disallow globbing
138
+ channel.start_with?('/public/') and not channel.include?('*')
139
+ end
140
+
141
+ server = Faye::RackAdapter.new(:mount => '/faye', :timeout => 15)
142
+ server.add_extension Faye::Authentication::ServerExtension.new('your shared secret key', {whitelist: channel_whitelist})
143
+ ````
144
+
124
145
  ### Ruby Faye client extension
125
146
 
126
147
  This extension allows the ruby ``Faye::Client`` to auto-sign its messages before sending them to the server.
127
148
 
128
- ````ruby
149
+ ```ruby
129
150
  client = Faye::Client.new('http://localhost:9292/faye')
130
151
  client.add_extension Faye::Authentication::ClientExtension.new('your shared secret key')
131
- ````
152
+ ```
132
153
 
133
154
  ## Contributing
134
155
 
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.6.0
@@ -1,8 +1,9 @@
1
- function FayeAuthentication(client, endpoint) {
1
+ function FayeAuthentication(client, endpoint, options) {
2
2
  this._client = client;
3
3
  this._endpoint = endpoint || '/faye/auth';
4
4
  this._signatures = {};
5
5
  this._outbox = {};
6
+ this._options = options || {};
6
7
  }
7
8
 
8
9
  FayeAuthentication.prototype.endpoint = function() {
@@ -49,16 +50,18 @@ FayeAuthentication.prototype.outgoing = function(message, callback) {
49
50
  };
50
51
 
51
52
  FayeAuthentication.prototype.authentication_required = function(message) {
52
- var subscription_or_channel = message.subscription || message.channel
53
- return (!this.public_channel(subscription_or_channel) && (message.channel == '/meta/subscribe' || message.channel.lastIndexOf('/meta/', 0) !== 0))
54
- };
55
-
56
- FayeAuthentication.prototype.public_channel = function(channel) {
57
- if (channel.lastIndexOf('/public/', 0) === 0) {
58
- return (channel.indexOf('*') == -1);
59
- } else {
53
+ var subscription_or_channel = message.subscription || message.channel;
54
+ if (message.channel == '/meta/subscribe' || message.channel.lastIndexOf('/meta/', 0) !== 0)
55
+ if(this._options.whitelist) {
56
+ try {
57
+ return (!this._options.whitelist(subscription_or_channel));
58
+ } catch (e) {
59
+ this.error("Error caught when evaluating whitelist function : " + e.message);
60
+ }
61
+ } else
62
+ return (true);
63
+ else
60
64
  return (false);
61
- }
62
65
  };
63
66
 
64
67
  FayeAuthentication.prototype.incoming = function(message, callback) {
@@ -69,8 +72,12 @@ FayeAuthentication.prototype.incoming = function(message, callback) {
69
72
  outbox_message.message.retried = true;
70
73
  delete outbox_message.message.id;
71
74
  delete this._outbox[message.id];
72
- this._client._send(outbox_message.message, callback);
75
+ this._client._sendMessage(outbox_message.message, {}, callback);
73
76
  }
74
77
  else
75
78
  callback(message);
76
79
  };
80
+
81
+ $(function() {
82
+ Faye.extend(FayeAuthentication.prototype, Faye.Logging);
83
+ });
@@ -18,13 +18,14 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.add_runtime_dependency 'jwt', '~> 1'
21
+ spec.add_runtime_dependency 'jwt', '~> 1.2'
22
+ spec.add_runtime_dependency 'faye', '~> 1.0'
22
23
 
23
24
  spec.add_development_dependency "bundler", "~> 1.5"
24
25
  spec.add_development_dependency "rake", '~> 10.3'
25
26
  spec.add_development_dependency 'rspec', '~> 3.0'
27
+ spec.add_development_dependency 'rspec-eventmachine', '~> 0.2'
26
28
  spec.add_development_dependency 'jasmine', '~> 2.0'
27
- spec.add_development_dependency 'faye', '~> 1.0'
28
29
  spec.add_development_dependency 'rack', '~> 1.5'
29
30
  spec.add_development_dependency 'thin', '~> 1.6'
30
31
  spec.add_development_dependency 'webmock', '~> 1.18'
@@ -8,7 +8,7 @@ module Faye
8
8
  end
9
9
 
10
10
  def outgoing(message, callback)
11
- if Faye::Authentication.authentication_required?(message)
11
+ if Faye::Authentication.authentication_required?(message, @options)
12
12
  message['signature'] = Faye::Authentication.sign({channel: message['subscription'] || message['channel'], clientId: message['clientId']}, @secret, @options)
13
13
  end
14
14
  callback.call(message)
@@ -5,12 +5,13 @@ module Faye
5
5
  class ServerExtension
6
6
  include Faye::Logging
7
7
 
8
- def initialize(secret)
8
+ def initialize(secret, options = {})
9
+ @options = options
9
10
  @secret = secret.to_s
10
11
  end
11
12
 
12
13
  def incoming(message, callback)
13
- if Faye::Authentication.authentication_required?(message)
14
+ if Faye::Authentication.authentication_required?(message, @options)
14
15
  begin
15
16
  Faye::Authentication.validate(message['signature'],
16
17
  message['subscription'] || message['channel'],
@@ -1,5 +1,5 @@
1
1
  module Faye
2
2
  module Authentication
3
- VERSION = "0.4.0"
3
+ VERSION = File.read(File.join(File.dirname(__FILE__),'..', '..', '..', 'VERSION') ).strip
4
4
  end
5
5
  end
@@ -1,4 +1,5 @@
1
1
  require 'jwt'
2
+ require 'faye/mixins/logging'
2
3
  require 'faye/authentication/version'
3
4
  require 'faye/authentication/server_extension'
4
5
  require 'faye/authentication/client_extension'
@@ -7,6 +8,9 @@ require 'faye/authentication/engine'
7
8
 
8
9
  module Faye
9
10
  module Authentication
11
+
12
+ extend Faye::Logging
13
+
10
14
  class AuthError < StandardError; end
11
15
  class ExpiredError < AuthError; end
12
16
  class PayloadError < AuthError; end
@@ -19,9 +23,12 @@ module Faye
19
23
 
20
24
  # Return signed payload or raise
21
25
  def self.decode(signature, secret)
22
- payload, _ = JWT.decode(signature, secret) rescue raise(AuthError)
23
- raise ExpiredError if Time.at(payload['exp'].to_i) < Time.now
26
+ payload, _ = JWT.decode(signature, secret)
24
27
  payload
28
+ rescue JWT::ExpiredSignature
29
+ raise ExpiredError
30
+ rescue
31
+ raise AuthError
25
32
  end
26
33
 
27
34
  # Return true if signature is valid and correspond to channel and clientId or raise
@@ -32,13 +39,18 @@ module Faye
32
39
  true
33
40
  end
34
41
 
35
- def self.authentication_required?(message)
42
+ def self.authentication_required?(message, options = {})
36
43
  subscription_or_channel = message['subscription'] || message['channel']
37
- !public_channel?(subscription_or_channel) && (message['channel'] == '/meta/subscribe' || (!(message['channel'].start_with?('/meta/'))))
38
- end
39
-
40
- def self.public_channel?(channel)
41
- channel.start_with?('/public/') and not channel.include?('*')
44
+ return false unless (message['channel'] == '/meta/subscribe' || (!(message['channel'].start_with?('/meta/'))))
45
+ whitelist_proc = options[:whitelist]
46
+ if whitelist_proc
47
+ begin
48
+ return !whitelist_proc.call(subscription_or_channel)
49
+ rescue => e
50
+ error("Error caught when evaluating whitelist lambda : #{e.message}")
51
+ end
52
+ end
53
+ true
42
54
  end
43
55
 
44
56
  end
@@ -14,6 +14,103 @@ describe('faye-authentication', function() {
14
14
 
15
15
  });
16
16
 
17
+ describe('authentication_required', function() {
18
+
19
+ beforeEach(function() {
20
+ this.auth = new FayeAuthentication(new Faye.Client('http://example.com'));
21
+ Faye.logger = null;
22
+ });
23
+
24
+ function sharedExamplesForSubscribeAndPublish() {
25
+ it('returns true if no options is passed', function() {
26
+ expect(this.auth.authentication_required(this.message)).toBe(true);
27
+ });
28
+
29
+ it('calls function with subscription or channel', function() {
30
+ this.auth._options.whitelist = function(message) { return(true); }
31
+ spyOn(this.auth._options, 'whitelist');
32
+ this.auth.authentication_required(this.message);
33
+ expect(this.auth._options.whitelist).toHaveBeenCalledWith(this.message.subscription || this.message.channel);
34
+ });
35
+
36
+ it('logs error if the function throws', function() {
37
+ this.auth._options.whitelist = function(message) { throw new Error("boom"); }
38
+ Faye.logger = {error: function() {}};
39
+ spyOn(Faye.logger, 'error');
40
+ this.auth.authentication_required(this.message);
41
+ expect(Faye.logger.error).toHaveBeenCalledWith('[Faye] Error caught when evaluating whitelist function : boom');
42
+ });
43
+
44
+ it ('returns false if function returns true', function() {
45
+ this.auth._options.whitelist = function(message) { return(true); }
46
+ expect(this.auth.authentication_required(this.message)).toBe(false);
47
+ });
48
+
49
+ it ('returns true if function returns false', function() {
50
+ this.auth._options.whitelist = function(message) { return(false); }
51
+ expect(this.auth.authentication_required(this.message)).toBe(true);
52
+ });
53
+ }
54
+
55
+ function sharedExamplesForMetaExceptPublish() {
56
+ it('returns false if no options is passed', function() {
57
+ expect(this.auth.authentication_required(this.message)).toBe(false);
58
+ });
59
+
60
+ it ('returns false if function returns true', function() {
61
+ this.auth._options.whitelist = function(message) { return(true); }
62
+ expect(this.auth.authentication_required(this.message)).toBe(false);
63
+ });
64
+
65
+ it ('returns false if function returns false', function() {
66
+ this.auth._options.whitelist = function(message) { return(false); }
67
+ expect(this.auth.authentication_required(this.message)).toBe(false);
68
+ });
69
+ }
70
+
71
+ describe('publish', function() {
72
+
73
+ beforeEach(function() {
74
+ this.message = {'channel': '/foobar'};
75
+ });
76
+
77
+ sharedExamplesForSubscribeAndPublish();
78
+ });
79
+
80
+ describe('subscribe', function() {
81
+ beforeEach(function() {
82
+ this.message = {'channel': '/meta/subscribe', 'subscription': '/foobar'};
83
+ });
84
+
85
+ sharedExamplesForSubscribeAndPublish();
86
+ });
87
+
88
+ describe('handshake', function() {
89
+ beforeEach(function() {
90
+ this.message = {'channel': '/meta/handshake'};
91
+ });
92
+
93
+ sharedExamplesForMetaExceptPublish();
94
+ });
95
+
96
+ describe('connect', function() {
97
+ beforeEach(function() {
98
+ this.message = {'channel': '/meta/connect'};
99
+ });
100
+
101
+ sharedExamplesForMetaExceptPublish();
102
+ });
103
+
104
+ describe('unsubscribe', function() {
105
+ beforeEach(function() {
106
+ this.message = {'channel': '/meta/unsubscribe', 'handshake': '/foobar'};
107
+ });
108
+
109
+ sharedExamplesForMetaExceptPublish();
110
+ });
111
+
112
+ });
113
+
17
114
  describe('extension', function() {
18
115
  beforeEach(function() {
19
116
  jasmine.Ajax.install();
@@ -54,87 +151,93 @@ describe('faye-authentication', function() {
54
151
  'responseText': '{"signature": "foobarsignature"}'
55
152
  });
56
153
 
57
- this.fake_transport = {connectionType: "fake", endpoint: {}, send: function() {}};
58
- spyOn(this.fake_transport, 'send');
154
+ this.dispatcher = {connectionType: "fake", sendMessage: function() {}, selectTransport: function() { }};
155
+ spyOn(this.dispatcher, 'sendMessage');
156
+ spyOn(this.dispatcher, 'selectTransport');
157
+ Faye.extend(this.dispatcher, Faye.Publisher)
59
158
  });
60
159
 
61
160
  it('should add the signature to subscribe message', function(done) {
62
161
  var self = this;
63
162
 
64
163
  this.client.handshake(function() {
65
- self.client._transport = self.fake_transport
164
+ self.client._dispatcher = self.dispatcher;
66
165
  self.client.subscribe('/foobar');
67
- }, this.client);
68
166
 
69
- setTimeout(function() {
70
- var calls = self.fake_transport.send.calls.all();
71
- var last_call = calls[calls.length - 1];
72
- var message = last_call.args[0].message;
73
- expect(message.channel).toBe('/meta/subscribe');
74
- expect(message.signature).toBe('foobarsignature');
75
- done();
76
- }, 500);
167
+ setTimeout(function() {
168
+ var calls = self.dispatcher.sendMessage.calls.all();
169
+ var last_call = calls[calls.length - 1];
170
+ var message = last_call.args[0];
171
+ expect(message.channel).toBe('/meta/subscribe');
172
+ expect(message.signature).toBe('foobarsignature');
173
+ done();
174
+ }, 300);
77
175
 
176
+ }, this.client);
78
177
  });
79
178
 
80
179
  it('should add the signature to publish message', function(done) {
81
180
  var self = this;
82
181
 
83
182
  this.client.handshake(function() {
84
- self.client._transport = self.fake_transport
183
+ self.client._dispatcher = self.dispatcher;
85
184
  self.client.publish('/foobar', {text: 'hallo'});
86
- }, this.client);
87
185
 
88
- setTimeout(function() {
89
- var calls = self.fake_transport.send.calls.all();
90
- var last_call = calls[calls.length - 1];
91
- var message = last_call.args[0].message;
92
- expect(message.channel).toBe('/foobar');
93
- expect(message.signature).toBe('foobarsignature');
94
- done();
95
- }, 500);
186
+ setTimeout(function() {
187
+ var calls = self.dispatcher.sendMessage.calls.all();
188
+ var last_call = calls[calls.length - 1];
189
+ var message = last_call.args[0];
190
+ expect(message.channel).toBe('/foobar');
191
+ expect(message.signature).toBe('foobarsignature');
192
+ done();
193
+ }, 300);
194
+
195
+ }, this.client);
96
196
  });
97
197
 
98
198
  it('preserves messages integrity', function(done) {
99
199
  var self = this;
100
200
 
101
201
  this.client.handshake(function() {
102
- self.client._transport = self.fake_transport
202
+ self.client._dispatcher = self.dispatcher;
103
203
  self.client.publish('/foo', {text: 'hallo'});
104
204
  self.client.subscribe('/foo');
105
- }, this.client);
106
205
 
107
- setTimeout(function() {
108
- var calls = self.fake_transport.send.calls.all();
109
- var subscribe_call = calls[calls.length - 1];
110
- var publish_call = calls[calls.length - 2];
111
- var subscribe_message = subscribe_call.args[0].message;
112
- var publish_message = publish_call.args[0].message;
113
- expect(publish_message.channel).toBe('/foo');
114
- expect(subscribe_message.channel).toBe('/meta/subscribe');
115
- expect(publish_message.signature).toBe('foobarsignature');
116
- expect(subscribe_message.signature).toBe('foobarsignature');
117
- done();
118
- }, 500);
119
- });
206
+ setTimeout(function() {
207
+ var calls = self.dispatcher.sendMessage.calls.all();
208
+ var subscribe_call = calls[calls.length - 1];
209
+ var publish_call = calls[calls.length - 2];
210
+ var subscribe_message = subscribe_call.args[0];
211
+ var publish_message = publish_call.args[0];
212
+ expect(publish_message.channel).toBe('/foo');
213
+ expect(subscribe_message.channel).toBe('/meta/subscribe');
214
+ expect(publish_message.signature).toBe('foobarsignature');
215
+ expect(subscribe_message.signature).toBe('foobarsignature');
216
+ done();
217
+ }, 300);
120
218
 
219
+ }, this.client);
220
+ });
121
221
 
122
- it('does not add the signature to a public message', function(done) {
222
+ it('does not add the signature if authentication is not required', function(done) {
123
223
  var self = this;
124
224
 
225
+ spyOn(this.auth, 'authentication_required').and.returnValue(false);
226
+
125
227
  this.client.handshake(function() {
126
- self.client._transport = self.fake_transport
127
- self.client.publish('/public/foo', {text: 'hallo'});
228
+ self.client._dispatcher = self.dispatcher;
229
+ self.client.publish('/foobar', {text: 'hallo'});
230
+
231
+ setTimeout(function() {
232
+ var calls = self.dispatcher.sendMessage.calls.all();
233
+ var last_call = calls[calls.length - 1];
234
+ var message = last_call.args[0];
235
+ expect(message.channel).toBe('/foobar');
236
+ expect(message.signature).toBe(undefined);
237
+ done();
238
+ }, 300);
128
239
  }, this.client);
129
240
 
130
- setTimeout(function() {
131
- var calls = self.fake_transport.send.calls.all();
132
- var last_call = calls[calls.length - 1];
133
- var message = last_call.args[0].message;
134
- expect(message.channel).toBe('/public/foo');
135
- expect(message.signature).toBe(undefined);
136
- done();
137
- }, 500);
138
241
  });
139
242
 
140
243
  });