From 1dd92d634313617c04e5b3ac291a99621a18a08c Mon Sep 17 00:00:00 2001 From: vishtree Date: Fri, 3 Jan 2020 10:12:23 +0100 Subject: [PATCH 1/2] CRNS-4: Updating devtoken methode (for compatibility with RN) --- src/signing.js | 69 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 64 insertions(+), 5 deletions(-) diff --git a/src/signing.js b/src/signing.js index c8e1b5cd9..30dca975e 100644 --- a/src/signing.js +++ b/src/signing.js @@ -82,12 +82,71 @@ export function UserFromToken(token) { return data.user_id; } -function encodeBase64(s) { - if (typeof window !== 'undefined' && window.btoa) { - return window.btoa(s); - } else { - return Buffer.from(s.toString(), 'binary').toString('base64'); +/** + * Credit: https://github.com/mathiasbynens/base64 + * + * `encode` is designed to be fully compatible with `btoa` as described in the + * HTML Standard: http://whatwg.org/html/webappapis.html#dom-windowbase64-btoa + * + * @param {*} input + * + * @return {string} + */ +function encodeBase64(input) { + input = String(input); + const TABLE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + if (/[^\0-\xFF]/.test(input)) { + // Note: no need to special-case astral symbols here, as surrogates are + // matched, and the input is supposed to only contain ASCII anyway. + throw Error( + 'The string to be encoded contains characters outside of the ' + + 'Latin1 range.', + ); } + const padding = input.length % 3; + const outputArray = []; + let position = -1; + let a; + let b; + let c; + let buffer; + // Make sure any padding is handled outside of the loop. + const length = input.length - padding; + + while (++position < length) { + // Read three bytes, i.e. 24 bits. + a = input.charCodeAt(position) << 16; + b = input.charCodeAt(++position) << 8; + c = input.charCodeAt(++position); + buffer = a + b + c; + // Turn the 24 bits into four chunks of 6 bits each, and append the + // matching character for each of them to the output. + outputArray.push( + TABLE.charAt((buffer >> 18) & 0x3f) + + TABLE.charAt((buffer >> 12) & 0x3f) + + TABLE.charAt((buffer >> 6) & 0x3f) + + TABLE.charAt(buffer & 0x3f), + ); + } + + if (padding === 2) { + a = input.charCodeAt(position) << 8; + b = input.charCodeAt(++position); + buffer = a + b; + outputArray.push( + TABLE.charAt(buffer >> 10) + + TABLE.charAt((buffer >> 4) & 0x3f) + + TABLE.charAt((buffer << 2) & 0x3f) + + '=', + ); + } else if (padding === 1) { + buffer = input.charCodeAt(position); + outputArray.push( + TABLE.charAt(buffer >> 2) + TABLE.charAt((buffer << 4) & 0x3f) + '==', + ); + } + + return outputArray.join(''); } /** From 9cadd0b70720d3e4a18bb5183011298070f33858 Mon Sep 17 00:00:00 2001 From: vishtree Date: Tue, 7 Jan 2020 17:45:59 +0100 Subject: [PATCH 2/2] Using base-64 library for encoding --- package.json | 1 + src/base64.js | 57 ++++++++++++++++++++++ src/index.js | 7 +++ src/signing.js | 94 +----------------------------------- test/test.js | 21 +++++++- types/stream-chat/index.d.ts | 6 ++- yarn.lock | 2 +- 7 files changed, 92 insertions(+), 96 deletions(-) create mode 100644 src/base64.js diff --git a/package.json b/package.json index 41d30dcb2..c4ed8e6cd 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "dependencies": { "@babel/runtime": "^7.3.1", "axios": "^0.18.1", + "base64-js": "^1.3.1", "chai-arrays": "^2.0.0", "cross-fetch": "^3.0.0", "form-data": "^2.3.3", diff --git a/src/base64.js b/src/base64.js new file mode 100644 index 000000000..c8a015fe1 --- /dev/null +++ b/src/base64.js @@ -0,0 +1,57 @@ +const base64 = require('base64-js'); + +// source - https://github.com/beatgammit/base64-js/blob/master/test/convert.js#L72 +const map = (arr, callback) => { + const res = []; + let kValue, mappedValue; + + for (let k = 0, len = arr.length; k < len; k++) { + if (typeof arr === 'string' && !!arr.charAt(k)) { + kValue = arr.charAt(k); + mappedValue = callback(kValue, k, arr); + res[k] = mappedValue; + } else if (typeof arr !== 'string' && k in arr) { + kValue = arr[k]; + mappedValue = callback(kValue, k, arr); + res[k] = mappedValue; + } + } + return res; +}; + +export function encodeBase64(data) { + return base64.fromByteArray( + map(data, function(char) { + return char.charCodeAt(0); + }), + ); +} + +// base-64 decoder throws exception if encoded string is not padded by '=' to make string length +// in multiples of 4. So gonna use our own method for this purpose to keep backwards compatibility +// https://github.com/beatgammit/base64-js/blob/master/index.js#L26 +export function decodeBase64(s) { + const e = {}, + w = String.fromCharCode, + L = s.length; + let i, + b = 0, + c, + x, + l = 0, + a, + r = ''; + const A = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + for (i = 0; i < 64; i++) { + e[A.charAt(i)] = i; + } + for (x = 0; x < L; x++) { + c = e[s.charAt(x)]; + b = (b << 6) + c; + l += 6; + while (l >= 8) { + ((a = (b >>> (l -= 8)) & 0xff) || x < L - 2) && (r += w(a)); + } + } + return r; +} diff --git a/src/index.js b/src/index.js index 76585abf3..24bd031d7 100644 --- a/src/index.js +++ b/src/index.js @@ -1,2 +1,9 @@ export { StreamChat } from './client'; +export * from './client_state'; export { logChatPromiseExecution } from './utils'; +export * from './channel'; +export * from './channel_state'; +export * from './permissions'; +export * from './events'; +export * from './signing'; +export * from './base64.js'; diff --git a/src/signing.js b/src/signing.js index 30dca975e..d9d66097e 100644 --- a/src/signing.js +++ b/src/signing.js @@ -1,5 +1,6 @@ import jwt from 'jsonwebtoken'; import crypto from 'crypto'; +import { encodeBase64, decodeBase64 } from './base64'; /** * Creates the JWT token that can be used for a UserSession @@ -42,32 +43,6 @@ export function JWTServerToken(apiSecret, jwtOptions = {}) { return jwt.sign(payload, apiSecret, opts); } -function decodeBase64(s) { - const e = {}, - w = String.fromCharCode, - L = s.length; - let i, - b = 0, - c, - x, - l = 0, - a, - r = ''; - const A = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; - for (i = 0; i < 64; i++) { - e[A.charAt(i)] = i; - } - for (x = 0; x < L; x++) { - c = e[s.charAt(x)]; - b = (b << 6) + c; - l += 6; - while (l >= 8) { - ((a = (b >>> (l -= 8)) & 0xff) || x < L - 2) && (r += w(a)); - } - } - return r; -} - /** * @return {string} */ @@ -82,73 +57,6 @@ export function UserFromToken(token) { return data.user_id; } -/** - * Credit: https://github.com/mathiasbynens/base64 - * - * `encode` is designed to be fully compatible with `btoa` as described in the - * HTML Standard: http://whatwg.org/html/webappapis.html#dom-windowbase64-btoa - * - * @param {*} input - * - * @return {string} - */ -function encodeBase64(input) { - input = String(input); - const TABLE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; - if (/[^\0-\xFF]/.test(input)) { - // Note: no need to special-case astral symbols here, as surrogates are - // matched, and the input is supposed to only contain ASCII anyway. - throw Error( - 'The string to be encoded contains characters outside of the ' + - 'Latin1 range.', - ); - } - const padding = input.length % 3; - const outputArray = []; - let position = -1; - let a; - let b; - let c; - let buffer; - // Make sure any padding is handled outside of the loop. - const length = input.length - padding; - - while (++position < length) { - // Read three bytes, i.e. 24 bits. - a = input.charCodeAt(position) << 16; - b = input.charCodeAt(++position) << 8; - c = input.charCodeAt(++position); - buffer = a + b + c; - // Turn the 24 bits into four chunks of 6 bits each, and append the - // matching character for each of them to the output. - outputArray.push( - TABLE.charAt((buffer >> 18) & 0x3f) + - TABLE.charAt((buffer >> 12) & 0x3f) + - TABLE.charAt((buffer >> 6) & 0x3f) + - TABLE.charAt(buffer & 0x3f), - ); - } - - if (padding === 2) { - a = input.charCodeAt(position) << 8; - b = input.charCodeAt(++position); - buffer = a + b; - outputArray.push( - TABLE.charAt(buffer >> 10) + - TABLE.charAt((buffer >> 4) & 0x3f) + - TABLE.charAt((buffer << 2) & 0x3f) + - '=', - ); - } else if (padding === 1) { - buffer = input.charCodeAt(position); - outputArray.push( - TABLE.charAt(buffer >> 2) + TABLE.charAt((buffer << 4) & 0x3f) + '==', - ); - } - - return outputArray.join(''); -} - /** * * @param userId {string} the id of the user diff --git a/test/test.js b/test/test.js index 479a0b590..6db6a85ae 100644 --- a/test/test.js +++ b/test/test.js @@ -4,7 +4,7 @@ import chai from 'chai'; import chaiAsPromised from 'chai-as-promised'; import chaiLike from 'chai-like'; import Immutable from 'seamless-immutable'; -import { StreamChat } from '../src'; +import { StreamChat, decodeBase64, encodeBase64 } from '../src'; import { expectHTTPErrorCode } from './utils'; import fs from 'fs'; import assertArrays from 'chai-arrays'; @@ -586,6 +586,25 @@ describe('Chat', function() { }); }); + describe('base64', function() { + it('encodes correctly', function() { + const testCases = [ + 'vishal', + 'vishy', + 'jaap', + 'eugene', + 'luke=', + 'Marcello!?', + ]; + + testCases.forEach(name => { + const b64str = encodeBase64(name); + const str = decodeBase64(b64str); + expect(str).to.equal(name); + }); + }); + }); + describe('Auth', function() { it('Token based auth', async function() { const token = createUserToken('daenerys'); diff --git a/types/stream-chat/index.d.ts b/types/stream-chat/index.d.ts index e0340dc58..4ec10dac0 100644 --- a/types/stream-chat/index.d.ts +++ b/types/stream-chat/index.d.ts @@ -342,7 +342,7 @@ export class Channel { unbanUser(targetUserID: string): Promise; on(callbackOrString: string, callbackOrNothing: any): void; off(callbackOrString: string, callbackOrNothing: any): void; - hide(userId?: string, clearHistory?: bool): Promise; + hide(userId?: string, clearHistory?: boolean): Promise; show(userId?: string): Promise; } @@ -431,6 +431,10 @@ export function DevToken(userId: string): string; export function CheckSignature(body: any, secret: string, signature: string): boolean; +export function encodeBase64(s: string): string; + +export function decodeBase64(s: string): string; + export function isValidEventType(eventType: string): boolean; export function logChatPromiseExecution(promise: Promise, name: string): void; diff --git a/yarn.lock b/yarn.lock index f071ab1c5..0d058fdf9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1351,7 +1351,7 @@ base64-arraybuffer@^0.1.5: resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8" integrity sha1-c5JncZI7Whl0etZmqlzUv5xunOg= -base64-js@^1.1.2: +base64-js@^1.1.2, base64-js@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==