From 466826fe05f319a145af16895a27aa5ddb5b50b5 Mon Sep 17 00:00:00 2001 From: Mikael Finstad Date: Wed, 12 Jun 2024 18:24:45 +0200 Subject: [PATCH 1/4] implement facebook app secret proof closes #5245 note that I couldn't get `appsecret_time` working, but it seems to be working without --- .../companion/src/server/provider/Provider.js | 5 +- .../src/server/provider/facebook/index.js | 84 ++++++++++++++----- .../companion/src/server/provider/index.js | 5 +- .../companion/test/__tests__/providers.js | 5 +- 4 files changed, 73 insertions(+), 26 deletions(-) diff --git a/packages/@uppy/companion/src/server/provider/Provider.js b/packages/@uppy/companion/src/server/provider/Provider.js index 43d3a978e9..cfbcfbd973 100644 --- a/packages/@uppy/companion/src/server/provider/Provider.js +++ b/packages/@uppy/companion/src/server/provider/Provider.js @@ -6,13 +6,14 @@ const { MAX_AGE_24H } = require('../helpers/jwt') class Provider { /** * - * @param {{providerName: string, allowLocalUrls: boolean, providerGrantConfig?: object}} options + * @param {{providerName: string, allowLocalUrls: boolean, providerGrantConfig?: object, secret: string}} options */ - constructor ({ allowLocalUrls, providerGrantConfig }) { + constructor ({ allowLocalUrls, providerGrantConfig, secret }) { // Some providers might need cookie auth for the thumbnails fetched via companion this.needsCookieAuth = false this.allowLocalUrls = allowLocalUrls this.providerGrantConfig = providerGrantConfig + this.secret = secret return this } diff --git a/packages/@uppy/companion/src/server/provider/facebook/index.js b/packages/@uppy/companion/src/server/provider/facebook/index.js index b48a24349b..532aab6a57 100644 --- a/packages/@uppy/companion/src/server/provider/facebook/index.js +++ b/packages/@uppy/companion/src/server/provider/facebook/index.js @@ -1,24 +1,15 @@ +const crypto = require('node:crypto'); + const Provider = require('../Provider') const { getURLMeta } = require('../../helpers/request') const logger = require('../../logger') const { adaptData, sortImages } = require('./adapter') const { withProviderErrorHandling } = require('../providerErrors') const { prepareStream } = require('../../helpers/utils') +const { StreamHttpJsonError } = require('../../helpers/utils') const got = require('../../got') -const getClient = async ({ token }) => (await got).extend({ - prefixUrl: 'https://graph.facebook.com', - headers: { - authorization: `Bearer ${token}`, - }, -}) - -async function getMediaUrl ({ token, id }) { - const body = await (await getClient({ token })).get(String(id), { searchParams: { fields: 'images' }, responseType: 'json' }).json() - const sortedImages = sortImages(body.images) - return sortedImages[sortedImages.length - 1].source -} /** * Adapter for API https://developers.facebook.com/docs/graph-api/using-graph-api/ @@ -28,6 +19,49 @@ class Facebook extends Provider { return 'facebook' } + async runRequestBatch({ token, requests }) { + // https://developers.facebook.com/docs/facebook-login/security/#appsecret + // couldn't get `appsecret_time` working, but it seems to be working without it + // const time = Math.floor(Date.now() / 1000) + const appSecretProof = crypto.createHmac('sha256', this.secret) + // .update(`${token}|${time}`) + .update(token) + .digest('hex'); + + const form = new FormData() + form.append('access_token', token) + form.append('appsecret_proof', appSecretProof) + // form.append('appsecret_time', String(time)) + form.append('batch', JSON.stringify(requests)) + + const responsesRaw = await (await got).post('https://graph.facebook.com', { + // @ts-expect-error todo types + body: form, + }).json() + + const responses = responsesRaw.map((response) => ({ ...response, body: JSON.parse(response.body) })) + + responses.forEach((response) => { + if (response.code !== 200) { + throw new StreamHttpJsonError({ statusCode: response.code, responseJson: response.body }) + } + }) + + return responses + } + + async getMediaUrl ({ token, id }) { + const [{ body }] = await this.runRequestBatch({ + token, + requests: [ + { method: 'GET', relative_url: `${id}?${new URLSearchParams({ fields: 'images' }).toString()}` }, + ], + }); + + const sortedImages = sortImages(body.images) + return sortedImages[sortedImages.length - 1].source + } + async list ({ directory, token, query = { cursor: null } }) { return this.#withErrorHandling('provider.facebook.list.error', async () => { const qs = { fields: 'name,cover_photo,created_time,type' } @@ -40,19 +74,23 @@ class Facebook extends Provider { qs.fields = 'icon,images,name,width,height,created_time' } - const client = await getClient({ token }) + const [response1, response2] = await this.runRequestBatch({ + token, + requests: [ + { method: 'GET', relative_url: `me?${new URLSearchParams({ fields: 'email' }).toString()}` }, + { method: 'GET', relative_url: `${path}?${new URLSearchParams(qs)}` }, + ], + }); - const [{ email }, list] = await Promise.all([ - client.get('me', { searchParams: { fields: 'email' }, responseType: 'json' }).json(), - client.get(path, { searchParams: qs, responseType: 'json' }).json(), - ]) + const { email } = response1.body + const list = response2.body return adaptData(list, email, directory, query) }) } async download ({ id, token }) { return this.#withErrorHandling('provider.facebook.download.error', async () => { - const url = await getMediaUrl({ token, id }) + const url = await this.getMediaUrl({ token, id }) const stream = (await got).stream.get(url, { responseType: 'json' }) await prepareStream(stream) return { stream } @@ -68,7 +106,7 @@ class Facebook extends Provider { async size ({ id, token }) { return this.#withErrorHandling('provider.facebook.size.error', async () => { - const url = await getMediaUrl({ token, id }) + const url = await this.getMediaUrl({ token, id }) const { size } = await getURLMeta(url) return size }) @@ -76,7 +114,13 @@ class Facebook extends Provider { async logout ({ token }) { return this.#withErrorHandling('provider.facebook.logout.error', async () => { - await (await getClient({ token })).delete('me/permissions', { responseType: 'json' }).json() + await this.runRequestBatch({ + token, + requests: [ + { method: 'DELETE', relative_url: 'me/permissions' }, + ], + }); + return { revoked: true } }) } diff --git a/packages/@uppy/companion/src/server/provider/index.js b/packages/@uppy/companion/src/server/provider/index.js index c556395b30..6c87f2044b 100644 --- a/packages/@uppy/companion/src/server/provider/index.js +++ b/packages/@uppy/companion/src/server/provider/index.js @@ -41,7 +41,7 @@ module.exports.getProviderMiddleware = (providers, grantConfig) => { const middleware = (req, res, next, providerName) => { const ProviderClass = providers[providerName] if (ProviderClass && validOptions(req.companion.options)) { - const { allowLocalUrls } = req.companion.options + const { allowLocalUrls, providerOptions } = req.companion.options const { oauthProvider } = ProviderClass let providerGrantConfig @@ -51,7 +51,8 @@ module.exports.getProviderMiddleware = (providers, grantConfig) => { req.companion.providerGrantConfig = providerGrantConfig } - req.companion.provider = new ProviderClass({ providerName, providerGrantConfig, allowLocalUrls }) + const { secret } = providerOptions[providerName] + req.companion.provider = new ProviderClass({ secret, providerName, providerGrantConfig, allowLocalUrls }) req.companion.providerClass = ProviderClass } else { logger.warn('invalid provider options detected. Provider will not be loaded', 'provider.middleware.invalid', req.id) diff --git a/packages/@uppy/companion/test/__tests__/providers.js b/packages/@uppy/companion/test/__tests__/providers.js index 6012490549..5f69c5e676 100644 --- a/packages/@uppy/companion/test/__tests__/providers.js +++ b/packages/@uppy/companion/test/__tests__/providers.js @@ -19,8 +19,9 @@ const defaults = require('../fixtures/constants') const tokenService = require('../../src/server/helpers/jwt') const { getServer } = require('../mockserver') +const secret = process.env.COMPANION_SECRET // todo don't share server between tests. rewrite to not use env variables -const authServer = getServer({ COMPANION_CLIENT_SOCKET_CONNECT_TIMEOUT: '0' }) +const authServer = getServer({ COMPANION_CLIENT_SOCKET_CONNECT_TIMEOUT: '0', COMPANION_SECRET: secret }) const OAUTH_STATE = 'some-cool-nice-encrytpion' const providers = require('../../src/server/provider').getDefaultProviders() @@ -34,7 +35,7 @@ const authData = {} providerNames.forEach((provider) => { authData[provider] = { accessToken: 'token value' } }) -const token = tokenService.generateEncryptedAuthToken(authData, process.env.COMPANION_SECRET) +const token = tokenService.generateEncryptedAuthToken(authData, secret) const thisOrThat = (value1, value2) => { if (value1 !== undefined) { From 7679e7994cc464a3eb3eaae4fd5dbc1bce9af64b Mon Sep 17 00:00:00 2001 From: Mikael Finstad Date: Wed, 12 Jun 2024 22:08:08 +0200 Subject: [PATCH 2/4] fix tests use form urlencoded instead for easiser testing --- .../src/server/provider/facebook/index.js | 16 ++- .../companion/test/__tests__/providers.js | 105 ++++++++++++------ packages/@uppy/companion/test/mockserver.js | 3 + 3 files changed, 81 insertions(+), 43 deletions(-) diff --git a/packages/@uppy/companion/src/server/provider/facebook/index.js b/packages/@uppy/companion/src/server/provider/facebook/index.js index 532aab6a57..94371b140e 100644 --- a/packages/@uppy/companion/src/server/provider/facebook/index.js +++ b/packages/@uppy/companion/src/server/provider/facebook/index.js @@ -28,16 +28,14 @@ class Facebook extends Provider { .update(token) .digest('hex'); - const form = new FormData() - form.append('access_token', token) - form.append('appsecret_proof', appSecretProof) - // form.append('appsecret_time', String(time)) - form.append('batch', JSON.stringify(requests)) + const form = { + access_token: token, + appsecret_proof: appSecretProof, + // appsecret_time: String(time), + batch: JSON.stringify(requests), + } - const responsesRaw = await (await got).post('https://graph.facebook.com', { - // @ts-expect-error todo types - body: form, - }).json() + const responsesRaw = await (await got).post('https://graph.facebook.com', { form }).json() const responses = responsesRaw.map((response) => ({ ...response, body: JSON.parse(response.body) })) diff --git a/packages/@uppy/companion/test/__tests__/providers.js b/packages/@uppy/companion/test/__tests__/providers.js index 5f69c5e676..e8894b9ccc 100644 --- a/packages/@uppy/companion/test/__tests__/providers.js +++ b/packages/@uppy/companion/test/__tests__/providers.js @@ -19,9 +19,8 @@ const defaults = require('../fixtures/constants') const tokenService = require('../../src/server/helpers/jwt') const { getServer } = require('../mockserver') -const secret = process.env.COMPANION_SECRET // todo don't share server between tests. rewrite to not use env variables -const authServer = getServer({ COMPANION_CLIENT_SOCKET_CONNECT_TIMEOUT: '0', COMPANION_SECRET: secret }) +const authServer = getServer({ COMPANION_CLIENT_SOCKET_CONNECT_TIMEOUT: '0' }) const OAUTH_STATE = 'some-cool-nice-encrytpion' const providers = require('../../src/server/provider').getDefaultProviders() @@ -35,7 +34,7 @@ const authData = {} providerNames.forEach((provider) => { authData[provider] = { accessToken: 'token value' } }) -const token = tokenService.generateEncryptedAuthToken(authData, secret) +const token = tokenService.generateEncryptedAuthToken(authData, process.env.COMPANION_SECRET) const thisOrThat = (value1, value2) => { if (value1 !== undefined) { @@ -184,29 +183,44 @@ describe('list provider files', () => { }) test('facebook', async () => { - nock('https://graph.facebook.com').get('/me?fields=email').reply(200, { - name: 'Fiona Fox', - birthday: '01/01/1985', - email: defaults.USERNAME, - }) - nock('https://graph.facebook.com').get('/ALBUM-ID/photos?fields=icon%2Cimages%2Cname%2Cwidth%2Cheight%2Ccreated_time').reply(200, { - data: [ - { - images: [ + nock('https://graph.facebook.com').post('/', + [ + 'access_token=token+value', + 'appsecret_proof=ee28d8152093b877f193f5fe84a34544ec27160e7f34c7645d02930b3fa95160', + `batch=${encodeURIComponent('[{"method":"GET","relative_url":"me?fields=email"},{"method":"GET","relative_url":"ALBUM-ID/photos?fields=icon%2Cimages%2Cname%2Cwidth%2Cheight%2Ccreated_time"}]')}`, + ].join('&') + ).reply(200, + [ + { + code: 200, + body: JSON.stringify({ + name: 'Fiona Fox', + birthday: '01/01/1985', + email: defaults.USERNAME, + }), + }, + { + code: 200, + body: JSON.stringify({ + data: [ { - height: 1365, - source: defaults.THUMBNAIL_URL, - width: 2048, + images: [ + { + height: 1365, + source: defaults.THUMBNAIL_URL, + width: 2048, + }, + ], + width: 720, + height: 479, + created_time: '2015-07-17T17:26:50+0000', + id: defaults.ITEM_ID, }, ], - width: 720, - height: 479, - created_time: '2015-07-17T17:26:50+0000', - id: defaults.ITEM_ID, - }, - ], - paging: {}, - }) + paging: {}, + }), + }, + ]) await runTest('facebook') }) @@ -333,16 +347,27 @@ describe('provider file gets downloaded from', () => { test('facebook', async () => { // times(2) because of size request - nock('https://graph.facebook.com').get(`/${defaults.ITEM_ID}?fields=images`).times(2).reply(200, { - images: [ - { - height: 1365, - source: defaults.THUMBNAIL_URL, - width: 2048, - }, - ], - id: defaults.ITEM_ID, - }) + nock('https://graph.facebook.com').post('/', + [ + 'access_token=token+value', + 'appsecret_proof=ee28d8152093b877f193f5fe84a34544ec27160e7f34c7645d02930b3fa95160', + `batch=${encodeURIComponent('[{"method":"GET","relative_url":"DUMMY-FILE-ID?fields=images"}]')}`, + ].join('&') + ).times(2).reply(200, + [{ + code: 200, + body: JSON.stringify({ + images: [ + { + height: 1365, + source: defaults.THUMBNAIL_URL, + width: 2048, + }, + ], + id: defaults.ITEM_ID, + }), + }]) + await runTest('facebook') }) @@ -424,7 +449,19 @@ describe('logout of provider', () => { }) test('facebook', async () => { - nock('https://graph.facebook.com').delete('/me/permissions').reply(200, {}) + // times(2) because of size request + nock('https://graph.facebook.com').post('/', + [ + 'access_token=token+value', + 'appsecret_proof=ee28d8152093b877f193f5fe84a34544ec27160e7f34c7645d02930b3fa95160', + `batch=${encodeURIComponent('[{"method":"DELETE","relative_url":"me/permissions"}]')}`, + ].join('&') + ).reply(200, + [{ + code: 200, + body: JSON.stringify({}), + }]) + await runTest('facebook') }) diff --git a/packages/@uppy/companion/test/mockserver.js b/packages/@uppy/companion/test/mockserver.js index 765306844f..b236e65050 100644 --- a/packages/@uppy/companion/test/mockserver.js +++ b/packages/@uppy/companion/test/mockserver.js @@ -31,6 +31,9 @@ const defaultEnv = { COMPANION_INSTAGRAM_KEY: 'instagram_key', COMPANION_INSTAGRAM_SECRET: 'instagram_secret', + COMPANION_FACEBOOK_KEY: 'facebook_key', + COMPANION_FACEBOOK_SECRET: 'facebook_secret', + COMPANION_ZOOM_KEY: localZoomKey, COMPANION_ZOOM_SECRET: localZoomSecret, COMPANION_ZOOM_VERIFICATION_TOKEN: localZoomVerificationToken, From b9748ec0b3f1b91364ea3505ca9fbd3a005b0dd6 Mon Sep 17 00:00:00 2001 From: Mikael Finstad Date: Wed, 12 Jun 2024 22:12:01 +0200 Subject: [PATCH 3/4] rename confusing error name --- docs/guides/migration-guides.md | 1 + packages/@uppy/companion/src/server/helpers/utils.js | 10 +++++----- .../companion/src/server/provider/facebook/index.js | 4 ++-- .../companion/src/server/provider/providerErrors.js | 2 +- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/guides/migration-guides.md b/docs/guides/migration-guides.md index ac27020c98..81152cf170 100644 --- a/docs/guides/migration-guides.md +++ b/docs/guides/migration-guides.md @@ -29,6 +29,7 @@ These cover all the major Uppy versions and how to migrate to them. - `getProtectedHttpAgent` parameter `blockLocalIPs` changed to `allowLocalIPs` (inverted boolean). - `downloadURL` 2nd (boolean) argument inverted. + - `StreamHttpJsonError` renamed to `HttpError`. ### `@uppy/companion-client` diff --git a/packages/@uppy/companion/src/server/helpers/utils.js b/packages/@uppy/companion/src/server/helpers/utils.js index 627d87fcb1..479be775ba 100644 --- a/packages/@uppy/companion/src/server/helpers/utils.js +++ b/packages/@uppy/companion/src/server/helpers/utils.js @@ -148,7 +148,7 @@ module.exports.decrypt = (encrypted, secret) => { module.exports.defaultGetKey = (req, filename) => `${crypto.randomUUID()}-${filename}` -class StreamHttpJsonError extends Error { +class HttpError extends Error { statusCode responseJson @@ -157,11 +157,11 @@ class StreamHttpJsonError extends Error { super(`Request failed with status ${statusCode}`) this.statusCode = statusCode this.responseJson = responseJson - this.name = 'StreamHttpJsonError' + this.name = 'HttpError' } } -module.exports.StreamHttpJsonError = StreamHttpJsonError +module.exports.HttpError = HttpError module.exports.prepareStream = async (stream) => new Promise((resolve, reject) => { stream @@ -173,7 +173,7 @@ module.exports.prepareStream = async (stream) => new Promise((resolve, reject) = }) .on('error', (err) => { // In this case the error object is not a normal GOT HTTPError where json is already parsed, - // we create our own StreamHttpJsonError error for this case + // we create our own HttpError error for this case if (typeof err.response?.body === 'string' && typeof err.response?.statusCode === 'number') { let responseJson try { @@ -183,7 +183,7 @@ module.exports.prepareStream = async (stream) => new Promise((resolve, reject) = return } - reject(new StreamHttpJsonError({ statusCode: err.response.statusCode, responseJson })) + reject(new HttpError({ statusCode: err.response.statusCode, responseJson })) return } diff --git a/packages/@uppy/companion/src/server/provider/facebook/index.js b/packages/@uppy/companion/src/server/provider/facebook/index.js index 94371b140e..267b21634b 100644 --- a/packages/@uppy/companion/src/server/provider/facebook/index.js +++ b/packages/@uppy/companion/src/server/provider/facebook/index.js @@ -6,7 +6,7 @@ const logger = require('../../logger') const { adaptData, sortImages } = require('./adapter') const { withProviderErrorHandling } = require('../providerErrors') const { prepareStream } = require('../../helpers/utils') -const { StreamHttpJsonError } = require('../../helpers/utils') +const { HttpError } = require('../../helpers/utils') const got = require('../../got') @@ -41,7 +41,7 @@ class Facebook extends Provider { responses.forEach((response) => { if (response.code !== 200) { - throw new StreamHttpJsonError({ statusCode: response.code, responseJson: response.body }) + throw new HttpError({ statusCode: response.code, responseJson: response.body }) } }) diff --git a/packages/@uppy/companion/src/server/provider/providerErrors.js b/packages/@uppy/companion/src/server/provider/providerErrors.js index 715de19592..de02ab9158 100644 --- a/packages/@uppy/companion/src/server/provider/providerErrors.js +++ b/packages/@uppy/companion/src/server/provider/providerErrors.js @@ -43,7 +43,7 @@ async function withProviderErrorHandling({ if (err?.name === 'HTTPError') { statusCode = err.response?.statusCode body = err.response?.body - } else if (err?.name === 'StreamHttpJsonError') { + } else if (err?.name === 'HttpError') { statusCode = err.statusCode body = err.responseJson } From db316bf3da0416e0b5c9847f5e6483c98d27d910 Mon Sep 17 00:00:00 2001 From: Mikael Finstad Date: Mon, 17 Jun 2024 22:29:45 +0200 Subject: [PATCH 4/4] implement review feedback --- .../src/server/provider/facebook/index.js | 93 ++++++++++--------- 1 file changed, 48 insertions(+), 45 deletions(-) diff --git a/packages/@uppy/companion/src/server/provider/facebook/index.js b/packages/@uppy/companion/src/server/provider/facebook/index.js index 267b21634b..b31588c5b1 100644 --- a/packages/@uppy/companion/src/server/provider/facebook/index.js +++ b/packages/@uppy/companion/src/server/provider/facebook/index.js @@ -11,6 +11,48 @@ const { HttpError } = require('../../helpers/utils') const got = require('../../got') +async function runRequestBatch({ secret, token, requests }) { + // https://developers.facebook.com/docs/facebook-login/security/#appsecret + // couldn't get `appsecret_time` working, but it seems to be working without it + // const time = Math.floor(Date.now() / 1000) + const appSecretProof = crypto.createHmac('sha256', secret) + // .update(`${token}|${time}`) + .update(token) + .digest('hex'); + + const form = { + access_token: token, + appsecret_proof: appSecretProof, + // appsecret_time: String(time), + batch: JSON.stringify(requests), + } + + const responsesRaw = await (await got).post('https://graph.facebook.com', { form }).json() + + const responses = responsesRaw.map((response) => ({ ...response, body: JSON.parse(response.body) })) + + const errorResponse = responses.find((response) => response.code !== 200) + if (errorResponse) { + throw new HttpError({ statusCode: errorResponse.code, responseJson: errorResponse.body }) + } + + return responses +} + +async function getMediaUrl ({ secret, token, id }) { + const [{ body }] = await runRequestBatch({ + secret, + token, + requests: [ + { method: 'GET', relative_url: `${id}?${new URLSearchParams({ fields: 'images' }).toString()}` }, + ], + }); + + const sortedImages = sortImages(body.images) + return sortedImages[sortedImages.length - 1].source +} + + /** * Adapter for API https://developers.facebook.com/docs/graph-api/using-graph-api/ */ @@ -19,47 +61,6 @@ class Facebook extends Provider { return 'facebook' } - async runRequestBatch({ token, requests }) { - // https://developers.facebook.com/docs/facebook-login/security/#appsecret - // couldn't get `appsecret_time` working, but it seems to be working without it - // const time = Math.floor(Date.now() / 1000) - const appSecretProof = crypto.createHmac('sha256', this.secret) - // .update(`${token}|${time}`) - .update(token) - .digest('hex'); - - const form = { - access_token: token, - appsecret_proof: appSecretProof, - // appsecret_time: String(time), - batch: JSON.stringify(requests), - } - - const responsesRaw = await (await got).post('https://graph.facebook.com', { form }).json() - - const responses = responsesRaw.map((response) => ({ ...response, body: JSON.parse(response.body) })) - - responses.forEach((response) => { - if (response.code !== 200) { - throw new HttpError({ statusCode: response.code, responseJson: response.body }) - } - }) - - return responses - } - - async getMediaUrl ({ token, id }) { - const [{ body }] = await this.runRequestBatch({ - token, - requests: [ - { method: 'GET', relative_url: `${id}?${new URLSearchParams({ fields: 'images' }).toString()}` }, - ], - }); - - const sortedImages = sortImages(body.images) - return sortedImages[sortedImages.length - 1].source - } - async list ({ directory, token, query = { cursor: null } }) { return this.#withErrorHandling('provider.facebook.list.error', async () => { const qs = { fields: 'name,cover_photo,created_time,type' } @@ -72,7 +73,8 @@ class Facebook extends Provider { qs.fields = 'icon,images,name,width,height,created_time' } - const [response1, response2] = await this.runRequestBatch({ + const [response1, response2] = await runRequestBatch({ + secret: this.secret, token, requests: [ { method: 'GET', relative_url: `me?${new URLSearchParams({ fields: 'email' }).toString()}` }, @@ -88,7 +90,7 @@ class Facebook extends Provider { async download ({ id, token }) { return this.#withErrorHandling('provider.facebook.download.error', async () => { - const url = await this.getMediaUrl({ token, id }) + const url = await getMediaUrl({ secret: this.secret, token, id }) const stream = (await got).stream.get(url, { responseType: 'json' }) await prepareStream(stream) return { stream } @@ -104,7 +106,7 @@ class Facebook extends Provider { async size ({ id, token }) { return this.#withErrorHandling('provider.facebook.size.error', async () => { - const url = await this.getMediaUrl({ token, id }) + const url = await getMediaUrl({ secret: this.secret, token, id }) const { size } = await getURLMeta(url) return size }) @@ -112,7 +114,8 @@ class Facebook extends Provider { async logout ({ token }) { return this.#withErrorHandling('provider.facebook.logout.error', async () => { - await this.runRequestBatch({ + await runRequestBatch({ + secret: this.secret, token, requests: [ { method: 'DELETE', relative_url: 'me/permissions' },