Skip to content

Commit 199fb2b

Browse files
committed
Bind 2 stream management
1 parent c3c8672 commit 199fb2b

File tree

8 files changed

+183
-63
lines changed

8 files changed

+183
-63
lines changed

Makefile

+1-2
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,9 @@ e2e:
3434
$(warning e2e tests require prosody-trunk and luarocks)
3535
cd server && prosodyctl --config prosody.cfg.lua install mod_sasl2 > /dev/null
3636
cd server && prosodyctl --config prosody.cfg.lua install mod_sasl2_bind2 > /dev/null
37-
37+
cd server && prosodyctl --config prosody.cfg.lua install mod_sasl2_sm > /dev/null
3838
# https://github.com/xmppjs/xmpp.js/pull/1006
3939
# cd server && prosodyctl --config prosody.cfg.lua install mod_sasl2_fast > /dev/null
40-
# cd server && prosodyctl --config prosody.cfg.lua install mod_sasl2_sm > /dev/null
4140
npm run e2e
4241

4342
clean:

packages/client-core/src/bind2/bind2.js

+52-12
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,56 @@ import xml from "@xmpp/xml";
22

33
const NS_BIND = "urn:xmpp:bind:0";
44

5-
export default function bind2({ sasl2 }, tag) {
6-
sasl2.use(NS_BIND, async (element) => {
7-
if (!element.is("bind", NS_BIND)) return;
8-
9-
tag = typeof tag === "function" ? await tag() : tag;
10-
11-
return xml(
12-
"bind",
13-
{ xmlns: "urn:xmpp:bind:0" },
14-
tag && xml("tag", null, tag),
15-
);
16-
});
5+
export default function bind2({ sasl2, entity }, tag) {
6+
const features = new Map();
7+
8+
sasl2.use(
9+
NS_BIND,
10+
async (element) => {
11+
if (!element.is("bind", NS_BIND)) return;
12+
13+
tag = typeof tag === "function" ? await tag() : tag;
14+
15+
const sessionFeatures = await getSessionFeatures({ element, features });
16+
17+
return xml(
18+
"bind",
19+
{ xmlns: "urn:xmpp:bind:0" },
20+
tag && xml("tag", null, tag),
21+
...sessionFeatures,
22+
);
23+
},
24+
(element) => {
25+
const aid = element.root().getChildText("authorization-identifier");
26+
if (aid) entity._jid(aid);
27+
28+
for (const child of element.getChildElements()) {
29+
const feature = features.get(child.getNS());
30+
if (!feature?.[1]) continue;
31+
feature?.[1](child);
32+
}
33+
},
34+
);
35+
36+
return {
37+
use(ns, req, res) {
38+
features.set(ns, [req, res]);
39+
},
40+
};
41+
}
42+
43+
function getSessionFeatures({ element, features }) {
44+
const promises = [];
45+
46+
const inline = element.getChild("inline");
47+
if (!inline) return promises;
48+
49+
for (const element of inline.getChildElements()) {
50+
const xmlns = element.attrs.var;
51+
const feature = features.get(xmlns);
52+
if (!feature) continue;
53+
promises.push(feature[0](element));
54+
}
55+
56+
return Promise.all(promises);
1757
}

packages/client/example.js

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ xmpp.on("stanza", async (stanza) => {
3434
xmpp.on("online", async (address) => {
3535
console.log("online as", address.toString());
3636

37+
// return;
3738
// Makes itself available
3839
await xmpp.send(xml("presence"));
3940

packages/client/index.js

+7-3
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ function client(options = {}) {
6060
{ streamFeatures, saslFactory },
6161
createOnAuthenticate(credentials ?? { username, password }, userAgent),
6262
);
63+
64+
// SASL2 inline features
65+
const bind2 = _bind2({ sasl2, entity }, resource);
66+
67+
// Stream features - order matters and define priority
6368
const sasl = _sasl(
6469
{ streamFeatures, saslFactory },
6570
createOnAuthenticate(credentials ?? { username, password }, userAgent),
@@ -68,6 +73,8 @@ function client(options = {}) {
6873
streamFeatures,
6974
entity,
7075
middleware,
76+
bind2,
77+
sasl2,
7178
});
7279
const resourceBinding = _resourceBinding(
7380
{ iqCaller, streamFeatures },
@@ -78,9 +85,6 @@ function client(options = {}) {
7885
streamFeatures,
7986
});
8087

81-
// SASL2 inline features
82-
const bind2 = _bind2({ sasl2 }, resource);
83-
8488
return Object.assign(entity, {
8589
entity,
8690
reconnect,

packages/sasl2/index.js

+16-22
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { encode, decode } from "@xmpp/base64";
22
import SASLError from "@xmpp/sasl/lib/SASLError.js";
3-
import jid from "@xmpp/jid";
43
import xml from "@xmpp/xml";
54

65
// https://xmpp.org/extensions/xep-0388.html
@@ -17,7 +16,8 @@ async function authenticate({
1716
mechanism,
1817
credentials,
1918
userAgent,
20-
sessionFeatures,
19+
streamFeatures,
20+
features,
2121
}) {
2222
const mech = saslFactory.create([mechanism]);
2323
if (!mech) {
@@ -38,7 +38,7 @@ async function authenticate({
3838

3939
return new Promise((resolve, reject) => {
4040
const handler = (element) => {
41-
if (element.attrs.xmlns !== NS) {
41+
if (element.getNS() !== NS) {
4242
return;
4343
}
4444

@@ -61,7 +61,6 @@ async function authenticate({
6161
}
6262

6363
if (element.name === "continue") {
64-
// No tasks supported yet
6564
reject(new Error("continue is not supported yet"));
6665
return;
6766
}
@@ -71,19 +70,14 @@ async function authenticate({
7170
if (additionalData && mech.final) {
7271
mech.final(decode(additionalData));
7372
}
74-
// This jid will be bare unless we do inline bind2 then it will be the bound full jid
75-
const aid = element.getChild("authorization-identifier")?.text();
76-
if (aid) {
77-
if (!entity.jid?.resource) {
78-
// No jid or bare jid, so update it
79-
entity._jid(aid);
80-
} else if (jid(aid).resource) {
81-
// We have a full jid so use it
82-
entity._jid(aid);
83-
}
73+
74+
for (const child of element.getChildElements()) {
75+
const feature = features.get(child.getNS());
76+
if (!feature?.[1]) continue;
77+
feature?.[1](child);
8478
}
79+
8580
resolve(element);
86-
return;
8781
}
8882

8983
entity.removeListener("nonza", handler);
@@ -97,15 +91,14 @@ async function authenticate({
9791
mech.clientFirst &&
9892
xml("initial-response", {}, encode(mech.response(creds))),
9993
userAgent,
100-
...sessionFeatures,
94+
...streamFeatures,
10195
]),
10296
)
10397
.catch(reject);
10498
});
10599
}
106100

107101
export default function sasl2({ streamFeatures, saslFactory }, onAuthenticate) {
108-
// inline
109102
const features = new Map();
110103

111104
streamFeatures.use(
@@ -120,7 +113,7 @@ export default function sasl2({ streamFeatures, saslFactory }, onAuthenticate) {
120113
throw new SASLError("SASL: No compatible mechanism available.");
121114
}
122115

123-
const sessionFeatures = await getSessionFeatures({ element, features });
116+
const streamFeatures = await getStreamFeatures({ element, features });
124117

125118
async function done(credentials, mechanism, userAgent) {
126119
await authenticate({
@@ -129,7 +122,8 @@ export default function sasl2({ streamFeatures, saslFactory }, onAuthenticate) {
129122
mechanism,
130123
credentials,
131124
userAgent,
132-
sessionFeatures,
125+
streamFeatures,
126+
features,
133127
});
134128
}
135129

@@ -141,12 +135,12 @@ export default function sasl2({ streamFeatures, saslFactory }, onAuthenticate) {
141135

142136
return {
143137
use(ns, req, res) {
144-
features.set(ns, req, res);
138+
features.set(ns, [req, res]);
145139
},
146140
};
147141
}
148142

149-
function getSessionFeatures({ element, features }) {
143+
function getStreamFeatures({ element, features }) {
150144
const promises = [];
151145

152146
const inline = element.getChild("inline");
@@ -156,7 +150,7 @@ function getSessionFeatures({ element, features }) {
156150
const xmlns = element.getNS();
157151
const feature = features.get(xmlns);
158152
if (!feature) continue;
159-
promises.push(feature(element));
153+
promises.push(feature[0](element));
160154
}
161155

162156
return Promise.all(promises);

packages/stream-management/README.md

+4
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,7 @@ However `entity.status` is set to `online`.
1111
If the session fails to resume, entity will fallback to regular session establishment in which case `online` event will be emitted.
1212

1313
Automatically responds to acks but does not support requesting acks yet.
14+
15+
## References
16+
17+
[XEP-0198: Stream Management](https://xmpp.org/extensions/xep-0198.html#inline-enable)

0 commit comments

Comments
 (0)