providers.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549
  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 oauthProviders = Object.fromEntries(
  23. Object.entries(providers).flatMap(([name, provider]) => (
  24. provider.oauthProvider != null ? [[name, provider.oauthProvider]] : []
  25. ))
  26. )
  27. const authData = {}
  28. providerNames.forEach((provider) => {
  29. authData[provider] = { accessToken: 'token value' }
  30. })
  31. const token = tokenService.generateEncryptedAuthToken(authData, process.env.COMPANION_SECRET)
  32. const thisOrThat = (value1, value2) => {
  33. if (value1 !== undefined) {
  34. return value1
  35. }
  36. return value2
  37. }
  38. beforeAll(() => {
  39. const url = new URL(defaults.THUMBNAIL_URL)
  40. nock(url.origin).get(url.pathname).reply(200, () => '').persist()
  41. })
  42. afterAll(() => {
  43. nock.cleanAll()
  44. nock.restore()
  45. })
  46. describe('list provider files', () => {
  47. async function runTest (providerName) {
  48. const providerFixture = fixtures.providers[providerName]?.expects ?? {}
  49. return request(authServer)
  50. .get(`/${providerName}/list/${providerFixture.listPath || ''}`)
  51. .set('uppy-auth-token', token)
  52. .expect(200)
  53. .then((res) => {
  54. expect(res.header['i-am']).toBe('http://localhost:3020')
  55. return {
  56. username: res.body.username,
  57. items: res.body.items,
  58. providerFixture,
  59. }
  60. })
  61. }
  62. function expect1({ username, items, providerFixture }) {
  63. expect(username).toBe(defaults.USERNAME)
  64. const item = items[0]
  65. expect(item.isFolder).toBe(false)
  66. expect(item.name).toBe(providerFixture.itemName || defaults.ITEM_NAME)
  67. expect(item.mimeType).toBe(providerFixture.itemMimeType || defaults.MIME_TYPE)
  68. expect(item.id).toBe(providerFixture.itemId || defaults.ITEM_ID)
  69. expect(item.size).toBe(thisOrThat(providerFixture.itemSize, defaults.FILE_SIZE))
  70. expect(item.requestPath).toBe(providerFixture.itemRequestPath || defaults.ITEM_ID)
  71. expect(item.icon).toBe(providerFixture.itemIcon || defaults.THUMBNAIL_URL)
  72. }
  73. test('dropbox', async () => {
  74. nock('https://api.dropboxapi.com').post('/2/users/get_current_account').reply(200, {
  75. name: {
  76. given_name: 'Franz',
  77. surname: 'Ferdinand',
  78. familiar_name: 'Franz',
  79. display_name: 'Franz Ferdinand (Personal)',
  80. abbreviated_name: 'FF',
  81. },
  82. email: defaults.USERNAME,
  83. email_verified: true,
  84. disabled: false,
  85. locale: 'en',
  86. referral_link: 'https://db.tt/ZITNuhtI',
  87. is_paired: true,
  88. })
  89. nock('https://api.dropboxapi.com').post('/2/files/list_folder').reply(200, {
  90. entries: [
  91. {
  92. '.tag': 'file',
  93. name: defaults.ITEM_NAME,
  94. id: defaults.ITEM_ID,
  95. client_modified: '2015-05-12T15:50:38Z',
  96. server_modified: '2015-05-12T15:50:38Z',
  97. rev: 'a1c10ce0dd78',
  98. size: defaults.FILE_SIZE,
  99. path_lower: '/homework/math/prime_numbers.txt',
  100. path_display: '/Homework/math/Prime_Numbers.txt',
  101. is_downloadable: true,
  102. has_explicit_shared_members: false,
  103. content_hash: 'e3b0c44298fc1c149afbf41e4649b934ca49',
  104. file_lock_info: {
  105. is_lockholder: true,
  106. lockholder_name: 'Imaginary User',
  107. created: '2015-05-12T15:50:38Z',
  108. },
  109. },
  110. ],
  111. cursor: 'ZtkX9_EHj3x7PMkVuFIhwKYXEpwpLwyxp9vMKomUhllil9q7eWiAu',
  112. has_more: false,
  113. })
  114. const { username, items, providerFixture } = await runTest('dropbox')
  115. expect1({ username, items, providerFixture })
  116. })
  117. test('box', async () => {
  118. nock('https://api.box.com').get('/2.0/users/me').reply(200, {
  119. login: defaults.USERNAME,
  120. })
  121. nock('https://api.box.com').get('/2.0/folders/0/items?fields=id%2Cmodified_at%2Cname%2Cpermissions%2Csize%2Ctype&limit=1000').reply(200, {
  122. entries: [
  123. {
  124. type: 'file',
  125. name: defaults.ITEM_NAME,
  126. id: defaults.ITEM_ID,
  127. modified_at: '2015-05-12T15:50:38Z',
  128. size: defaults.FILE_SIZE,
  129. },
  130. ],
  131. })
  132. const { username, items, providerFixture } = await runTest('box')
  133. expect1({ username, items, providerFixture })
  134. })
  135. test('drive', async () => {
  136. nock('https://www.googleapis.com').get('/drive/v3/drives?fields=*&pageToken=&pageSize=100').reply(200, {
  137. kind: 'drive#driveList', drives: [],
  138. })
  139. 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, {
  140. kind: 'drive#fileList',
  141. nextPageToken: defaults.NEXT_PAGE_TOKEN,
  142. files: [
  143. {
  144. kind: 'drive#file',
  145. id: defaults.ITEM_ID,
  146. name: defaults.ITEM_NAME,
  147. mimeType: defaults.MIME_TYPE,
  148. iconLink: 'https://drive-thirdparty.googleusercontent.com/16/type/video/mp4',
  149. thumbnailLink: defaults.THUMBNAIL_URL,
  150. modifiedTime: '2016-07-10T20:00:08.096Z',
  151. ownedByMe: true,
  152. permissions: [{ role: 'owner', emailAddress: defaults.USERNAME }],
  153. size: '758051',
  154. },
  155. ],
  156. })
  157. nock('https://www.googleapis.com').get((uri) => uri.includes('about')).reply(200, { user: { emailAddress: 'john.doe@transloadit.com' } })
  158. const { username, items, providerFixture } = await runTest('drive')
  159. // Drive has a virtual "shared-with-me" folder as the first item
  160. const [item0, ...rest] = items
  161. expect(item0.isFolder).toBe(true)
  162. expect(item0.name).toBe('Shared with me')
  163. expect(item0.mimeType).toBe('application/vnd.google-apps.folder')
  164. expect(item0.id).toBe('shared-with-me')
  165. expect(item0.requestPath).toBe('shared-with-me')
  166. expect(item0.icon).toBe('folder')
  167. expect1({ username, items: rest, providerFixture })
  168. })
  169. test('googlephotos', async () => {
  170. nock('https://photoslibrary.googleapis.com').get('/v1/albums?pageSize=50').reply(200, {
  171. albums: [
  172. {
  173. coverPhotoBaseUrl: 'https://test',
  174. title: 'album',
  175. id: '1',
  176. }
  177. ]
  178. })
  179. nock('https://photoslibrary.googleapis.com').get('/v1/sharedAlbums?pageSize=50').reply(200, {
  180. sharedAlbums: [
  181. {
  182. coverPhotoBaseUrl: 'https://test2',
  183. title: 'shared album',
  184. id: '2',
  185. }
  186. ]
  187. })
  188. nock('https://www.googleapis.com').get('/oauth2/v1/userinfo').reply(200, {
  189. email: defaults.USERNAME,
  190. })
  191. const { items } = await runTest('googlephotos')
  192. expect(items[0].isFolder).toBe(true)
  193. expect(items[0].name).toBe('album')
  194. expect(items[0].id).toBe('1')
  195. expect(items[0].requestPath).toBe('1')
  196. expect(items[0].icon).toBe('https://drive-thirdparty.googleusercontent.com/32/type/application/vnd.google-apps.folder')
  197. expect(items[0].thumbnail).toBe('https://test=w300-h300-c')
  198. expect(items[1].isFolder).toBe(true)
  199. expect(items[1].name).toBe('shared album')
  200. expect(items[1].id).toBe('2')
  201. expect(items[1].requestPath).toBe('2')
  202. expect(items[1].icon).toBe('https://drive-thirdparty.googleusercontent.com/32/type/application/vnd.google-apps.folder')
  203. expect(items[1].thumbnail).toBe('https://test2=w300-h300-c')
  204. })
  205. test('facebook', async () => {
  206. nock('https://graph.facebook.com').post('/',
  207. [
  208. 'access_token=token+value',
  209. 'appsecret_proof=ee28d8152093b877f193f5fe84a34544ec27160e7f34c7645d02930b3fa95160',
  210. `batch=${encodeURIComponent('[{"method":"GET","relative_url":"me?fields=email"},{"method":"GET","relative_url":"ALBUM-ID/photos?fields=icon%2Cimages%2Cname%2Cwidth%2Cheight%2Ccreated_time"}]')}`,
  211. ].join('&')
  212. ).reply(200,
  213. [
  214. {
  215. code: 200,
  216. body: JSON.stringify({
  217. name: 'Fiona Fox',
  218. birthday: '01/01/1985',
  219. email: defaults.USERNAME,
  220. }),
  221. },
  222. {
  223. code: 200,
  224. body: JSON.stringify({
  225. data: [
  226. {
  227. images: [
  228. {
  229. height: 1365,
  230. source: defaults.THUMBNAIL_URL,
  231. width: 2048,
  232. },
  233. ],
  234. width: 720,
  235. height: 479,
  236. created_time: '2015-07-17T17:26:50+0000',
  237. id: defaults.ITEM_ID,
  238. },
  239. ],
  240. paging: {},
  241. }),
  242. },
  243. ])
  244. const { username, items, providerFixture } = await runTest('facebook')
  245. expect1({ username, items, providerFixture })
  246. })
  247. test('instagram', async () => {
  248. nock('https://graph.instagram.com').get('/me?fields=username').reply(200, {
  249. id: '17841405793187218',
  250. username: defaults.USERNAME,
  251. })
  252. 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, {
  253. data: [
  254. {
  255. id: defaults.ITEM_ID,
  256. media_type: 'IMAGE',
  257. timestamp: '2017-08-31T18:10:00+0000',
  258. media_url: defaults.THUMBNAIL_URL,
  259. },
  260. ],
  261. })
  262. const { username, items, providerFixture } = await runTest('instagram')
  263. expect1({ username, items, providerFixture })
  264. })
  265. test('onedrive', async () => {
  266. nock('https://graph.microsoft.com').get('/v1.0/me').reply(200, {
  267. userPrincipalName: defaults.USERNAME,
  268. mail: defaults.USERNAME,
  269. })
  270. nock('https://graph.microsoft.com').get('/v1.0/me/drive/root/children?%24expand=thumbnails&%24top=999').reply(200, {
  271. value: [
  272. {
  273. createdDateTime: '2020-01-31T15:40:26.197Z',
  274. id: defaults.ITEM_ID,
  275. lastModifiedDateTime: '2020-01-31T15:40:38.723Z',
  276. name: defaults.ITEM_NAME,
  277. size: defaults.FILE_SIZE,
  278. parentReference: {
  279. driveId: 'DUMMY-DRIVE-ID',
  280. driveType: 'personal',
  281. path: '/drive/root:',
  282. },
  283. file: {
  284. mimeType: defaults.MIME_TYPE,
  285. },
  286. thumbnails: [{
  287. id: '0',
  288. large: {
  289. height: 452,
  290. url: defaults.THUMBNAIL_URL,
  291. width: 800,
  292. },
  293. medium: {
  294. height: 100,
  295. url: defaults.THUMBNAIL_URL,
  296. width: 176,
  297. },
  298. small: {
  299. height: 54,
  300. url: defaults.THUMBNAIL_URL,
  301. width: 96,
  302. },
  303. }],
  304. },
  305. ],
  306. })
  307. const { username, items, providerFixture } = await runTest('onedrive')
  308. expect1({ username, items, providerFixture })
  309. })
  310. test('zoom', async () => {
  311. nock('https://zoom.us').get('/v2/users/me').reply(200, {
  312. id: 'DUMMY-USER-ID',
  313. first_name: 'John',
  314. last_name: 'Doe',
  315. email: 'john.doe@transloadit.com',
  316. timezone: '',
  317. dept: '',
  318. created_at: '2020-07-21T09:13:30Z',
  319. last_login_time: '2020-10-12T07:55:02Z',
  320. group_ids: [],
  321. im_group_ids: [],
  322. account_id: 'DUMMY-ACCOUNT-ID',
  323. language: 'en-US',
  324. })
  325. nockZoomRecordings()
  326. const { username, items, providerFixture } = await runTest('zoom')
  327. expect1({ username, items, providerFixture })
  328. })
  329. })
  330. describe('provider file gets downloaded from', () => {
  331. async function runTest (providerName) {
  332. const providerFixture = fixtures.providers[providerName]?.expects ?? {}
  333. const res = await request(authServer)
  334. .post(`/${providerName}/get/${providerFixture.itemRequestPath || defaults.ITEM_ID}`)
  335. .set('uppy-auth-token', token)
  336. .set('Content-Type', 'application/json')
  337. .send({
  338. endpoint: 'http://tusd.tusdemo.net/files',
  339. protocol: 'tus',
  340. })
  341. .expect(200)
  342. expect(res.body.token).toBeTruthy()
  343. }
  344. test('dropbox', async () => {
  345. nock('https://api.dropboxapi.com').post('/2/files/get_metadata').reply(200, { size: defaults.FILE_SIZE })
  346. nock('https://content.dropboxapi.com').post('/2/files/download').reply(200, {})
  347. await runTest('dropbox')
  348. })
  349. test('box', async () => {
  350. nock('https://api.box.com').get(`/2.0/files/${defaults.ITEM_ID}`).reply(200, { size: defaults.FILE_SIZE })
  351. nock('https://api.box.com').get(`/2.0/files/${defaults.ITEM_ID}/content`).reply(200, { size: defaults.FILE_SIZE })
  352. await runTest('box')
  353. })
  354. test('drive', async () => {
  355. nockGoogleDownloadFile()
  356. await runTest('drive')
  357. })
  358. test('googlephotos', async () => {
  359. nock('https://photoslibrary.googleapis.com').get(`/v1/mediaItems/${defaults.ITEM_ID}`).reply(200, {
  360. baseUrl: 'https://lh3.googleusercontent.com/test',
  361. })
  362. nock('https://lh3.googleusercontent.com').get(`/test=d`).reply(200, ' ', { 'content-length': 1 })
  363. await runTest('googlephotos')
  364. })
  365. test('facebook', async () => {
  366. // times(2) because of size request
  367. nock('https://graph.facebook.com').post('/',
  368. [
  369. 'access_token=token+value',
  370. 'appsecret_proof=ee28d8152093b877f193f5fe84a34544ec27160e7f34c7645d02930b3fa95160',
  371. `batch=${encodeURIComponent('[{"method":"GET","relative_url":"DUMMY-FILE-ID?fields=images"}]')}`,
  372. ].join('&')
  373. ).times(2).reply(200,
  374. [{
  375. code: 200,
  376. body: JSON.stringify({
  377. images: [
  378. {
  379. height: 1365,
  380. source: defaults.THUMBNAIL_URL,
  381. width: 2048,
  382. },
  383. ],
  384. id: defaults.ITEM_ID,
  385. }),
  386. }])
  387. await runTest('facebook')
  388. })
  389. test('instagram', async () => {
  390. // times(2) because of size request
  391. nock('https://graph.instagram.com').get(`/${defaults.ITEM_ID}?fields=media_url`).times(2).reply(200, {
  392. id: defaults.ITEM_ID,
  393. media_type: 'IMAGE',
  394. media_url: defaults.THUMBNAIL_URL,
  395. timestamp: '2017-08-31T18:10:00+0000',
  396. })
  397. await runTest('instagram')
  398. })
  399. test('onedrive', async () => {
  400. nock('https://graph.microsoft.com').get(`/v1.0/drives/DUMMY-DRIVE-ID/items/${defaults.ITEM_ID}`).reply(200, {
  401. size: defaults.FILE_SIZE,
  402. })
  403. nock('https://graph.microsoft.com').get(`/v1.0/drives/DUMMY-DRIVE-ID/items/${defaults.ITEM_ID}/content`).reply(200, {})
  404. await runTest('onedrive')
  405. })
  406. test('zoom', async () => {
  407. // times(2) because of size request
  408. nockZoomRecordings({ times: 2 })
  409. nock('https://us02web.zoom.us').get('/rec/download/DUMMY-DOWNLOAD-PATH?access_token=token%20value').reply(200, {})
  410. await runTest('zoom')
  411. })
  412. })
  413. describe('connect to provider', () => {
  414. test.each(providerNames)('connect to %s via grant.js endpoint', async (providerName) => {
  415. const oauthProvider = oauthProviders[providerName]
  416. if (oauthProvider == null) return
  417. await request(authServer)
  418. .get(`/${providerName}/connect?foo=bar`)
  419. .set('uppy-auth-token', token)
  420. .expect(302)
  421. .expect('Location', `http://localhost:3020/connect/${oauthProvider}?state=${OAUTH_STATE}`)
  422. })
  423. })
  424. describe('logout of provider', () => {
  425. async function runTest (providerName) {
  426. const res = await request(authServer)
  427. .get(`/${providerName}/logout/`)
  428. .set('uppy-auth-token', token)
  429. .expect(200)
  430. // only some providers can actually be revoked
  431. const expectRevoked = ['box', 'dropbox', 'drive', 'googlephotos', 'facebook', 'zoom'].includes(providerName)
  432. expect(res.body).toMatchObject({
  433. ok: true,
  434. revoked: expectRevoked,
  435. })
  436. }
  437. test('dropbox', async () => {
  438. nock('https://api.dropboxapi.com').post('/2/auth/token/revoke').reply(200, {})
  439. await runTest('dropbox')
  440. })
  441. test('box', async () => {
  442. nock('https://api.box.com').post('/oauth2/revoke').reply(200, {})
  443. await runTest('box')
  444. })
  445. test('dropbox', async () => {
  446. nock('https://api.dropboxapi.com').post('/2/auth/token/revoke').reply(200, {})
  447. await runTest('dropbox')
  448. })
  449. test('drive', async () => {
  450. nock('https://accounts.google.com').post('/o/oauth2/revoke?token=token+value').reply(200, {})
  451. await runTest('drive')
  452. })
  453. test('googlephotos', async () => {
  454. nock('https://accounts.google.com').post('/o/oauth2/revoke?token=token+value').reply(200, {})
  455. await runTest('googlephotos')
  456. })
  457. test('facebook', async () => {
  458. // times(2) because of size request
  459. nock('https://graph.facebook.com').post('/',
  460. [
  461. 'access_token=token+value',
  462. 'appsecret_proof=ee28d8152093b877f193f5fe84a34544ec27160e7f34c7645d02930b3fa95160',
  463. `batch=${encodeURIComponent('[{"method":"DELETE","relative_url":"me/permissions"}]')}`,
  464. ].join('&')
  465. ).reply(200,
  466. [{
  467. code: 200,
  468. body: JSON.stringify({}),
  469. }])
  470. await runTest('facebook')
  471. })
  472. test('instagram', async () => {
  473. await runTest('instagram')
  474. })
  475. test('onedrive', async () => {
  476. await runTest('onedrive')
  477. })
  478. test('zoom', async () => {
  479. nockZoomRevoke({ key: localZoomKey, secret: localZoomSecret })
  480. await runTest('zoom')
  481. })
  482. })