From 1564de41a6620a6346d7b4699d6e28228906271e Mon Sep 17 00:00:00 2001 From: fcaps Date: Wed, 29 Nov 2023 03:56:53 +0100 Subject: [PATCH] init m2m client --- .env.example | 2 + .env.faf-stack | 3 +- config/app.js | 5 ++ lib/JavaApiM2MClient.js | 6 +++ lib/JavaApiM2MClientFactory.js | 51 +++++++++++++++++++ lib/LeaderboardService.js | 4 +- package.json | 1 + routes/middleware.js | 32 +++++++----- routes/views/leaderboardRouter.js | 14 +----- scripts/cron-jobs.js | 35 +++++++++---- yarn.lock | 81 +++++++++++++++++++++++++++++++ 11 files changed, 197 insertions(+), 37 deletions(-) create mode 100644 lib/JavaApiM2MClient.js create mode 100644 lib/JavaApiM2MClientFactory.js diff --git a/.env.example b/.env.example index e41373f1..cd233a8e 100755 --- a/.env.example +++ b/.env.example @@ -4,6 +4,8 @@ API_URL=https://api.test.faforever.com OAUTH_URL=https://hydra.test.faforever.com WP_URL=https://direct.faforever.com CALLBACK=auth +OAUTH_M2M_CLIENT_ID=faf-website-public +OAUTH_M2M_CLIENT_SECRET=banana OAUTH_CLIENT_ID=faf-website OAUTH_CLIENT_SECRET=banana SESSION_SECRET_KEY=banana diff --git a/.env.faf-stack b/.env.faf-stack index 39514b35..d1aa4d2c 100644 --- a/.env.faf-stack +++ b/.env.faf-stack @@ -20,7 +20,8 @@ OAUTH_PUBLIC_URL=http://localhost:4444 # unsing the "xyz" wordpress because the faf-local-stack is just an empty instance without any news etc. WP_URL=https://direct.faforever.xyz - +OAUTH_M2M_CLIENT_ID=faf-website-public +OAUTH_M2M_CLIENT_SECRET=banana OAUTH_CLIENT_ID=faf-website OAUTH_CLIENT_SECRET=banana SESSION_SECRET_KEY=banana diff --git a/config/app.js b/config/app.js index 85d6d104..c184ace1 100644 --- a/config/app.js +++ b/config/app.js @@ -18,6 +18,11 @@ const appConfig = { publicUrl: process.env.OAUTH_PUBLIC_URL || oauthUrl, callback: process.env.CALLBACK || 'callback', }, + m2mOauth: { + clientId: process.env.OAUTH_M2M_CLIENT_ID || 'faf-website-public', + clientSecret: process.env.OAUTH_M2M_CLIENT_SECRET || 'banana', + url: oauthUrl, + }, apiUrl: process.env.API_URL || 'https://api.faforever.com', wordpressUrl: process.env.WP_URL || 'https://direct.faforever.com', extractorInterval: process.env.EXTRACTOR_INTERVAL || 5, diff --git a/lib/JavaApiM2MClient.js b/lib/JavaApiM2MClient.js new file mode 100644 index 00000000..1021ffbe --- /dev/null +++ b/lib/JavaApiM2MClient.js @@ -0,0 +1,6 @@ +const { JavaApiM2MClientFactory } = require("./JavaApiM2MClientFactory"); +const appConfig = require("../config/app"); + +const client = JavaApiM2MClientFactory(appConfig.m2mOauth.clientId, appConfig.m2mOauth.clientSecret, appConfig.m2mOauth.url, appConfig.apiUrl) + +module.exports.JavaApiM2MClient = client diff --git a/lib/JavaApiM2MClientFactory.js b/lib/JavaApiM2MClientFactory.js new file mode 100644 index 00000000..5668b6d1 --- /dev/null +++ b/lib/JavaApiM2MClientFactory.js @@ -0,0 +1,51 @@ +const {ClientCredentials} = require("simple-oauth2"); +const { Axios } = require("axios"); +const { AuthFailed } = require("./ApiErrors"); + +const getToken = async function(clientId, clientSecret, host, scope) { + const tokenClient = new ClientCredentials({ + client: { + id: clientId, + secret: clientSecret, + + }, + auth: { + tokenHost: host, + tokenPath: '/oauth2/token', + revokePath: '/oauth2/revoke' + }, + options: { + authorizationMethod: 'body' + } + }) + + try { + return tokenClient.getToken({ + scope: scope ?? '', + }) + } catch (error) { + throw new AuthFailed(error.toString()) + } +} + +module.exports.JavaApiM2MClientFactory = async function(clientId, clientSecret, host, javaApiBaseURL) { + let passport = await getToken(clientId, clientSecret, host, '') + + const client = new Axios({ + baseURL: javaApiBaseURL + }) + + client.interceptors.request.use(config => { + if (passport.expired()) { + passport = passport.refresh() + } + + config.headers = { + Authorization: `Bearer ${passport.token.access_token}` + } + + return config; + }); + + return client +} diff --git a/lib/LeaderboardService.js b/lib/LeaderboardService.js index efc86c58..ea6e0eb4 100644 --- a/lib/LeaderboardService.js +++ b/lib/LeaderboardService.js @@ -9,7 +9,7 @@ class LeaderboardService { this.leaderboardRepository = leaderboardRepository } - async getLeaderboard(id) { + async getLeaderboard(id, ignoreCache = false) { if (typeof (id) !== 'number') { throw new Error('LeaderboardService:getLeaderboard id must be a number') @@ -17,7 +17,7 @@ class LeaderboardService { const cacheKey = 'leaderboard-' + id - if (this.cacheService.has(cacheKey)) { + if (this.cacheService.has(cacheKey) && ignoreCache === false) { return this.cacheService.get(cacheKey) } diff --git a/package.json b/package.json index 46185d0c..579872cd 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "request": "2.88.2", "session-file-store": "^1.5.0", "showdown": "^2.1.0", + "simple-oauth2": "^5.0.0", "supertest-session": "^5.0.1", "url-slug": "^4.0.1" }, diff --git a/routes/middleware.js b/routes/middleware.js index 8d31807c..b376e467 100755 --- a/routes/middleware.js +++ b/routes/middleware.js @@ -1,5 +1,6 @@ const WordpressServiceFactory = require("../lib/WordpressServiceFactory"); const {JavaApiClientFactory} = require('../lib/JavaApiClientFactory') +const {JavaApiM2MClient} = require('../lib/JavaApiM2MClient') const LeaderboardService = require('../lib/LeaderboardService') const LeaderboardRepository = require('../lib/LeaderboardRepository') const cacheService = require('../lib/CacheService') @@ -9,7 +10,7 @@ const fs = require('fs'); const webpackManifestJS = JSON.parse(fs.readFileSync('dist/js/manifest.json', 'utf8')); const LoggedInUserService = require('../lib/LoggedInUserService') const UserRepository = require('../lib/UserRepository'); - + exports.initLocals = function(req, res, next) { let locals = res.locals; @@ -59,19 +60,24 @@ exports.isAuthenticated = (redirectUrlAfterLogin = null, isApiRequest = false) = } } -exports.injectServices = (req, res, next) => { - req.services = {} - req.services.wordpressService = wordpressService +exports.injectServices = async (req, res, next) => { + try { + req.services = {} + req.services.wordpressService = wordpressService + req.services.javaApiM2MClient = await JavaApiM2MClient + req.services.leaderboardService = new LeaderboardService(cacheService, new LeaderboardRepository(req.services.javaApiM2MClient)) - if (req.isAuthenticated()) { - try { - req.services.javaApiClient = JavaApiClientFactory(appConfig.apiUrl, req.user.oAuthPassport) - req.services.userService = new LoggedInUserService(new UserRepository(req.services.javaApiClient), req) - req.services.leaderboardService = new LeaderboardService(cacheService, new LeaderboardRepository(req.services.javaApiClient)) - } catch (e) { - req.logout(() => next(e)) + if (req.isAuthenticated()) { + try { + req.services.javaApiClient = JavaApiClientFactory(appConfig.apiUrl, req.user.oAuthPassport) + req.services.userService = new LoggedInUserService(new UserRepository(req.services.javaApiClient), req) + } catch (e) { + req.logout(() => next(e)) + } } - } - next() + next() + } catch (e) { + next(e) + } } diff --git a/routes/views/leaderboardRouter.js b/routes/views/leaderboardRouter.js index 0a667ff0..fa758fe4 100644 --- a/routes/views/leaderboardRouter.js +++ b/routes/views/leaderboardRouter.js @@ -20,11 +20,11 @@ const getLeaderboardId = (leaderboardName) => { return null } -router.get('/', middlewares.isAuthenticated(), (req, res) => { +router.get('/', (req, res) => { return res.render('leaderboards') }) -router.get('/:leaderboard.json', middlewares.isAuthenticated(null, true), async (req, res) => { +router.get('/:leaderboard.json', async (req, res) => { try { const leaderboardId = getLeaderboardId(req.params.leaderboard ?? null); @@ -38,16 +38,6 @@ router.get('/:leaderboard.json', middlewares.isAuthenticated(null, true), async return res.status(503).json({error: 'timeout reached'}) } - if (e instanceof AuthFailed) { - req.logout(function(err) { - if (err) { - throw err - } - }) - - return res.status(400).json({error: 'authentication failed, reload site'}) - } - console.error('[error] leaderboardRouter::get:leaderboard.json failed with "' + e.toString() + '"') if (!res.headersSent) { diff --git a/scripts/cron-jobs.js b/scripts/cron-jobs.js index cb5fe181..7bad044e 100644 --- a/scripts/cron-jobs.js +++ b/scripts/cron-jobs.js @@ -1,17 +1,21 @@ const appConfig = require("../config/app") const WordpressServiceFactory = require("../lib/WordpressServiceFactory"); const Scheduler = require("../lib/Scheduler"); +const LeaderboardService = require("../lib/LeaderboardService"); +const cacheService = require("../lib/CacheService"); +const LeaderboardRepository = require("../lib/LeaderboardRepository"); +const {JavaApiM2MClient} = require('../lib/JavaApiM2MClient') -const warmupWordpressCache = async () => { +const successHandler = (name) => { + console.info(name, 'cache generated') +} +const errorHandler = (e, name) => { + console.error(name, e.toString(), 'cache failed') +} + +const warmupWordpressCache = () => { const wordpressService = WordpressServiceFactory(appConfig.wordpressUrl) - const successHandler = (name) => { - console.info(name, 'cache generated') - } - const errorHandler = (e, name) => { - console.error(name, e.toString(), 'cache failed') - } - wordpressService.getNews(true).then(() => successHandler('getNews')).catch((e) => errorHandler(e, 'getNews')) wordpressService.getNewshub(true).then(() => successHandler('getNewshub')).catch((e) => errorHandler(e, 'getNewshub')) wordpressService.getContentCreators(true).then(() => successHandler('getContentCreators')).catch((e) => errorHandler(e, 'getContentCreators')) @@ -19,9 +23,22 @@ const warmupWordpressCache = async () => { wordpressService.getFafTeams(true).then(() => successHandler('getFafTeams')).catch((e) => errorHandler(e, 'getFafTeams')) } +const warmupLeaderboard = async () => { + const leaderboardService = new LeaderboardService(cacheService, new LeaderboardRepository(await JavaApiM2MClient)) + + await leaderboardService.getLeaderboard(1, true).then(() => successHandler('getLeaderboard(global)')).catch((e) => errorHandler(e, 'getLeaderboard(global)')) + await leaderboardService.getLeaderboard(2, true).then(() => successHandler('getLeaderboard(1v1)')).catch((e) => errorHandler(e, 'getLeaderboard(1v1)')) + await leaderboardService.getLeaderboard(3, true).then(() => successHandler('getLeaderboard(2v2)')).catch((e) => errorHandler(e, 'getLeaderboard(2v2)')) + await leaderboardService.getLeaderboard(4, true).then(() => successHandler('getLeaderboard(4v4)')).catch((e) => errorHandler(e, 'getLeaderboard(4v4)')) +} + module.exports = async () => { - await warmupWordpressCache() + warmupWordpressCache() + await warmupLeaderboard() const wordpressScheduler = new Scheduler('createWordpressCaches', warmupWordpressCache, 60 * 59 * 1000) wordpressScheduler.start() + + const leaderboardScheduler = new Scheduler('createLeaderboardCaches', warmupLeaderboard, 60 * 59 * 1000) + leaderboardScheduler.start() } diff --git a/yarn.lock b/yarn.lock index ccb783a2..3e4e9295 100644 --- a/yarn.lock +++ b/yarn.lock @@ -346,6 +346,49 @@ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.54.0.tgz#4fab9a2ff7860082c304f750e94acd644cf984cf" integrity sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ== +"@hapi/boom@^10.0.1": + version "10.0.1" + resolved "https://registry.yarnpkg.com/@hapi/boom/-/boom-10.0.1.tgz#ebb14688275ae150aa6af788dbe482e6a6062685" + integrity sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA== + dependencies: + "@hapi/hoek" "^11.0.2" + +"@hapi/bourne@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@hapi/bourne/-/bourne-3.0.0.tgz#f11fdf7dda62fe8e336fa7c6642d9041f30356d7" + integrity sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w== + +"@hapi/hoek@^10.0.1": + version "10.0.1" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-10.0.1.tgz#ee9da297fabc557e1c040a0f44ee89c266ccc306" + integrity sha512-CvlW7jmOhWzuqOqiJQ3rQVLMcREh0eel4IBnxDx2FAcK8g7qoJRQK4L1CPBASoCY6y8e6zuCy3f2g+HWdkzcMw== + +"@hapi/hoek@^11.0.2": + version "11.0.2" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-11.0.2.tgz#cb3ea547daac7de5c9cf1d960c3f35c34f065427" + integrity sha512-aKmlCO57XFZ26wso4rJsW4oTUnrgTFw2jh3io7CAtO9w4UltBNwRXvXIVzzyfkaaLRo3nluP/19msA8vDUUuKw== + +"@hapi/hoek@^9.0.0": + version "9.3.0" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb" + integrity sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ== + +"@hapi/topo@^5.0.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012" + integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg== + dependencies: + "@hapi/hoek" "^9.0.0" + +"@hapi/wreck@^18.0.0": + version "18.0.1" + resolved "https://registry.yarnpkg.com/@hapi/wreck/-/wreck-18.0.1.tgz#6df04532be25fd128c5244e72ccc21438cf8bb65" + integrity sha512-OLHER70+rZxvDl75xq3xXOfd3e8XIvz8fWY0dqg92UvhZ29zo24vQgfqgHSYhB5ZiuFpSLeriOisAlxAo/1jWg== + dependencies: + "@hapi/boom" "^10.0.1" + "@hapi/bourne" "^3.0.0" + "@hapi/hoek" "^11.0.2" + "@humanwhocodes/config-array@^0.11.13": version "0.11.13" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.13.tgz#075dc9684f40a531d9b26b0822153c1e832ee297" @@ -862,6 +905,23 @@ resolved "https://registry.yarnpkg.com/@servie/events/-/events-1.0.0.tgz#8258684b52d418ab7b86533e861186638ecc5dc1" integrity sha512-sBSO19KzdrJCM3gdx6eIxV8M9Gxfgg6iDQmH5TIAGaUu+X9VDdsINXJOnoiZ1Kx3TrHdH4bt5UVglkjsEGBcvw== +"@sideway/address@^4.1.3": + version "4.1.4" + resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.4.tgz#03dccebc6ea47fdc226f7d3d1ad512955d4783f0" + integrity sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw== + dependencies: + "@hapi/hoek" "^9.0.0" + +"@sideway/formula@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.1.tgz#80fcbcbaf7ce031e0ef2dd29b1bfc7c3f583611f" + integrity sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg== + +"@sideway/pinpoint@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df" + integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== + "@sinclair/typebox@^0.27.8": version "0.27.8" resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" @@ -5199,6 +5259,17 @@ jit-grunt@0.10.0: resolved "https://registry.yarnpkg.com/jit-grunt/-/jit-grunt-0.10.0.tgz#008c3a7fe1e96bd0d84e260ea1fa1783457f79c2" integrity sha512-eT/f4c9wgZ3buXB7X1JY1w6uNtAV0bhrbOGf/mFmBb0CDNLUETJ/VRoydayWOI54tOoam0cz9RooVCn3QY1WoA== +joi@^17.6.4: + version "17.11.0" + resolved "https://registry.yarnpkg.com/joi/-/joi-17.11.0.tgz#aa9da753578ec7720e6f0ca2c7046996ed04fc1a" + integrity sha512-NgB+lZLNoqISVy1rZocE9PZI36bL/77ie924Ri43yEvi9GUUMPeyVIr8KdFTMUlby1p0PBYMk9spIxEUQYqrJQ== + dependencies: + "@hapi/hoek" "^9.0.0" + "@hapi/topo" "^5.0.0" + "@sideway/address" "^4.1.3" + "@sideway/formula" "^3.0.1" + "@sideway/pinpoint" "^2.0.0" + jquery@^3.7.1: version "3.7.1" resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.7.1.tgz#083ef98927c9a6a74d05a6af02806566d16274de" @@ -7501,6 +7572,16 @@ signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== +simple-oauth2@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/simple-oauth2/-/simple-oauth2-5.0.0.tgz#3b7d85700944b26f8f5451c017426292f330460c" + integrity sha512-8291lo/z5ZdpmiOFzOs1kF3cxn22bMj5FFH+DNUppLJrpoIlM1QnFiE7KpshHu3J3i21TVcx4yW+gXYjdCKDLQ== + dependencies: + "@hapi/hoek" "^10.0.1" + "@hapi/wreck" "^18.0.0" + debug "^4.3.4" + joi "^17.6.4" + sisteransi@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"