From cc90db60d7dd0c2d2056812d6f5f285e171e7532 Mon Sep 17 00:00:00 2001 From: Nick Cappadona Date: Thu, 16 Aug 2018 16:27:19 -0400 Subject: [PATCH] Keep LibCal API creds secret All together now... * dotenv [1] * axios module [2] * proxy module [3] * serverMiddleware [4] * body-parser [5] Go team. Heavily based on modify-post example [6] from http-proxy-middleware. The need to restream the parsed body prior to proxying [7 - 9] was a special treat. [1] https://github.com/motdotla/dotenv [2] https://axios.nuxtjs.org/ [3] https://github.com/nuxt-community/proxy-module [4] https://nuxtjs.org/api/configuration-servermiddleware [5] https://github.com/expressjs/body-parser [6] https://github.com/chimurai/http-proxy-middleware/blob/master/recipes/modify-post.md [7] https://github.com/chimurai/http-proxy-middleware/issues/40#issuecomment-249430255 [8] https://github.com/nodejitsu/node-http-proxy/pull/1027 [9] https://github.com/nodejitsu/node-http-proxy/pull/1264 --- .env.example | 5 ++++ .gitignore | 3 +++ nuxt.config.js | 51 +++++++++++++++++++++++++++++++++++++++ package.json | 2 ++ utils/libcal-schema.js | 9 +++---- utils/libcal.js | 14 ++--------- yarn.lock | 55 ++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 121 insertions(+), 18 deletions(-) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..b98b1a9 --- /dev/null +++ b/.env.example @@ -0,0 +1,5 @@ +# Copy this file to .env before editing any values + +# LibCal API 1.1 credentials +LIBCAL_CLIENT_ID=CHANGEME +LIBCAL_CLIENT_SECRET=CHANGEME diff --git a/.gitignore b/.gitignore index 7ee6115..5a99723 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,6 @@ npm-debug.log # Nuxt generate dist + +# Environment variables via dotenv +.env diff --git a/nuxt.config.js b/nuxt.config.js index 16761fe..24bf263 100644 --- a/nuxt.config.js +++ b/nuxt.config.js @@ -1,7 +1,58 @@ +// Use .env in nuxt config only (keep secrets on the dl) +// -- https://github.com/nuxt/nuxt.js/issues/2033#issuecomment-398820574 +// -- https://github.com/nuxt-community/dotenv-module#using-env-file-in-nuxtconfigjs +require('dotenv').config() + +// Body parser middleware needed to inject required fields for LibCal API auth +const bodyParser = require('body-parser') + +const libcalApi = 'https://api2.libcal.com' +const libcalApiPath = '/api/libcal/' +const libcalHoursApi = 'https://api3.libcal.com' +const libcalHoursApiPath = '/api/libcal-hours/' + module.exports = { modules: [ '@nuxtjs/axios' ], + serverMiddleware: [ + // Parse request body so it can be manipulated by proxy middleware + // -- https://nuxtjs.org/api/configuration-servermiddleware + { path: libcalApiPath, handler: bodyParser.json() } + ], + axios: { + prefix: '/api/', + proxy: true + }, + proxy: { + [libcalApiPath]: { + target: libcalApi, + pathRewrite: { [`^${libcalApiPath}`]: '' }, + onProxyReq: (proxyReq, req, res) => { + if (req.method === 'POST' && req.body) { + // Build object for POST request to obtain access token + let body = { + client_id: process.env.LIBCAL_CLIENT_ID, + client_secret: process.env.LIBCAL_CLIENT_SECRET, + grant_type: 'client_credentials' + } + + body = JSON.stringify(body) + + // Update headers + proxyReq.setHeader('Content-Type', 'application/json') + proxyReq.setHeader('Content-Length', Buffer.byteLength(body)) + + // Write new body to the proxyReq stream + proxyReq.write(body) + } + } + }, + [libcalHoursApiPath]: { + target: libcalHoursApi, + pathRewrite: { [`^${libcalHoursApiPath}`]: '' } + } + }, /* ** Headers of the page */ diff --git a/package.json b/package.json index 08e7813..84f298c 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,8 @@ }, "dependencies": { "@nuxtjs/axios": "^5.1.1", + "body-parser": "^1.18.3", + "dotenv": "^6.0.0", "jsonp-promise": "^0.1.2", "lodash": "^4.17.4", "nuxt": "^1.4.0" diff --git a/utils/libcal-schema.js b/utils/libcal-schema.js index 819cd52..a5e5ace 100644 --- a/utils/libcal-schema.js +++ b/utils/libcal-schema.js @@ -1,12 +1,9 @@ -const baseUrl = 'https://api2.libcal.com/' -const baseUrlHours = 'https://api3.libcal.com/' - export default { endpoints: { - auth: baseUrl + '1.1/oauth/token', - hours: baseUrlHours + 'api_hours_date.php?iid=973&format=json&nocache=1&lid=', + auth: 'libcal/1.1/oauth/token', + hours: 'libcal-hours/api_hours_date.php?iid=973&format=json&nocache=1&lid=', spaces: { - bookings: baseUrl + '1.1/space/bookings?' + bookings: 'libcal/1.1/space/bookings?' } }, desks: { diff --git a/utils/libcal.js b/utils/libcal.js index 9ddd056..8e06cc9 100644 --- a/utils/libcal.js +++ b/utils/libcal.js @@ -30,16 +30,6 @@ const libCal = { return moment().isSameOrAfter(lastClosing) }, - authenticate: function (axios) { - // TODO: Handle sensitive LibCal oAuth (potentially via dotenv) - // -- https://spaces.library.cornell.edu/admin_api.php?action=authentication - // -- https://github.com/nuxt-community/dotenv-module - return axios.$post(api.endpoints.auth, { - client_id: 'changeMe', - client_secret: 'changeMe', - grant_type: 'client_credentials' - }) - }, availableSlot: function (start, end) { // QUESTION: Unique ID necessary for available slots? return { @@ -166,8 +156,8 @@ const libCal = { // console.log('where to?', url) - let authorize = await libCal.authenticate(axios) - axios.setHeader('Authorization', 'Bearer ' + authorize.access_token) + let authorize = await axios.$post(api.endpoints.auth) + axios.setToken(authorize.access_token, 'Bearer') return axios.$get(url) }, diff --git a/yarn.lock b/yarn.lock index 2d8b280..85475c3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1044,6 +1044,21 @@ body-parser@1.18.2: raw-body "2.3.2" type-is "~1.6.15" +body-parser@^1.18.3: + version "1.18.3" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.3.tgz#5b292198ffdd553b3a0f20ded0592b956955c8b4" + dependencies: + bytes "3.0.0" + content-type "~1.0.4" + debug "2.6.9" + depd "~1.1.2" + http-errors "~1.6.3" + iconv-lite "0.4.23" + on-finished "~2.3.0" + qs "6.5.2" + raw-body "2.3.3" + type-is "~1.6.16" + boolbase@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" @@ -2055,6 +2070,10 @@ domutils@1.5.1: dom-serializer "0" domelementtype "1" +dotenv@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-6.0.0.tgz#24e37c041741c5f4b25324958ebbc34bca965935" + duplexer@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" @@ -3163,6 +3182,15 @@ http-errors@1.6.2, http-errors@~1.6.2: setprototypeof "1.0.3" statuses ">= 1.3.1 < 2" +http-errors@1.6.3, http-errors@~1.6.3: + version "1.6.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.0" + statuses ">= 1.4.0 < 2" + http-proxy-middleware@^0.17.4: version "0.17.4" resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.17.4.tgz#642e8848851d66f09d4f124912846dbaeb41b833" @@ -3203,6 +3231,12 @@ iconv-lite@0.4.19, iconv-lite@^0.4.17, iconv-lite@~0.4.13: version "0.4.19" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" +iconv-lite@0.4.23: + version "0.4.23" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63" + dependencies: + safer-buffer ">= 2.1.2 < 3" + icss-replace-symbols@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded" @@ -5380,6 +5414,10 @@ qs@6.5.1, qs@~6.5.1: version "6.5.1" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" +qs@6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" + qs@~6.3.0: version "6.3.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.2.tgz#e75bd5f6e268122a2a0e0bda630b2550c166502c" @@ -5436,6 +5474,15 @@ raw-body@2.3.2: iconv-lite "0.4.19" unpipe "1.0.0" +raw-body@2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.3.tgz#1b324ece6b5706e153855bc1148c65bb7f6ea0c3" + dependencies: + bytes "3.0.0" + http-errors "1.6.3" + iconv-lite "0.4.23" + unpipe "1.0.0" + rc@^1.1.7: version "1.2.6" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.6.tgz#eb18989c6d4f4f162c399f79ddd29f3835568092" @@ -5848,6 +5895,10 @@ safe-regex@^1.1.0: dependencies: ret "~0.1.10" +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + sass-graph@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-2.2.4.tgz#13fbd63cd1caf0908b9fd93476ad43a51d1e0b49" @@ -6195,6 +6246,10 @@ static-extend@^0.1.1: version "1.4.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087" +"statuses@>= 1.4.0 < 2": + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + statuses@~1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e"