providers.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. const request = require('supertest')
  2. const nock = require('nock')
  3. const mockOauthState = require('../mockoauthstate')
  4. jest.mock('tus-js-client')
  5. jest.mock('../../src/server/helpers/request', () => {
  6. return {
  7. getURLMeta: () => Promise.resolve({ size: 758051 }),
  8. }
  9. })
  10. jest.mock('../../src/server/helpers/oauth-state', () => mockOauthState())
  11. const fixtures = require('../fixtures')
  12. const { nockGoogleDownloadFile } = require('../fixtures/drive')
  13. const { nockZoomRecordings, nockZoomRevoke, expects: { localZoomKey, localZoomSecret } } = require('../fixtures/zoom')
  14. const defaults = require('../fixtures/constants')
  15. const tokenService = require('../../src/server/helpers/jwt')
  16. const { getServer } = require('../mockserver')
  17. // todo don't share server between tests. rewrite to not use env variables
  18. const authServer = getServer({ COMPANION_CLIENT_SOCKET_CONNECT_TIMEOUT: '0' })
  19. const OAUTH_STATE = 'some-cool-nice-encrytpion'
  20. const providers = require('../../src/server/provider').getDefaultProviders()
  21. const providerNames = Object.keys(providers)
  22. const AUTH_PROVIDERS = {
  23. drive: 'google',
  24. onedrive: 'microsoft',
  25. }
  26. const authData = {}
  27. providerNames.forEach((provider) => {
  28. authData[provider] = { accessToken: 'token value' }
  29. })
  30. const token = tokenService.generateEncryptedAuthToken(authData, process.env.COMPANION_SECRET)
  31. const thisOrThat = (value1, value2) => {
  32. if (value1 !== undefined) {
  33. return value1
  34. }
  35. return value2
  36. }
  37. beforeAll(() => {
  38. const url = new URL(defaults.THUMBNAIL_URL)
  39. nock(url.origin).get(url.pathname).reply(200, () => '').persist()
  40. })
  41. afterAll(() => {
  42. nock.cleanAll()
  43. nock.restore()
  44. })
  45. describe('list provider files', () => {
  46. async function runTest (providerName) {
  47. const providerFixtures = fixtures.providers[providerName].expects
  48. return request(authServer)
  49. .get(`/${providerName}/list/${providerFixtures.listPath || ''}`)
  50. .set('uppy-auth-token', token)
  51. .expect(200)
  52. .then((res) => {
  53. expect(res.header['i-am']).toBe('http://localhost:3020')
  54. expect(res.body.username).toBe(defaults.USERNAME)
  55. const items = [...res.body.items]
  56. // Drive has a virtual "shared-with-me" folder as the first item
  57. if (providerName === 'drive') {
  58. const item0 = items.shift()
  59. expect(item0.isFolder).toBe(true)
  60. expect(item0.name).toBe('Shared with me')
  61. expect(item0.mimeType).toBe('application/vnd.google-apps.folder')
  62. expect(item0.id).toBe('shared-with-me')
  63. expect(item0.requestPath).toBe('shared-with-me')
  64. expect(item0.icon).toBe('folder')
  65. }
  66. const item = items[0]
  67. expect(item.isFolder).toBe(false)
  68. expect(item.name).toBe(providerFixtures.itemName || defaults.ITEM_NAME)
  69. expect(item.mimeType).toBe(providerFixtures.itemMimeType || defaults.MIME_TYPE)
  70. expect(item.id).toBe(providerFixtures.itemId || defaults.ITEM_ID)
  71. expect(item.size).toBe(thisOrThat(providerFixtures.itemSize, defaults.FILE_SIZE))
  72. expect(item.requestPath).toBe(providerFixtures.itemRequestPath || defaults.ITEM_ID)
  73. expect(item.icon).toBe(providerFixtures.itemIcon || defaults.THUMBNAIL_URL)
  74. })
  75. }
  76. test('dropbox', async () => {
  77. nock('https://api.dropboxapi.com').post('/2/users/get_current_account').reply(200, {
  78. name: {
  79. given_name: 'Franz',
  80. surname: 'Ferdinand',
  81. familiar_name: 'Franz',
  82. display_name: 'Franz Ferdinand (Personal)',
  83. abbreviated_name: 'FF',
  84. },
  85. email: defaults.USERNAME,
  86. email_verified: true,
  87. disabled: false,
  88. locale: 'en',
  89. referral_link: 'https://db.tt/ZITNuhtI',
  90. is_paired: true,
  91. })
  92. nock('https://api.dropboxapi.com').post('/2/files/list_folder').reply(200, {
  93. entries: [
  94. {
  95. '.tag': 'file',
  96. name: defaults.ITEM_NAME,
  97. id: defaults.ITEM_ID,
  98. client_modified: '2015-05-12T15:50:38Z',
  99. server_modified: '2015-05-12T15:50:38Z',
  100. rev: 'a1c10ce0dd78',
  101. size: defaults.FILE_SIZE,
  102. path_lower: '/homework/math/prime_numbers.txt',
  103. path_display: '/Homework/math/Prime_Numbers.txt',
  104. is_downloadable: true,
  105. has_explicit_shared_members: false,
  106. content_hash: 'e3b0c44298fc1c149afbf41e4649b934ca49',
  107. file_lock_info: {
  108. is_lockholder: true,
  109. lockholder_name: 'Imaginary User',
  110. created: '2015-05-12T15:50:38Z',
  111. },
  112. },
  113. ],
  114. cursor: 'ZtkX9_EHj3x7PMkVuFIhwKYXEpwpLwyxp9vMKomUhllil9q7eWiAu',
  115. has_more: false,
  116. })
  117. await runTest('dropbox')
  118. })
  119. test('box', async () => {
  120. nock('https://api.box.com').get('/2.0/users/me').reply(200, {
  121. login: defaults.USERNAME,
  122. })
  123. nock('https://api.box.com').get('/2.0/folders/0/items?fields=id%2Cmodified_at%2Cname%2Cpermissions%2Csize%2Ctype&limit=1000').reply(200, {
  124. entries: [
  125. {
  126. type: 'file',
  127. name: defaults.ITEM_NAME,
  128. id: defaults.ITEM_ID,
  129. modified_at: '2015-05-12T15:50:38Z',
  130. size: defaults.FILE_SIZE,
  131. },
  132. ],
  133. })
  134. await runTest('box')
  135. })
  136. test('drive', async () => {
  137. nock('https://www.googleapis.com').get('/drive/v3/drives?fields=*&pageToken=&pageSize=100').reply(200, {
  138. kind: 'drive#driveList', drives: [],
  139. })
  140. nock('https://www.googleapis.com').get('/drive/v3/files?fields=kind%2CnextPageToken%2CincompleteSearch%2Cfiles%28kind%2Cid%2CimageMediaMetadata%2Cname%2CmimeType%2CownedByMe%2Csize%2CmodifiedTime%2CiconLink%2CthumbnailLink%2CteamDriveId%2CvideoMediaMetadata%2CexportLinks%2CshortcutDetails%28targetId%2CtargetMimeType%29%29&q=%28%27root%27+in+parents%29+and+trashed%3Dfalse&pageSize=1000&orderBy=folder%2Cname&includeItemsFromAllDrives=true&supportsAllDrives=true').reply(200, {
  141. kind: 'drive#fileList',
  142. nextPageToken: defaults.NEXT_PAGE_TOKEN,
  143. files: [
  144. {
  145. kind: 'drive#file',
  146. id: defaults.ITEM_ID,
  147. name: defaults.ITEM_NAME,
  148. mimeType: defaults.MIME_TYPE,
  149. iconLink: 'https://drive-thirdparty.googleusercontent.com/16/type/video/mp4',
  150. thumbnailLink: defaults.THUMBNAIL_URL,
  151. modifiedTime: '2016-07-10T20:00:08.096Z',
  152. ownedByMe: true,
  153. permissions: [{ role: 'owner', emailAddress: defaults.USERNAME }],
  154. size: '758051',
  155. },
  156. ],
  157. })
  158. nock('https://www.googleapis.com').get((uri) => uri.includes('about')).reply(200, { user: { emailAddress: 'john.doe@transloadit.com' } })
  159. await runTest('drive')
  160. })
  161. test('facebook', async () => {
  162. nock('https://graph.facebook.com').get('/me?fields=email').reply(200, {
  163. name: 'Fiona Fox',
  164. birthday: '01/01/1985',
  165. email: defaults.USERNAME,
  166. })
  167. nock('https://graph.facebook.com').get('/ALBUM-ID/photos?fields=icon%2Cimages%2Cname%2Cwidth%2Cheight%2Ccreated_time').reply(200, {
  168. data: [
  169. {
  170. images: [
  171. {
  172. height: 1365,
  173. source: defaults.THUMBNAIL_URL,
  174. width: 2048,
  175. },
  176. ],
  177. width: 720,
  178. height: 479,
  179. created_time: '2015-07-17T17:26:50+0000',
  180. id: defaults.ITEM_ID,
  181. },
  182. ],
  183. paging: {},
  184. })
  185. await runTest('facebook')
  186. })
  187. test('instagram', async () => {
  188. nock('https://graph.instagram.com').get('/me?fields=username').reply(200, {
  189. id: '17841405793187218',
  190. username: defaults.USERNAME,
  191. })
  192. nock('https://graph.instagram.com').get('/me/media?fields=id%2Cmedia_type%2Cthumbnail_url%2Cmedia_url%2Ctimestamp%2Cchildren%7Bmedia_type%2Cmedia_url%2Cthumbnail_url%2Ctimestamp%7D').reply(200, {
  193. data: [
  194. {
  195. id: defaults.ITEM_ID,
  196. media_type: 'IMAGE',
  197. timestamp: '2017-08-31T18:10:00+0000',
  198. media_url: defaults.THUMBNAIL_URL,
  199. },
  200. ],
  201. })
  202. await runTest('instagram')
  203. })
  204. test('onedrive', async () => {
  205. nock('https://graph.microsoft.com').get('/v1.0/me').reply(200, {
  206. userPrincipalName: defaults.USERNAME,
  207. mail: defaults.USERNAME,
  208. })
  209. nock('https://graph.microsoft.com').get('/v1.0/me/drive/root/children?%24expand=thumbnails&%24top=999').reply(200, {
  210. value: [
  211. {
  212. createdDateTime: '2020-01-31T15:40:26.197Z',
  213. id: defaults.ITEM_ID,
  214. lastModifiedDateTime: '2020-01-31T15:40:38.723Z',
  215. name: defaults.ITEM_NAME,
  216. size: defaults.FILE_SIZE,
  217. parentReference: {
  218. driveId: 'DUMMY-DRIVE-ID',
  219. driveType: 'personal',
  220. path: '/drive/root:',
  221. },
  222. file: {
  223. mimeType: defaults.MIME_TYPE,
  224. },
  225. thumbnails: [{
  226. id: '0',
  227. large: {
  228. height: 452,
  229. url: defaults.THUMBNAIL_URL,
  230. width: 800,
  231. },
  232. medium: {
  233. height: 100,
  234. url: defaults.THUMBNAIL_URL,
  235. width: 176,
  236. },
  237. small: {
  238. height: 54,
  239. url: defaults.THUMBNAIL_URL,
  240. width: 96,
  241. },
  242. }],
  243. },
  244. ],
  245. })
  246. await runTest('onedrive')
  247. })
  248. test('zoom', async () => {
  249. nock('https://zoom.us').get('/v2/users/me').reply(200, {
  250. id: 'DUMMY-USER-ID',
  251. first_name: 'John',
  252. last_name: 'Doe',
  253. email: 'john.doe@transloadit.com',
  254. timezone: '',
  255. dept: '',
  256. created_at: '2020-07-21T09:13:30Z',
  257. last_login_time: '2020-10-12T07:55:02Z',
  258. group_ids: [],
  259. im_group_ids: [],
  260. account_id: 'DUMMY-ACCOUNT-ID',
  261. language: 'en-US',
  262. })
  263. nockZoomRecordings()
  264. await runTest('zoom')
  265. })
  266. })
  267. describe('provider file gets downloaded from', () => {
  268. async function runTest (providerName) {
  269. const providerFixtures = fixtures.providers[providerName].expects
  270. const res = await request(authServer)
  271. .post(`/${providerName}/get/${providerFixtures.itemRequestPath || defaults.ITEM_ID}`)
  272. .set('uppy-auth-token', token)
  273. .set('Content-Type', 'application/json')
  274. .send({
  275. endpoint: 'http://tusd.tusdemo.net/files',
  276. protocol: 'tus',
  277. })
  278. .expect(200)
  279. expect(res.body.token).toBeTruthy()
  280. }
  281. test('dropbox', async () => {
  282. nock('https://api.dropboxapi.com').post('/2/files/get_metadata').reply(200, { size: defaults.FILE_SIZE })
  283. nock('https://content.dropboxapi.com').post('/2/files/download').reply(200, {})
  284. await runTest('dropbox')
  285. })
  286. test('box', async () => {
  287. nock('https://api.box.com').get(`/2.0/files/${defaults.ITEM_ID}`).reply(200, { size: defaults.FILE_SIZE })
  288. nock('https://api.box.com').get(`/2.0/files/${defaults.ITEM_ID}/content`).reply(200, { size: defaults.FILE_SIZE })
  289. await runTest('box')
  290. })
  291. test('drive', async () => {
  292. // times(2) because of size request
  293. nockGoogleDownloadFile({ times: 2 })
  294. await runTest('drive')
  295. })
  296. test('facebook', async () => {
  297. // times(2) because of size request
  298. nock('https://graph.facebook.com').get(`/${defaults.ITEM_ID}?fields=images`).times(2).reply(200, {
  299. images: [
  300. {
  301. height: 1365,
  302. source: defaults.THUMBNAIL_URL,
  303. width: 2048,
  304. },
  305. ],
  306. id: defaults.ITEM_ID,
  307. })
  308. await runTest('facebook')
  309. })
  310. test('instagram', async () => {
  311. // times(2) because of size request
  312. nock('https://graph.instagram.com').get(`/${defaults.ITEM_ID}?fields=media_url`).times(2).reply(200, {
  313. id: defaults.ITEM_ID,
  314. media_type: 'IMAGE',
  315. media_url: defaults.THUMBNAIL_URL,
  316. timestamp: '2017-08-31T18:10:00+0000',
  317. })
  318. await runTest('instagram')
  319. })
  320. test('onedrive', async () => {
  321. nock('https://graph.microsoft.com').get(`/v1.0/drives/DUMMY-DRIVE-ID/items/${defaults.ITEM_ID}`).reply(200, {
  322. size: defaults.FILE_SIZE,
  323. })
  324. nock('https://graph.microsoft.com').get(`/v1.0/drives/DUMMY-DRIVE-ID/items/${defaults.ITEM_ID}/content`).reply(200, {})
  325. await runTest('onedrive')
  326. })
  327. test('zoom', async () => {
  328. // times(2) because of size request
  329. nockZoomRecordings({ times: 2 })
  330. nock('https://us02web.zoom.us').get('/rec/download/DUMMY-DOWNLOAD-PATH?access_token=token%20value').reply(200, {})
  331. await runTest('zoom')
  332. })
  333. })
  334. describe('connect to provider', () => {
  335. test.each(providerNames)('connect to %s via grant.js endpoint', (providerName) => {
  336. const authProvider = AUTH_PROVIDERS[providerName] || providerName
  337. if (authProvider.authProvider == null) return
  338. request(authServer)
  339. .get(`/${providerName}/connect?foo=bar`)
  340. .set('uppy-auth-token', token)
  341. .expect(302)
  342. .expect('Location', `http://localhost:3020/connect/${authProvider}?state=${OAUTH_STATE}`)
  343. })
  344. })
  345. describe('logout of provider', () => {
  346. async function runTest (providerName) {
  347. const res = await request(authServer)
  348. .get(`/${providerName}/logout/`)
  349. .set('uppy-auth-token', token)
  350. .expect(200)
  351. // only some providers can actually be revoked
  352. const expectRevoked = ['box', 'dropbox', 'drive', 'facebook', 'zoom'].includes(providerName)
  353. expect(res.body).toMatchObject({
  354. ok: true,
  355. revoked: expectRevoked,
  356. })
  357. }
  358. test('dropbox', async () => {
  359. nock('https://api.dropboxapi.com').post('/2/auth/token/revoke').reply(200, {})
  360. await runTest('dropbox')
  361. })
  362. test('box', async () => {
  363. nock('https://api.box.com').post('/oauth2/revoke').reply(200, {})
  364. await runTest('box')
  365. })
  366. test('dropbox', async () => {
  367. nock('https://api.dropboxapi.com').post('/2/auth/token/revoke').reply(200, {})
  368. await runTest('dropbox')
  369. })
  370. test('drive', async () => {
  371. nock('https://accounts.google.com').post('/o/oauth2/revoke?token=token+value').reply(200, {})
  372. await runTest('drive')
  373. })
  374. test('facebook', async () => {
  375. nock('https://graph.facebook.com').delete('/me/permissions').reply(200, {})
  376. await runTest('facebook')
  377. })
  378. test('instagram', async () => {
  379. await runTest('instagram')
  380. })
  381. test('onedrive', async () => {
  382. await runTest('onedrive')
  383. })
  384. test('zoom', async () => {
  385. nockZoomRevoke({ key: localZoomKey, secret: localZoomSecret })
  386. await runTest('zoom')
  387. })
  388. })