diff --git a/src/index.js b/src/index.js index 8f8c4da6ab9a9df18085b9f0b332fa93cca42ebf..c7b4cbff8a2a38faf4ac89fd20e68255552fb952 100644 --- a/src/index.js +++ b/src/index.js @@ -41,6 +41,10 @@ const fileStorage = { const { callMicroservice } = require('./utils/microservices') +const { + authenticatedCall: makeOAuthCall, +} = require('./utils/authenticatedCall') + const createJWT = authentication.token.create const verifyJWT = authentication.token.verify @@ -70,5 +74,6 @@ module.exports = { connectToJobQueue, callMicroservice, + makeOAuthCall, WaxToDocxConverter, } diff --git a/src/utils/__tests__/authenticatedCall.test.js b/src/utils/__tests__/authenticatedCall.test.js new file mode 100644 index 0000000000000000000000000000000000000000..58c2a1fd6a1033565e766348e986390732493670 --- /dev/null +++ b/src/utils/__tests__/authenticatedCall.test.js @@ -0,0 +1,16 @@ +const axios = require('axios') +const { authenticatedCall } = require('../authenticatedCall') + +jest.mock('../getAuthTokens.js', () => { + return jest.fn(() => 'token') +}) + +jest.mock('axios') + +describe('Authenticated call', () => { + it('calls provider with auth', async () => { + axios.mockResolvedValue(true) + const res = await authenticatedCall('123', 'lulu', {}) + expect(res).toBe(true) + }) +}) diff --git a/src/utils/authenticatedCall.js b/src/utils/authenticatedCall.js new file mode 100644 index 0000000000000000000000000000000000000000..10d8ccd41e2cbc3cccb9af0237e3fe9700fc6650 --- /dev/null +++ b/src/utils/authenticatedCall.js @@ -0,0 +1,19 @@ +const makeCall = require('./makeCall') +const getAuthTokens = require('./getAuthTokens') + +const authenticatedCall = async (userId, providerLabel, callParameters) => { + try { + if (!callParameters) throw new Error(`Call parameters are required`) + + const accessToken = await getAuthTokens(userId, providerLabel) + + return makeCall(callParameters, accessToken) + } catch (e) { + throw new Error(e) + } +} + +module.exports = { + authenticatedCall, + getAuthTokens, +} diff --git a/src/utils/getAuthTokens.js b/src/utils/getAuthTokens.js new file mode 100644 index 0000000000000000000000000000000000000000..626662667c55f018809684dc6826d89cb1f638ad --- /dev/null +++ b/src/utils/getAuthTokens.js @@ -0,0 +1,97 @@ +const config = require('config') +const axios = require('axios') + +const { Identity } = require('../models') + +const getAuthTokens = async (userId, providerLabel) => { + try { + const providerUserIdentity = await Identity.findOne({ + userId, + provider: providerLabel, + }) + + if (!providerUserIdentity) { + throw new Error(`identity for provider ${providerLabel} does not exist`) + } + + const { + oauthAccessToken, + oauthAccessTokenExpiration, + oauthRefreshToken, + oauthRefreshTokenExpiration, + } = providerUserIdentity + + const accessTokenExpired = oauthAccessTokenExpiration < new Date().getTime() + + if (!accessTokenExpired) { + return oauthAccessToken + } + + const refreshTokenExpired = + oauthRefreshTokenExpiration < new Date().getTime() + + if (refreshTokenExpired) { + throw new Error( + `refresh token for provider ${providerLabel} expired, authorization flow should (provider login) be followed by the user`, + ) + } + + const integrations = + config.has('integrations') && config.get('integrations') + + if (!integrations) { + throw new Error('Integrations are undefined in config') + } + + const externalProvider = integrations[providerLabel] + + if (!externalProvider) { + throw new Error( + `Integration ${providerLabel} configuration is undefined `, + ) + } + + const { tokenUrl, clientId } = integrations[providerLabel] + + if (!tokenUrl) { + throw new Error(`Integration ${providerLabel} tokenUrl is undefined `) + } + + if (!clientId) { + throw new Error(`Integration ${providerLabel} clientId is undefined `) + } + + const tokenData = new URLSearchParams({ + grant_type: 'refresh_token', + refresh_token: oauthRefreshToken, + client_id: clientId, + }) + + const { data } = await axios({ + method: 'post', + url: tokenUrl, + + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + data: tokenData.toString(), + }) + + /* eslint-disable camelcase */ + const { access_token, expires_in, refresh_token, refresh_expires_in } = data + + await Identity.patchAndFetchById(providerUserIdentity.id, { + oauthAccessToken: access_token, + oauthRefreshToken: refresh_token, + oauthAccessTokenExpiration: + new Date().getTime() + 1000 * parseInt(expires_in, 10), + oauthRefreshTokenExpiration: + new Date().getTime() + 1000 * parseInt(refresh_expires_in, 10), + }) + + return access_token + /* eslint-enable camelcase */ + } catch (e) { + throw new Error(e) + } +} + +module.exports = getAuthTokens diff --git a/src/utils/makeCall.js b/src/utils/makeCall.js new file mode 100644 index 0000000000000000000000000000000000000000..4a8047cb9f676e0612c79c54ec6cb3bc750bbe87 --- /dev/null +++ b/src/utils/makeCall.js @@ -0,0 +1,19 @@ +const axios = require('axios') +const clone = require('lodash/clone') + +const makeCall = async (callParameters, token) => { + const axiosParams = clone(callParameters) + const { headers } = axiosParams + + if (!headers) { + axiosParams.headers = { + authorization: `Bearer ${token}`, + } + } else { + axiosParams.headers.authorization = `Bearer ${token}` + } + + return axios(axiosParams) +} + +module.exports = makeCall diff --git a/src/utils/microservices.js b/src/utils/microservices.js index 7fb8ac915b638bad057c548730d502acda095adf..642dc1975a19c36c1f9b9b1d1cfa6515190e3704 100644 --- a/src/utils/microservices.js +++ b/src/utils/microservices.js @@ -1,5 +1,4 @@ -const axios = require('axios') -const clone = require('lodash/clone') +const makeCall = require('./makeCall') const getAccessToken = require('./getAccessToken') @@ -32,24 +31,9 @@ const callMicroservice = async (serviceName, callParameters) => { `communication parameters needed for calling ${serviceName} microservice`, ) - const makeCall = async token => { - const axiosParams = clone(callParameters) - const { headers } = axiosParams - - if (!headers) { - axiosParams.headers = { - authorization: `Bearer ${token}`, - } - } else { - axiosParams.headers.authorization = `Bearer ${token}` - } - - return axios(axiosParams) - } - const accessToken = await getAccessToken(serviceName) - return makeCall(accessToken).catch(async err => { + return makeCall(callParameters, accessToken).catch(async err => { const { response } = err if (!response) { @@ -61,7 +45,7 @@ const callMicroservice = async (serviceName, callParameters) => { if (status === 401 && msg === 'expired token') { const freshToken = await getAccessToken(serviceName, true) - return makeCall(freshToken) + return makeCall(callParameters, freshToken) } throw new Error(err)