From 48e9b056ca181b68a4e77ff01fa77ab8ff0e9267 Mon Sep 17 00:00:00 2001 From: Brent Bumann Date: Tue, 28 Sep 2021 17:55:03 -0700 Subject: [PATCH] Add cookie auth and custom header feature --- src/constants.js | 1 + src/dropbox.js | 14 ++++++++++ test/unit/dropbox.js | 62 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+) diff --git a/src/constants.js b/src/constants.js index abd19e2c..6012518b 100644 --- a/src/constants.js +++ b/src/constants.js @@ -6,6 +6,7 @@ export const APP_AUTH = 'app'; export const USER_AUTH = 'user'; export const TEAM_AUTH = 'team'; export const NO_AUTH = 'noauth'; +export const COOKIE = 'cookie'; export const DEFAULT_API_DOMAIN = 'dropboxapi.com'; export const DEFAULT_DOMAIN = 'dropbox.com'; diff --git a/src/dropbox.js b/src/dropbox.js index 57972e7f..4aad6321 100644 --- a/src/dropbox.js +++ b/src/dropbox.js @@ -6,6 +6,7 @@ import { TEAM_AUTH, USER_AUTH, NO_AUTH, + COOKIE, } from './constants.js'; import { routes } from '../lib/routes.js'; import DropboxAuth from './auth.js'; @@ -50,6 +51,8 @@ const b64 = typeof btoa === 'undefined' * should only be used for testing as scaffolding to avoid making network requests. * @arg {String} [options.domainDelimiter] - A custom delimiter to use when separating domain from * subdomain. This should only be used for testing as scaffolding. + * @arg {Object} [options.customHeaders] - An object designed to set custom headers to use + * during a request. */ export default class Dropbox { constructor(options) { @@ -68,6 +71,7 @@ export default class Dropbox { this.domain = options.domain; this.domainDelimiter = options.domainDelimiter; + this.customHeaders = options.customHeaders; Object.assign(this, routes); } @@ -125,6 +129,11 @@ export default class Dropbox { break; case NO_AUTH: break; + case COOKIE: + if (this.auth.accessToken) { + throw new Error('Can\'t use access tokens with cookie auth'); + } + break; default: throw new Error(`Unhandled auth type: ${auth}`); } @@ -206,5 +215,10 @@ export default class Dropbox { if (this.pathRoot) { options.headers['Dropbox-API-Path-Root'] = this.pathRoot; } + if (this.customHeaders) { + for (const header in this.customHeaders) { + options.headers[header] = this.customHeaders[header]; + } + } } } diff --git a/test/unit/dropbox.js b/test/unit/dropbox.js index 31472efd..0ede08e8 100644 --- a/test/unit/dropbox.js +++ b/test/unit/dropbox.js @@ -11,6 +11,7 @@ import { TEAM_AUTH, APP_AUTH, NO_AUTH, + COOKIE, } from '../../src/constants.js'; import { Dropbox, DropboxAuth } from '../../index.js'; @@ -28,6 +29,18 @@ describe('Dropbox', () => { }); }); + describe('customHeaders', () => { + it('can be set in the constructor', () => { + const dbx = new Dropbox({ customHeaders: { foo: 'bar' } }); + chai.assert.equal(dbx.customHeaders.foo, 'bar'); + }); + + it('is undefined if not set in constructor', () => { + const dbx = new Dropbox(); + chai.assert.equal(dbx.customHeaders, undefined); + }); + }); + describe('RPC requests', () => { it('request() calls the correct request method', () => { const dbx = new Dropbox(); @@ -86,6 +99,26 @@ describe('Dropbox', () => { chai.assert.equal(APP_AUTH, dbx.rpcRequest.getCall(0).args[2]); }); + it('completes a cookie auth RPC request when not supplied an access token', () => { + const dbxAuth = new DropboxAuth(); + const dbx = new Dropbox({ auth: dbxAuth }); + const rpcSpy = sinon.spy(dbx, 'rpcRequest'); + dbx.request('path', {}, COOKIE, 'api', RPC) + .catch((error) => { + fail(error); + }); + chai.assert.isTrue(rpcSpy.calledOnce); + chai.assert.equal('path', dbx.rpcRequest.getCall(0).args[0]); + chai.assert.deepEqual({}, dbx.rpcRequest.getCall(0).args[1]); + chai.assert.equal(COOKIE, dbx.rpcRequest.getCall(0).args[2]); + }); + + it('errors if cookie auth is supplied an OAuth token', () => { + const dbxAuth = new DropboxAuth({ accessToken: 'foo' }); + const dbx = new Dropbox({ auth: dbxAuth }); + return chai.assert.isRejected(dbx.rpcRequest('path', {}, COOKIE, 'api'), Error, 'Can\'t use access tokens with cookie auth'); + }); + it('throws an error for invalid request styles', () => { chai.assert.throws( Dropbox.prototype.request.bind(Dropbox, '', {}, 'user', 'api', 'BADTYPE'), @@ -120,6 +153,10 @@ describe('Dropbox', () => { const dbx = new Dropbox(); return chai.assert.isRejected(dbx.uploadRequest('path', {}, NO_AUTH, 'api'), Error, `Unexpected auth type: ${NO_AUTH}`); }); + it('throws an error for cookie auth', () => { + const dbx = new Dropbox(); + return chai.assert.isRejected(dbx.uploadRequest('path', {}, COOKIE, 'api'), Error, `Unexpected auth type: ${COOKIE}`); + }); }); describe('Download Requests', () => { @@ -149,6 +186,11 @@ describe('Dropbox', () => { const dbx = new Dropbox(); return chai.assert.isRejected(dbx.downloadRequest('path', {}, NO_AUTH, 'api'), Error, `Unexpected auth type: ${NO_AUTH}`); }); + + it('throws an error for cookie auth', () => { + const dbx = new Dropbox(); + return chai.assert.isRejected(dbx.downloadRequest('path', {}, COOKIE, 'api'), Error, `Unexpected auth type: ${COOKIE}`); + }); }); describe('pathRoot', () => { @@ -186,5 +228,25 @@ describe('Dropbox', () => { } } }); + + it('sets custom headers correctly', () => { + const dbx = new Dropbox({ + customHeaders: { + foo: 'bar', + milk: 'shake', + cookie: 'hash', + }, + }); + + const fetchOptions = { + headers: {}, + }; + + dbx.setCommonHeaders(fetchOptions); + const { headers } = fetchOptions; + chai.assert.equal(headers.foo, 'bar'); + chai.assert.equal(headers.milk, 'shake'); + chai.assert.equal(headers.cookie, 'hash'); + }); }); });