Skip to content

Commit daf5eef

Browse files
Fix browser PKCE crypto (#1176)
1 parent 2435672 commit daf5eef

6 files changed

Lines changed: 57 additions & 42 deletions

File tree

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "dropbox",
3-
"version": "10.37.0",
3+
"version": "10.37.1",
44
"registry": "npm",
55
"description": "The Dropbox JavaScript SDK is a lightweight, promise based interface to the Dropbox v2 API that works in both nodejs and browser environments.",
66
"main": "cjs/index.js",

rollup.config.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,8 @@ const config = {
1818
output: {
1919
format: 'umd',
2020
sourcemap: (process.env.BUNDLE_TYPE !== 'minified'),
21-
globals: {
22-
crypto: 'crypto',
23-
},
2421
},
25-
external: ['es6-promise/auto', 'crypto'],
22+
external: ['es6-promise/auto'],
2623
plugins: [
2724
babel(),
2825
],

src/auth.js

Lines changed: 13 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import { parseResponse } from './response.js';
1010

1111
let fetch;
1212
let crypto;
13-
let Encoder;
13+
14+
const base64EncodeBytes = (bytes) => btoa(String.fromCharCode.apply(null, bytes));
1415

1516
// Expiration is 300 seconds but needs to be in milliseconds for Date object
1617
const TokenExpirationBuffer = 300 * 1000;
@@ -58,13 +59,7 @@ export default class DropboxAuth {
5859
fetch = typeof globalThis.fetch === 'function'
5960
? globalThis.fetch.bind(globalThis)
6061
: undefined;
61-
crypto = require('crypto'); // eslint-disable-line global-require
62-
}
63-
64-
if (typeof TextEncoder === 'undefined') {
65-
Encoder = require('util').TextEncoder; // eslint-disable-line global-require
66-
} else {
67-
Encoder = TextEncoder;
62+
crypto = globalThis.crypto;
6863
}
6964

7065
this.fetch = options.fetch || fetch;
@@ -183,35 +178,20 @@ export default class DropboxAuth {
183178
}
184179

185180
generateCodeChallenge() {
186-
const encoder = new Encoder();
181+
const encoder = new TextEncoder();
187182
const codeData = encoder.encode(this.codeVerifier);
188-
let codeChallenge;
189-
if (isBrowserEnv() || isWorkerEnv()) {
190-
return crypto.subtle.digest('SHA-256', codeData)
191-
.then((digestedHash) => {
192-
const base64String = btoa(String.fromCharCode.apply(null, new Uint8Array(digestedHash)));
193-
codeChallenge = createBrowserSafeString(base64String).substr(0, 128);
194-
this.codeChallenge = codeChallenge;
195-
});
196-
}
197-
const digestedHash = crypto.createHash('sha256').update(codeData).digest();
198-
codeChallenge = createBrowserSafeString(digestedHash);
199-
this.codeChallenge = codeChallenge;
200-
return Promise.resolve();
183+
return crypto.subtle.digest('SHA-256', codeData)
184+
.then((digestedHash) => {
185+
const base64String = base64EncodeBytes(new Uint8Array(digestedHash));
186+
this.codeChallenge = createBrowserSafeString(base64String).substr(0, 128);
187+
});
201188
}
202189

203190
generatePKCECodes() {
204-
let codeVerifier;
205-
if (isBrowserEnv() || isWorkerEnv()) {
206-
const array = new Uint8Array(PKCELength);
207-
const randomValueArray = crypto.getRandomValues(array);
208-
const base64String = btoa(randomValueArray);
209-
codeVerifier = createBrowserSafeString(base64String).substr(0, 128);
210-
} else {
211-
const randomBytes = crypto.randomBytes(PKCELength);
212-
codeVerifier = createBrowserSafeString(randomBytes).substr(0, 128);
213-
}
214-
this.codeVerifier = codeVerifier;
191+
const array = new Uint8Array(PKCELength);
192+
const randomValueArray = crypto.getRandomValues(array);
193+
const base64String = base64EncodeBytes(randomValueArray);
194+
this.codeVerifier = createBrowserSafeString(base64String).substr(0, 128);
215195

216196
return this.generateCodeChallenge();
217197
}

test/build/browser.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,20 @@ const executeTest = (testContainer) => {
2121
};
2222

2323
describe('Browser Definitions', () => {
24+
describe('Node built-in dependencies', () => {
25+
[
26+
'es/src/auth.js',
27+
'cjs/src/auth.js',
28+
'dist/Dropbox-sdk.js',
29+
'dist/Dropbox-sdk.min.js',
30+
].forEach((buildPath) => {
31+
it(`${buildPath} does not depend on Node crypto or util`, () => {
32+
const build = fs.readFileSync(path.resolve(__dirname, `../../${buildPath}`), 'utf8');
33+
chai.assert.notMatch(build, /require\(['"](?:crypto|util)['"]\)/);
34+
});
35+
});
36+
});
37+
2438
describe('ES Build', () => {
2539
let html;
2640
let dom;

test/unit/auth.js

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -242,8 +242,32 @@ describe('DropboxAuth', () => {
242242
it('saves a new code challenge on Auth obj', () => {
243243
const dbxAuth = new DropboxAuth();
244244
chai.assert.equal(dbxAuth.codeChallenge, undefined);
245-
dbxAuth.generatePKCECodes();
246-
chai.assert.isTrue(!!dbxAuth.codeChallenge);
245+
return dbxAuth.generatePKCECodes()
246+
.then(() => {
247+
chai.assert.isTrue(!!dbxAuth.codeChallenge);
248+
});
249+
});
250+
251+
it('base64 encodes the random bytes used for the code verifier', () => {
252+
const getRandomValuesStub = sinon.stub(globalThis.crypto, 'getRandomValues')
253+
.callsFake((array) => {
254+
for (let i = 0; i < array.length; i += 1) {
255+
array[i] = i;
256+
}
257+
return array;
258+
});
259+
const dbxAuth = new DropboxAuth();
260+
261+
return dbxAuth.generatePKCECodes()
262+
.then(() => {
263+
chai.assert.equal(
264+
dbxAuth.codeVerifier,
265+
'AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0-P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5f',
266+
);
267+
})
268+
.finally(() => {
269+
getRandomValuesStub.restore();
270+
});
247271
});
248272

249273
it('gets called when using PKCE flow', (done) => {
@@ -257,7 +281,7 @@ describe('DropboxAuth', () => {
257281
.catch(done);
258282
});
259283

260-
it('generates valid code challenge from verifier (Node)', (done) => {
284+
it('generates valid code challenge from verifier', (done) => {
261285
const dbxAuth = new DropboxAuth();
262286
const verifier = 'NTUsMjIsMzYsMTY4LDIyLDEzNywyNDMsOTYsMTIxLDIxNSwxNDAsMTYwLDMwLDE1LDIzMSw1NiwzMCwyMTIsMTQyLDIyMywxMzMsMTIsMjI1LDIzOCwxMDcsMjQ1LDM0';
263287
dbxAuth.setCodeVerifier(verifier);

0 commit comments

Comments
 (0)