Skip to content

Commit

Permalink
Provide an sasl2 user-agent API
Browse files Browse the repository at this point in the history
  • Loading branch information
sonnyp committed Dec 23, 2024
1 parent f6c77ed commit 6835611
Show file tree
Hide file tree
Showing 8 changed files with 74 additions and 67 deletions.
8 changes: 7 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,13 @@ make ci

Good luck and feel free to ask for help in https://github.com/xmppjs/xmpp.js/discussions

# Maintenance
## Design philosophy

xmpp.js is a high level XMPP library. Learning about XMPP is required to use it. While it provides helpers for complex mechanisms such as authentication or transports, it doesn't attempt to abstract XMPP or XML.

As such, simple XMPP semantics shouldn't be replaced with JavaScript APIs when a simple XML element can express them.

## Maintenance

## Release a new version

Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# xmpp.js

> XMPP is an open technology for real-time communication, which powers a wide range of applications including instant messaging, presence, multi-party chat, voice and video calls, collaboration, lightweight middleware, content syndication, and generalized routing of XML data.
> XMPP is the Extensible Messaging and Presence Protocol, a set of open technologies for instant messaging, presence, multi-party chat, voice and video calls, collaboration, lightweight middleware, content syndication, and generalized routing of XML data.
> [xmpp.org/about-xmpp/technology-overview/](https://xmpp.org/about/technology-overview.html)
[xmpp.org/about-xmpp/technology-overview/](https://xmpp.org/about/technology-overview.html)

**xmpp.js** is a JavaScript library for [XMPP](http://xmpp.org/).

Expand Down Expand Up @@ -60,7 +60,7 @@ Do you need help with working with xmpp.js? Please reach out to our community by
- [Logitech Harmony Hub library](https://github.com/AirBorne04/harmonyhub)
- [gx-twilio: Bridge between Twilio SMS and XMPP](https://github.com/pesvut/sgx-twilio)

<small> Feel free to send a PR to add your project or organization to this list.</small>
<small>Feel free to send a PR to add your project or organization to this list.</small>

## credits

Expand Down
32 changes: 16 additions & 16 deletions packages/client/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,9 @@ import scramsha1 from "@xmpp/sasl-scram-sha-1";
import plain from "@xmpp/sasl-plain";
import anonymous from "@xmpp/sasl-anonymous";

// In browsers and react-native some packages are excluded
// see package.json and https://metrobundler.dev/docs/configuration/#resolvermainfields
// in which case the default import returns an empty object
function setupIfAvailable(module, ...args) {
if (typeof module !== "function") {
return undefined;
}

return module(...args);
}

function client(options = {}) {
const { resource, credentials, username, password, ...params } = options;
const { clientId, software, device } = params;
const { resource, credentials, username, password, userAgent, ...params } =
options;

const { domain, service } = params;
if (!domain && service) {
Expand Down Expand Up @@ -68,12 +57,12 @@ function client(options = {}) {
const starttls = setupIfAvailable(_starttls, { streamFeatures });
const sasl2 = _sasl2(
{ streamFeatures, saslFactory },
createOnAuthenticate(credentials ?? { username, password }),
{ clientId, software, device },
createOnAuthenticate(credentials ?? { username, password }, userAgent),
);
service;
const sasl = _sasl(
{ streamFeatures, saslFactory },
createOnAuthenticate(credentials ?? { username, password }),
createOnAuthenticate(credentials ?? { username, password }, userAgent),
);
const streamManagement = _streamManagement({
streamFeatures,
Expand Down Expand Up @@ -111,4 +100,15 @@ function client(options = {}) {
});
}

// In browsers and react-native some packages are excluded
// see package.json and https://metrobundler.dev/docs/configuration/#resolvermainfields
// in which case the default import returns an empty object
function setupIfAvailable(module, ...args) {
if (typeof module !== "function") {
return undefined;
}

return module(...args);
}

export { xml, jid, client };
6 changes: 3 additions & 3 deletions packages/client/lib/createOnAuthenticate.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const ANONYMOUS = "ANONYMOUS";

export default function createOnAuthenticate(credentials) {
export default function createOnAuthenticate(credentials, userAgent) {
return async function onAuthenticate(authenticate, mechanisms) {
if (typeof credentials === "function") {
await credentials(authenticate, mechanisms);
Expand All @@ -12,10 +12,10 @@ export default function createOnAuthenticate(credentials) {
!credentials?.password &&
mechanisms.includes(ANONYMOUS)
) {
await authenticate(credentials, ANONYMOUS);
await authenticate(credentials, ANONYMOUS, userAgent);
return;
}

await authenticate(credentials, mechanisms[0]);
await authenticate(credentials, mechanisms[0], userAgent);
};
}
9 changes: 2 additions & 7 deletions packages/sasl/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,8 @@ async function onAuthenticate(authenticate, mechanisms) {
password: await prompt("enter password"),
};
console.debug("authenticating");
try {
await authenticate(credentials, mechanisms[0]);
console.debug("authenticated");
} catch (err) {
console.error(err);
throw err;
}
await authenticate(credentials, mechanisms[0]);
console.debug("authenticated");
}
```

Expand Down
48 changes: 29 additions & 19 deletions packages/sasl2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@ const client = xmpp({
credentials: {
username: "foo",
password: "bar",
clientId: "Some UUID for this client/server pair (optional)",
software: "Name of this software (optional)",
device: "Description of this device (optional)",
},
});
```
Expand All @@ -31,30 +28,43 @@ Uses cases:
- Have the user enter the password every time
- Do not ask for password before connection is made
- Debug authentication
- Using a SASL mechanism with specific requirements (such as FAST)
- Perform an asynchronous operation to get credentials
- Using a SASL mechanism with specific requirements
- Fetch credentials from a secure database

```js
import { xmpp } from "@xmpp/client";

const client = xmpp({
credentials: authenticate,
clientId: "Some UUID for this client/server pair (optional)",
software: "Name of this software (optional)",
device: "Description of this device (optional)",
});

async function authenticate(callback, mechanisms) {
const fast = mechanisms.find((mech) => mech.canFast)?.name;
const mech = mechanisms.find((mech) => mech.canOther)?.name;

return callback(
{
username: await prompt("enter username"),
password: await prompt("enter password"),
requestToken: fast,
},
mech,
async function onAuthenticate(authenticate, mechanisms) {
console.debug("authenticate", mechanisms);
const credentials = {
username: await prompt("enter username"),
password: await prompt("enter password"),
};
console.debug("authenticating");

// userAgent is optional
const userAgent = await getUserAgent();

await authenticate(credentials, mechanisms[0], userAgent);
console.debug("authenticated");
}

async function getUserAgent() {
let id = localStorage.get("user-agent-id");
if (!id) {
id = await crypto.randomUUID();
localStorage.set("user-agent-id", id);
}
return (
// https://xmpp.org/extensions/xep-0388.html#initiation
<user-agent id={id}>
<software>xmpp.js</software>
<device>Sonny's Laptop</device>
</user-agent>
);
}
```
Expand Down
19 changes: 4 additions & 15 deletions packages/sasl2/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,27 +95,15 @@ async function authenticate({
xml("authenticate", { xmlns: NS, mechanism: mech.name }, [
mech.clientFirst &&
xml("initial-response", {}, encode(mech.response(creds))),
(userAgent?.clientId || userAgent?.software || userAgent?.device) &&
xml(
"user-agent",
userAgent.clientId ? { id: userAgent.clientId } : {},
[
userAgent.software && xml("software", {}, userAgent.software),
userAgent.device && xml("device", {}, userAgent.device),
],
),
userAgent,
]),
);

entity.on("nonza", handler);
});
}

export default function sasl2(
{ streamFeatures, saslFactory },
onAuthenticate,
// userAgent,
) {
export default function sasl2({ streamFeatures, saslFactory }, onAuthenticate) {
streamFeatures.use("authentication", NS, async ({ stanza, entity }) => {
const offered = getMechanismNames(stanza);
const supported = saslFactory._mechs.map(({ name }) => name);
Expand All @@ -125,12 +113,13 @@ export default function sasl2(
throw new SASLError("SASL: No compatible mechanism available.");
}

async function done(credentials, mechanism) {
async function done(credentials, mechanism, userAgent) {
await authenticate({
saslFactory,
entity,
mechanism,
credentials,
userAgent,
});
}

Expand Down
13 changes: 10 additions & 3 deletions packages/sasl2/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,19 @@ test("with object credentials", async () => {

test("with function credentials", async () => {
const mech = "PLAIN";
const userAgent = (
<user-agent id="foo">
<software>xmpp.js</software>
<device>Sonny's Laptop</device>
</user-agent>
);

function authenticate(auth, mechanisms) {
function onAuthenticate(authenticate, mechanisms) {
expect(mechanisms).toEqual([mech]);
return auth(credentials, mech);
return authenticate(credentials, mech, userAgent);
}

const { entity } = mockClient({ credentials: authenticate });
const { entity } = mockClient({ credentials: onAuthenticate });

entity.mockInput(
<features xmlns="http://etherx.jabber.org/streams">
Expand All @@ -64,6 +70,7 @@ test("with function credentials", async () => {
expect(await promise(entity, "send")).toEqual(
<authenticate xmlns="urn:xmpp:sasl:2" mechanism={mech}>
<initial-response>AGZvbwBiYXI=</initial-response>
{userAgent}
</authenticate>,
);

Expand Down

0 comments on commit 6835611

Please sign in to comment.