diff --git a/deploy/auth/hbp-oidc.js b/deploy/auth/hbp-oidc.js index ab8ec12b7db637de3646138b176e7a0d1a422f69..cbb7ce83f0d34de5814048a3e2986a660640f40c 100644 --- a/deploy/auth/hbp-oidc.js +++ b/deploy/auth/hbp-oidc.js @@ -23,6 +23,7 @@ module.exports = async (app) => { discoveryUrl, redirectUri, cb, + scope: 'openid offline_access', clientConfig: { redirect_uris: [ redirectUri ], response_types: [ 'code' ] diff --git a/deploy/auth/index.js b/deploy/auth/index.js index f76f6cc42a55f17b631aa793e47f265a5cba999f..d4f4242875b4b1e5cafe9c2152a43aa0aa963c26 100644 --- a/deploy/auth/index.js +++ b/deploy/auth/index.js @@ -7,6 +7,11 @@ module.exports = async (app) => { app.use(passport.session()) passport.serializeUser((user, done) => { + const { tokenset, rest } = user + console.log({ + tokenset, + rest + }) objStoreDb.set(user.id, user) done(null, user.id) }) diff --git a/deploy/auth/oidc.js b/deploy/auth/oidc.js index 856adc48bb10413b985011e2f14e8bb73fdc3fa6..da3a0719d3984367fb3057d4aeac424b7b9b03c7 100644 --- a/deploy/auth/oidc.js +++ b/deploy/auth/oidc.js @@ -7,7 +7,7 @@ const defaultCb = (tokenset, {id, ...rest}, done) => { }) } -exports.configureAuth = async ({ discoveryUrl, clientId, clientSecret, redirectUri, clientConfig = {}, cb = defaultCb }) => { +exports.configureAuth = async ({ discoveryUrl, clientId, clientSecret, redirectUri, clientConfig = {}, cb = defaultCb, scope = 'openid' }) => { if (!discoveryUrl) throw new Error('discoveryUrl must be defined!') @@ -30,10 +30,14 @@ exports.configureAuth = async ({ discoveryUrl, clientId, clientSecret, redirectU const oidcStrategy = new Strategy({ client, - redirect_uri: redirectUri + params: { + scope + }, }, cb) return { - oidcStrategy + oidcStrategy, + issuer, + client } } \ No newline at end of file diff --git a/deploy/auth/util.js b/deploy/auth/util.js new file mode 100644 index 0000000000000000000000000000000000000000..2f225bc836621bb840f79e1c396da6f39fc69f24 --- /dev/null +++ b/deploy/auth/util.js @@ -0,0 +1,66 @@ +const { configureAuth } = require('./oidc') +const jwtDecode = require('jwt-decode') + +const HOSTNAME = process.env.HOSTNAME || 'http://localhost:3000' +const clientId = process.env.HBP_CLIENTID || 'no hbp id' +const clientSecret = process.env.HBP_CLIENTSECRET || 'no hbp client secret' +const discoveryUrl = 'https://services.humanbrainproject.eu/oidc' +const redirectUri = `${HOSTNAME}/hbp-oidc/cb` + +let REFRESH_TOKEN = process.env.REFRESH_TOKEN || null +const CLIENT_NOT_INIT = `Client is not initialised.` +const REFRESH_TOKEN_MISSING = `refresh token is missing` + +let __client +let __publicAccessToken + +const refreshToken = async () => { + if (!__client) + throw new Error(CLIENT_NOT_INIT) + if (!REFRESH_TOKEN) + throw new Error(REFRESH_TOKEN_MISSING) + const tokenset = await __client.refresh(REFRESH_TOKEN) + const {access_token: accessToken, refresh_token: refreshToken, id_token: idToken} = tokenset + if (refreshToken !== REFRESH_TOKEN) { + REFRESH_TOKEN = refreshToken + } + __publicAccessToken = accessToken + return true +} + +const getPublicAccessToken = async () => { + if (!__client) + throw new Error(CLIENT_NOT_INIT) + + if (!__publicAccessToken) { + await refreshToken() + } + + const decoded = jwtDecode(__publicAccessToken) + const { exp } = decoded + if (!exp || isNaN(exp) || (exp * 1000 - Date.now() < 0)) { + await refreshToken() + } + + return __publicAccessToken +} + +module.exports = async () => { + + const { client } = await configureAuth({ + clientId, + clientSecret, + discoveryUrl, + redirectUri, + clientConfig: { + redirect_uris: [ redirectUri ], + response_types: [ 'code' ] + } + }) + + __client = client + + return { + getPublicAccessToken: async () => await getPublicAccessToken() + } +} \ No newline at end of file diff --git a/deploy/auth/util.spec.js b/deploy/auth/util.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..7d05de88400a702ebfc7342e233cb4e07980037e --- /dev/null +++ b/deploy/auth/util.spec.js @@ -0,0 +1,34 @@ +const mocha = require('mocha') +const chai = require('chai') +const assert = chai.assert +const chaiAsPromised = require('chai-as-promised') +chai.use(chaiAsPromised) +const expect = chai.expect +const should = chai.should() + +describe('mocha.js', () => { + it('mocha works properly', () => { + assert(true) + }) +}) + +describe('chai-as-promised.js', () => { + it('resolving promise is resolved', () => { + + return Promise.resolve(2 + 2).should.eventually.equal(4) + + }) + it('rejecting promise is rejected', () => { + return Promise.reject('no reason').should.be.rejected + }) +}) + +describe('util.js with env', (done) => { + + it('when client id and client secret and refresh token is set, util should not throw', async () => { + + const util = require('./util') + const { getPublicAccessToken } = await util() + return getPublicAccessToken().should.be.fulfilled + }) +}) \ No newline at end of file diff --git a/deploy/auth/util_noenv.spec.js b/deploy/auth/util_noenv.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..769d926bbfeb06ce7180ca42555fc4b19e572e19 --- /dev/null +++ b/deploy/auth/util_noenv.spec.js @@ -0,0 +1,40 @@ +const mocha = require('mocha') +const chai = require('chai') +const assert = chai.assert +const chaiAsPromised = require('chai-as-promised') +chai.use(chaiAsPromised) +const expect = chai.expect +const should = chai.should() + + +describe('mocha.js', () => { + it('mocha works properly', () => { + assert(true) + }) +}) + +describe('chai-as-promised.js', () => { + it('resolving promise is resolved', () => { + + return Promise.resolve(2 + 2).should.eventually.equal(4) + + }) + it('rejecting promise is rejected', () => { + return Promise.reject('no reason').should.be.rejected + }) +}) + +describe('util.js without env', (done) => { + + const util = require('./util') + it('even when no env is set, it should fulfill', () => { + const getUtil = util() + return getUtil.should.be.fulfilled + }) + + it('if env is not set, getPublicToken method should fail', async () => { + const utilObj = await util() + const { getPublicAccessToken } = utilObj + return getPublicAccessToken().should.be.rejected + }) +}) \ No newline at end of file diff --git a/deploy/datasets/query.js b/deploy/datasets/query.js index 46d7ac3a8727d23eb8180882ee19a1458f419735..c1b4027c20099e93727d5a1e58c3188c31d25c25 100644 --- a/deploy/datasets/query.js +++ b/deploy/datasets/query.js @@ -3,17 +3,36 @@ const request = require('request') const path = require('path') const { getPreviewFile, hasPreview } = require('./supplements/previewFile') +const kgQueryUtil = require('./../auth/util') + let cachedData = null let otherQueryResult = null const queryUrl = process.env.KG_DATASET_QUERY_URL || `https://kg.humanbrainproject.org/query/minds/core/dataset/v1.0.0/interactiveViewerKgQuery/instances?size=450&vocab=https%3A%2F%2Fschema.hbp.eu%2FmyQuery%2F` const timeout = process.env.TIMEOUT || 5000 +let getPublicAccessToken +try { + const { getPublicAccessToken: getPublic } = await kgQueryUtil() + getPublicAccessToken = getPublic +} catch (e) { + console.log('kgQueryUtil error', e) +} + const fetchDatasetFromKg = (arg) => new Promise((resolve, reject) => { + const accessToken = arg && arg.user && arg.user.tokenset && arg.user.tokenset.access_token - const option = accessToken || process.env.ACCESS_TOKEN + let publicAccessToken + if (!accessToken && getPublicAccessToken) { + try { + publicAccessToken = await getPublicAccessToken() + } catch (e) { + console.log('getPublicAccessToken Error', e) + } + } + const option = accessToken || publicAccessToken || process.env.ACCESS_TOKEN ? { auth: { - 'bearer': accessToken || process.env.ACCESS_TOKEN + 'bearer': accessToken || publicAccessToken || process.env.ACCESS_TOKEN } } : {} diff --git a/deploy/package.json b/deploy/package.json index 5916cb005ce2ae2713da23b0ac1e4ef341aff308..e76b3d9bbe32d2aae33a3d7ae39d7ae3740f50e1 100644 --- a/deploy/package.json +++ b/deploy/package.json @@ -4,7 +4,10 @@ "description": "", "main": "index.js", "scripts": { - "start": "node server.js" + "start": "node server.js", + "test": "npm run testEnv && npm run testNoEnv", + "testEnv": "node -r dotenv/config ./node_modules/.bin/mocha ./test/mocha.test.js", + "testNoEnv": "node ./node_modules/.bin/mocha ./test/mocha.test.noenv.js" }, "keywords": [], "author": "", @@ -12,13 +15,17 @@ "dependencies": { "express": "^4.16.4", "express-session": "^1.15.6", + "jwt-decode": "^2.2.0", "memorystore": "^1.6.1", "openid-client": "^2.4.5", "passport": "^0.4.0", "request": "^2.88.0" }, "devDependencies": { + "chai": "^4.2.0", + "chai-as-promised": "^7.1.1", "cors": "^2.8.5", - "dotenv": "^6.2.0" + "dotenv": "^6.2.0", + "mocha": "^6.1.4" } } diff --git a/deploy/test/mocha.test.js b/deploy/test/mocha.test.js new file mode 100644 index 0000000000000000000000000000000000000000..8f55b54de1f2566b8d2348adef651303b0ef1fca --- /dev/null +++ b/deploy/test/mocha.test.js @@ -0,0 +1 @@ +require('../auth/util.spec') \ No newline at end of file diff --git a/deploy/test/mocha.test.noenv.js b/deploy/test/mocha.test.noenv.js new file mode 100644 index 0000000000000000000000000000000000000000..36a918fcdf251fd5bcf380d8859f9c711925496b --- /dev/null +++ b/deploy/test/mocha.test.noenv.js @@ -0,0 +1 @@ +require("../auth/util_noenv.spec") \ No newline at end of file