Skip to content

Commit

Permalink
init m2m client
Browse files Browse the repository at this point in the history
  • Loading branch information
fcaps committed Nov 29, 2023
1 parent 29b71cb commit e7d2401
Show file tree
Hide file tree
Showing 13 changed files with 249 additions and 51 deletions.
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion .env.faf-stack
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions config/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
6 changes: 6 additions & 0 deletions lib/JavaApiM2MClient.js
Original file line number Diff line number Diff line change
@@ -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
51 changes: 51 additions & 0 deletions lib/JavaApiM2MClientFactory.js
Original file line number Diff line number Diff line change
@@ -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
}
4 changes: 2 additions & 2 deletions lib/LeaderboardService.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ 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')
}

const cacheKey = 'leaderboard-' + id

if (this.cacheService.has(cacheKey)) {
if (this.cacheService.has(cacheKey) && ignoreCache === false) {
return this.cacheService.get(cacheKey)
}

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
32 changes: 19 additions & 13 deletions routes/middleware.js
Original file line number Diff line number Diff line change
@@ -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')
Expand All @@ -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;
Expand Down Expand Up @@ -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)
}
}
14 changes: 2 additions & 12 deletions routes/views/leaderboardRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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) {
Expand Down
82 changes: 67 additions & 15 deletions scripts/cron-jobs.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,79 @@
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 wordpressService = WordpressServiceFactory(appConfig.wordpressUrl)

const successHandler = (name) => {
console.info(name, 'cache generated')
}
const errorHandler = (e, name) => {
console.error(name, e.toString(), 'cache failed')
const successHandler = (name) => {
console.debug('Cache updated', {name})
}
const errorHandler = (e, name) => {
console.error(e.toString() , {name, entrypoint: 'cron-jobs.js'})
}

const warmupWordpressCache = () => {
try {
const wordpressService = WordpressServiceFactory(appConfig.wordpressUrl)

wordpressService.getNews(true)
.then(() => successHandler('wordpressService::getNews'))
.catch((e) => errorHandler(e, 'wordpressService::getNews'))

wordpressService.getNewshub(true)
.then(() => successHandler('wordpressService::getNewshub'))
.catch((e) => errorHandler(e, 'wordpressService::getNewshub'))

wordpressService.getContentCreators(true)
.then(() => successHandler('wordpressService::getContentCreators'))
.catch((e) => errorHandler(e, 'wordpressService::getContentCreators'))

wordpressService.getTournamentNews(true)
.then(() => successHandler('wordpressService::getTournamentNews'))
.catch((e) => errorHandler(e, 'wordpressService::getTournamentNews'))

wordpressService.getFafTeams(true)
.then(() => successHandler('wordpressService::getFafTeams'))
.catch((e) => errorHandler(e, 'wordpressService::getFafTeams'))
} catch (e) {
console.error('Error: cron-jobs::warmupWordpressCache failed with "' + e.toString() + '"', {entrypoint: 'cron-jobs.js'})
}
}

const warmupLeaderboard = async () => {
try {
const leaderboardService = new LeaderboardService(cacheService, new LeaderboardRepository(await JavaApiM2MClient))

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'))
wordpressService.getTournamentNews(true).then(() => successHandler('getTournamentNews')).catch((e) => errorHandler(e, 'getTournamentNews'))
wordpressService.getFafTeams(true).then(() => successHandler('getFafTeams')).catch((e) => errorHandler(e, 'getFafTeams'))
await leaderboardService.getLeaderboard(1, true)
.then(() => successHandler('leaderboardService::getLeaderboard(global)'))
.catch((e) => errorHandler(e, 'leaderboardService::getLeaderboard(global)'))

await leaderboardService.getLeaderboard(2, true)
.then(() => successHandler('leaderboardService::getLeaderboard(1v1)'))
.catch((e) => errorHandler(e, 'leaderboardService::getLeaderboard(1v1)'))

await leaderboardService.getLeaderboard(3, true)
.then(() => successHandler('leaderboardService::getLeaderboard(2v2)'))
.catch((e) => errorHandler(e, 'leaderboardService::getLeaderboard(2v2)'))

await leaderboardService.getLeaderboard(4, true)
.then(() => successHandler('leaderboardService::getLeaderboard(4v4)'))
.catch((e) => errorHandler(e, 'leaderboardService::getLeaderboard(4v4)'))

} catch (e) {
console.error('Error: cron-jobs::warmupLeaderboard failed with "' + e.toString() + '"', {entrypoint: 'cron-jobs.js'})
}
}

module.exports = async () => {
await warmupWordpressCache()
warmupWordpressCache()
warmupLeaderboard().then(() => {})

const wordpressScheduler = new Scheduler('createWordpressCaches', warmupWordpressCache, 60 * 59 * 1000)
wordpressScheduler.start()
wordpressScheduler.start()


const leaderboardScheduler = new Scheduler('createLeaderboardCaches', warmupLeaderboard, 60 * 59 * 1000)
leaderboardScheduler.start()
}
8 changes: 4 additions & 4 deletions tests/integration/leaderboardRouter.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,19 @@ beforeEach(() => {
})

describe('Leaderboard Routes', function () {
test('authentication required for main page', async () => {
test('no authentication required for main page', async () => {
let response = await testSession.get('/leaderboards')
expect(response.status).toBe(302)
expect(response.status).toBe(200)

await testSession.get('/mock-login')

response = await testSession.get('/leaderboards')
expect(response.status).toBe(200)
})

test('authentication required for datasets', async () => {
test('no authentication required for datasets', async () => {
const response = await testSession.get('/leaderboards/1v1.json')
expect(response.status).toBe(401)
expect(response.status).toBe(200)
})

test('fails with 404 on unknown leaderboard', async () => {
Expand Down
11 changes: 7 additions & 4 deletions tests/integration/servicesMiddleware.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@ beforeEach(() => {

describe('Services Middleware', function () {
test('public service are loaded without a user', (done) => {
expect.assertions(3)
expect.assertions(5)
testApp.get('/', (req, res) => {
try {
expect(req.services).not.toBeUndefined()
expect(Object.keys(req.services).length).toBe(1)
expect(Object.keys(req.services).length).toBe(3)
expect(req.services.javaApiM2MClient).toBeInstanceOf(Axios)
expect(req.services.wordpressService).toBeInstanceOf(WordpressService)
expect(req.services.leaderboardService).toBeInstanceOf(LeaderboardService)

return res.send('success')
} catch (e) {
Expand All @@ -37,14 +39,15 @@ describe('Services Middleware', function () {
})

test('additional services are loaded with authenticated user', (done) => {
expect.assertions(6)
expect.assertions(7)
testApp.get('/', (req, res) => {
try {
expect(req.services).not.toBeUndefined()
expect(Object.keys(req.services).length).toBe(4)
expect(Object.keys(req.services).length).toBe(5)
expect(req.services.wordpressService).toBeInstanceOf(WordpressService)
expect(req.services.userService).toBeInstanceOf(UserService)
expect(req.services.javaApiClient).toBeInstanceOf(Axios)
expect(req.services.javaApiM2MClient).toBeInstanceOf(Axios)
expect(req.services.leaderboardService).toBeInstanceOf(LeaderboardService)

return res.send('success')
Expand Down
Loading

0 comments on commit e7d2401

Please sign in to comment.