diff --git a/packages/client-core/src/fast/fast.js b/packages/client-core/src/fast/fast.js index 2b3adbbc..74586b5d 100644 --- a/packages/client-core/src/fast/fast.js +++ b/packages/client-core/src/fast/fast.js @@ -19,6 +19,8 @@ export default function fast({ sasl2 }, { saveToken, fetchToken } = {}) { }; Object.assign(fast, { + mechanism: null, + mechanisms: [], async saveToken() { try { await saveToken(); @@ -43,6 +45,10 @@ export default function fast({ sasl2 }, { saveToken, fetchToken } = {}) { streamFeatures, features, }) { + if (isTokenValid({ token, mechanisms: fast.mechanisms })) { + return false; + } + try { await authenticate({ saslFactory: fast.saslFactory, @@ -79,6 +85,7 @@ export default function fast({ sasl2 }, { saveToken, fetchToken } = {}) { function reset() { fast.mechanism = null; + fast.mechanisms = []; } reset(); @@ -93,6 +100,7 @@ export default function fast({ sasl2 }, { saveToken, fetchToken } = {}) { const mechanism = mechanisms[0]; if (!mechanism) return reset(); + fast.mechanisms = mechanisms; fast.mechanism = mechanism; // The rest is handled by @xmpp/sasl2 @@ -101,6 +109,8 @@ export default function fast({ sasl2 }, { saveToken, fetchToken } = {}) { if (element.is("token", NS)) { try { await saveToken({ + // The token is bound by the mechanism + // > Servers MUST bind tokens to the mechanism selected by the client in its original request, and reject attempts to use them with other mechanisms. mechanism: fast.mechanism, token: element.attrs.token, expiry: element.attrs.expiry, @@ -114,3 +124,15 @@ export default function fast({ sasl2 }, { saveToken, fetchToken } = {}) { return fast; } + +export function isTokenValid({ token, mechanisms }) { + // Avoid an error round trip if the server does not support the token mechanism anymore + if (!mechanisms.includes(token.mechanism)) { + return false; + } + // Avoid an error round trip if the token is already expired + if (new Date(token.expiry) <= new Date()) { + return false; + } + return true; +} diff --git a/packages/client-core/src/fast/isTokenValid.test.js b/packages/client-core/src/fast/isTokenValid.test.js new file mode 100644 index 00000000..4448d143 --- /dev/null +++ b/packages/client-core/src/fast/isTokenValid.test.js @@ -0,0 +1,57 @@ +import { isTokenValid } from "./fast.js"; +// eslint-disable-next-line n/no-extraneous-import +import { datetime } from "@xmpp/time"; + +const tomorrow = new Date(); +tomorrow.setDate(tomorrow.getDate() + 1); + +const yesterday = new Date(); +yesterday.setDate(yesterday.getDate() - 1); + +it("returns false if the token.mechanism is not available", async () => { + expect( + isTokenValid({ + mechanisms: ["foo"], + token: { + expires: datetime(tomorrow), + mechanism: "bar", + }, + }), + ); +}); + +it("returns true if the token.mechanism is available", async () => { + expect( + isTokenValid({ + mechanisms: ["foo"], + token: { + expires: datetime(tomorrow), + mechanism: "foo", + }, + }), + ); +}); + +it("returns false if the token is expired", async () => { + expect( + isTokenValid({ + mechanisms: ["foo"], + token: { + expires: datetime(yesterday), + mechanism: "foo", + }, + }), + ); +}); + +it("returns true if the token is not expired", async () => { + expect( + isTokenValid({ + mechanisms: ["foo"], + token: { + expires: datetime(tomorrow), + mechanism: "foo", + }, + }), + ); +});