Mercurial > eldonilo > lightstring
comparison lightstring.js @ 63:20da4fb67977
Auth PLAIN as plugin. Several fixes.
author | Sonny Piers <sonny.piers@gmail.com> |
---|---|
date | Wed, 01 Feb 2012 19:24:41 +0100 |
parents | b1e75cdbb0ad |
children | d9f5ae0b6d98 |
comparison
equal
deleted
inserted
replaced
62:b1e75cdbb0ad | 63:20da4fb67977 |
---|---|
124 Lightstring.Connection = function(aService) { | 124 Lightstring.Connection = function(aService) { |
125 if (aService) | 125 if (aService) |
126 this.service = aService; | 126 this.service = aService; |
127 this.handlers = {}; | 127 this.handlers = {}; |
128 this.callbacks = {}; | 128 this.callbacks = {}; |
129 this.on('stream:features', function(stanza) { | |
130 var nodes = stanza.DOM.querySelectorAll('mechanism'); | |
131 //SASL/Auth features | |
132 if (nodes.length > 0) { | |
133 this.emit('mechanisms', stanza); | |
134 var mechanisms = {}; | |
135 for (var i = 0; i < nodes.length; i++) | |
136 mechanisms[nodes[i].textContent] = true; | |
137 | |
138 | |
139 //FIXME support SCRAM-SHA1 && allow specify method preferences | |
140 if ('DIGEST-MD5' in mechanisms) | |
141 this.send( | |
142 "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl'" + | |
143 " mechanism='DIGEST-MD5'/>" | |
144 ); | |
145 else if ('PLAIN' in mechanisms) { | |
146 var token = btoa( | |
147 this.jid + | |
148 '\u0000' + | |
149 this.jid.node + | |
150 '\u0000' + | |
151 this.password | |
152 ); | |
153 this.send( | |
154 "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl'" + | |
155 " mechanism='PLAIN'>" + token + "</auth>" | |
156 ); | |
157 } | |
158 } | |
159 //XMPP features | |
160 else { | |
161 this.emit('features', stanza); | |
162 var that = this; | |
163 //Bind http://xmpp.org/rfcs/rfc3920.html#bind | |
164 var bind = | |
165 "<iq type='set' id='"+Lightstring.newId('sendiq:')+"' xmlns='jabber:client'>" + | |
166 "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>" + | |
167 (this.jid.resource? "<resource>" + this.jid.resource + "</resource>": "") + | |
168 "</bind>" + | |
169 "</iq>"; | |
170 this.send( | |
171 bind, | |
172 //Success | |
173 function(stanza) { | |
174 //Session http://xmpp.org/rfcs/rfc3921.html#session | |
175 this.jid = new Lightstring.JID(stanza.DOM.textContent); | |
176 that.send( | |
177 "<iq type='set' id='"+Lightstring.newId('sendiq:')+"' xmlns='jabber:client'>" + | |
178 "<session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>" + | |
179 "</iq>", | |
180 function() { | |
181 that.emit('connected'); | |
182 } | |
183 ); | |
184 }, | |
185 //Error | |
186 function(stanza) { | |
187 //TODO: Error? | |
188 } | |
189 ); | |
190 } | |
191 }); | |
192 this.on('success', function(stanza) { | |
193 this.send( | |
194 "<stream:stream to='" + this.jid.domain + "'" + | |
195 " xmlns='jabber:client'" + | |
196 " xmlns:stream='http://etherx.jabber.org/streams'" + | |
197 " version='1.0'/>" | |
198 ); | |
199 }); | |
200 this.on('failure', function(stanza) { | |
201 this.emit('conn-error', stanza.DOM.firstChild.tagName); | |
202 }); | |
203 this.on('challenge', function(stanza) { | |
204 //FIXME this is mostly Strophe code | |
205 | |
206 function _quote(str) { | |
207 return '"' + str.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"'; | |
208 }; | |
209 | |
210 var challenge = atob(stanza.DOM.textContent); | |
211 | |
212 var attribMatch = /([a-z]+)=("[^"]+"|[^,"]+)(?:,|$)/; | |
213 | |
214 var cnonce = MD5.hexdigest(Math.random() * 1234567890); | |
215 var realm = ''; | |
216 var host = null; | |
217 var nonce = ''; | |
218 var qop = ''; | |
219 var matches; | |
220 | |
221 while (challenge.match(attribMatch)) { | |
222 matches = challenge.match(attribMatch); | |
223 challenge = challenge.replace(matches[0], ''); | |
224 matches[2] = matches[2].replace(/^"(.+)"$/, '$1'); | |
225 switch (matches[1]) { | |
226 case 'realm': | |
227 realm = matches[2]; | |
228 break; | |
229 case 'nonce': | |
230 nonce = matches[2]; | |
231 break; | |
232 case 'qop': | |
233 qop = matches[2]; | |
234 break; | |
235 case 'host': | |
236 host = matches[2]; | |
237 break; | |
238 } | |
239 } | |
240 | |
241 var digest_uri = 'xmpp/' + this.jid.domain; | |
242 if (host !== null) | |
243 digest_uri = digest_uri + '/' + host; | |
244 var A1 = MD5.hash(this.jid.node + | |
245 ':' + realm + ':' + this.password) + | |
246 ':' + nonce + ':' + cnonce; | |
247 var A2 = 'AUTHENTICATE:' + digest_uri; | |
248 | |
249 var responseText = ''; | |
250 responseText += 'username=' + _quote(this.jid.node) + ','; | |
251 responseText += 'realm=' + _quote(realm) + ','; | |
252 responseText += 'nonce=' + _quote(nonce) + ','; | |
253 responseText += 'cnonce=' + _quote(cnonce) + ','; | |
254 responseText += 'nc="00000001",'; | |
255 responseText += 'qop="auth",'; | |
256 responseText += 'digest-uri=' + _quote(digest_uri) + ','; | |
257 responseText += 'response=' + _quote( | |
258 MD5.hexdigest(MD5.hexdigest(A1) + ':' + | |
259 nonce + ':00000001:' + | |
260 cnonce + ':auth:' + | |
261 MD5.hexdigest(A2))) + ','; | |
262 responseText += 'charset="utf-8"'; | |
263 this.send( | |
264 "<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" + | |
265 btoa(responseText) + | |
266 "</response>"); | |
267 }); | |
268 }; | 129 }; |
269 Lightstring.Connection.prototype = { | 130 Lightstring.Connection.prototype = { |
270 /** | 131 /** |
271 * @function Create and open a websocket then go though the XMPP authentification process. | 132 * @function Create and open a websocket then go though the XMPP authentification process. |
272 * @param {String} [aJid] The JID (Jabber id) to use. | 133 * @param {String} [aJid] The JID (Jabber id) to use. |
317 this.socket.addEventListener('message', function(e) { | 178 this.socket.addEventListener('message', function(e) { |
318 var stanza = new Lightstring.Stanza(e.data); | 179 var stanza = new Lightstring.Stanza(e.data); |
319 | 180 |
320 //TODO node-xmpp-bosh sends a self-closing stream:stream tag; it is wrong! | 181 //TODO node-xmpp-bosh sends a self-closing stream:stream tag; it is wrong! |
321 that.emit('input', stanza); | 182 that.emit('input', stanza); |
322 | 183 |
323 if(!stanza.DOM) | 184 if(!stanza.DOM) |
324 return; | 185 return; |
325 | 186 |
326 that.emit(stanza.DOM.tagName, stanza); | 187 |
327 | 188 var name = stanza.DOM.localName; |
328 if (stanza.DOM.tagName === 'iq') { | 189 if (name === 'features') { |
190 //SASL mechanisms | |
191 if (stanza.DOM.firstChild.localName === 'mechanisms') { | |
192 stanza.mechanisms = []; | |
193 var nodes = stanza.DOM.querySelectorAll('mechanism'); | |
194 for (var i = 0; i < nodes.length; i++) | |
195 stanza.mechanisms.push(nodes[i].textContent); | |
196 that.emit('mechanisms', stanza); | |
197 } | |
198 //XMPP features | |
199 else if (stanza.DOM.firstChild.localName === 'c') { | |
200 //TODO: stanza.features | |
201 that.emit('features', stanza); | |
202 } | |
203 } | |
204 else if (name === 'challenge') { | |
205 | |
206 | |
207 } | |
208 else if (name === 'response') { | |
209 | |
210 | |
211 } | |
212 else if (name === 'success') { | |
213 that.emit('success', stanza); | |
214 } | |
215 else if(name === 'stream') { | |
216 | |
217 | |
218 } | |
219 | |
220 | |
221 | |
222 //Iq callbacks | |
223 else if (name === 'iq') { | |
329 var payload = stanza.DOM.firstChild; | 224 var payload = stanza.DOM.firstChild; |
330 if (payload) | 225 if (payload) |
331 that.emit('iq/' + payload.namespaceURI + ':' + payload.localName, stanza); | 226 that.emit('iq/' + payload.namespaceURI + ':' + payload.localName, stanza); |
332 | 227 |
333 var id = stanza.DOM.getAttributeNS(null, 'id'); | 228 var id = stanza.DOM.getAttributeNS(null, 'id'); |
343 callback.success(stanza); | 238 callback.success(stanza); |
344 else if (type === 'error' && callback.error) | 239 else if (type === 'error' && callback.error) |
345 callback.error(stanza); | 240 callback.error(stanza); |
346 | 241 |
347 delete that.callbacks[id]; | 242 delete that.callbacks[id]; |
348 | |
349 //TODO: really needed? | |
350 } else if (stanza.DOM.tagName === 'message') { | |
351 var payloads = stanza.DOM.children; | |
352 for (var i = 0; i < payloads.length; i++) | |
353 that.emit('message/' + payloads[i].namespaceURI + ':' + payloads[i].localName, stanza); | |
354 } | 243 } |
355 }); | 244 }); |
356 }, | 245 }, |
357 /** | 246 /** |
358 * @function Send a message. | 247 * @function Send a message. |
422 for (var handler in plugin.handlers) | 311 for (var handler in plugin.handlers) |
423 this.on(handler, plugin.handlers[handler]); | 312 this.on(handler, plugin.handlers[handler]); |
424 | 313 |
425 //Methods | 314 //Methods |
426 this[name] = {}; | 315 this[name] = {}; |
427 for (var method in plugins.methods) | 316 for (var method in plugin.methods) |
428 this[name][method].bind(this); | 317 this[name][method].bind(this); |
429 | 318 |
430 if (plugin.init) | 319 if (plugin.init) |
431 plugin.init(); | 320 plugin.init(); |
432 } | 321 } |
439 emit: function(aName, aData) { | 328 emit: function(aName, aData) { |
440 var handlers = this.handlers[aName]; | 329 var handlers = this.handlers[aName]; |
441 if (!handlers) | 330 if (!handlers) |
442 return; | 331 return; |
443 | 332 |
444 if (aData.localName !== 'iq') { | 333 if (aData && aData.DOM && aData.DOM.localName !== 'iq') { |
445 for (var i = 0; i < handlers.length; i++) | 334 for (var i = 0; i < handlers.length; i++) |
446 handlers[i].call(this, aData); | 335 handlers[i].call(this, aData); |
447 | 336 |
448 return; | 337 return; |
449 } | 338 } |