diff --git a/Framework/Backend/http/server.js b/Framework/Backend/http/server.js index f55090bb8..39c7a6b76 100644 --- a/Framework/Backend/http/server.js +++ b/Framework/Backend/http/server.js @@ -34,6 +34,7 @@ class HttpServer { /** * Sets up the server, routes and binds HTTP and HTTPS sockets. * @param {object} httpConfig - configuration of HTTP server + * @param {boolean} [httpConfig.apiAuth=false] - whether API authentication is enabled * @param {object} [jwtConfig] - configuration of JWT * @param {object} [connectIdConfig] - configuration of OpenID Connect */ @@ -43,6 +44,7 @@ class HttpServer { httpConfig.tls = !httpConfig.tls ? false : httpConfig.tls; httpConfig.hostname = !httpConfig.hostname ? 'localhost' : httpConfig.hostname; this.limit = !httpConfig.limit ? '100kb' : httpConfig.limit; + this.apiAuth = Boolean(httpConfig.apiAuth); this.app = express(); @@ -54,6 +56,7 @@ class HttpServer { this.o2TokenService = new O2TokenService(jwtConfig); if (connectIdConfig) { + assert(!this.apiAuth || connectIdConfig.api_redirect_uri, 'Missing config value: api_redirect_uri'); this.openid = new OpenId(connectIdConfig); this.openid.createIssuer().catch(() => process.exit(1)); this.ipAddressWhitelist = !connectIdConfig.sa_whitelist ? '127.0.0.1' : connectIdConfig.sa_whitelist; diff --git a/Framework/Backend/test/mocha-http.js b/Framework/Backend/test/mocha-http.js index 859080466..a355f7609 100644 --- a/Framework/Backend/test/mocha-http.js +++ b/Framework/Backend/test/mocha-http.js @@ -16,11 +16,13 @@ process.env.NODE_ENV = 'test'; const request = require('supertest'); const assert = require('assert'); +const sinon = require('sinon'); const path = require('path'); const url = require('url'); const config = require('./../config-default.json'); const O2TokenService = require('./../services/O2TokenService.js'); const HttpServer = require('./../http/server'); +const OpenId = require('./../http/openid.js'); const { parseUrlParameters } = require('../http/parseUrlParameters.js'); const { buildUrl } = require('../http/buildUrl.js'); @@ -283,7 +285,8 @@ describe('HTTP server', () => { describe('HTTP constructor checks', () => { let httpServer; afterEach(async () => { - await httpServer.close(); + await httpServer?.close(); + httpServer = null; }); it('should succesfully add default limit for request body size to 100kb', async () => { httpServer = new HttpServer(config.http, config.jwt); @@ -295,6 +298,34 @@ describe('HTTP constructor checks', () => { httpServer = new HttpServer(conf, config.jwt); assert.strictEqual(httpServer.limit, '10Mb', 'Provided limit was not set'); }); + it('should default apiAuth to false', () => { + httpServer = new HttpServer(config.http, config.jwt); + assert.strictEqual(httpServer.apiAuth, false); + }); + it('should enable apiAuth when passed as true', () => { + httpServer = new HttpServer({ ...config.http, apiAuth: true }, config.jwt); + assert.strictEqual(httpServer.apiAuth, true); + }); + + describe('with OpenID mocked', () => { + // Skip issuer as it'll exit the process if the well-known endpoint is unreachable + before(() => sinon.stub(OpenId.prototype, 'createIssuer').resolves()); + after(() => sinon.restore()); + + it('should throw when apiAuth is enabled but api_redirect_uri is missing', () => { + const openIdConf = { ...config.openId, api_redirect_uri: undefined }; + assert.throws( + () => new HttpServer({ ...config.http, apiAuth: true }, config.jwt, openIdConf), + /Missing config value: api_redirect_uri/, + ); + }); + it('should not throw when apiAuth is enabled and api_redirect_uri is provided', () => { + const openIdConf = { ...config.openId, api_redirect_uri: 'http://localhost/api/auth/callback' }; + assert.doesNotThrow(() => { + httpServer = new HttpServer({ ...config.http, apiAuth: true }, config.jwt, openIdConf); + }); + }); + }); }); describe('URL parameters extraction checks', () => { diff --git a/Framework/docs/guide/http-server.md b/Framework/docs/guide/http-server.md index b8bed2252..1638540aa 100644 --- a/Framework/docs/guide/http-server.md +++ b/Framework/docs/guide/http-server.md @@ -9,7 +9,7 @@ #### Instance ```js const {HttpServer} = require('@aliceo2/web-ui'); -HttpServer({port: PORT, hostname: HOSTNAME, tls: TLS_ENABLED, portSecure: HTTPS_PORT, key: TLS_KEY, cert: TLS_CERT, autoListen: AUTO_LISTEN}, JWT_CONF, OPENID_CONF); +HttpServer({port: PORT, hostname: HOSTNAME, tls: TLS_ENABLED, portSecure: HTTPS_PORT, key: TLS_KEY, cert: TLS_CERT, autoListen: AUTO_LISTEN, limit: LIMIT, apiAuth: API_AUTH}, JWT_CONF, OPENID_CONF); ``` Where: * `HTTP_CONF` consists of following fields: @@ -21,6 +21,7 @@ Where: * [`TLS_CERT`] - certificate filepath, TLS must be enabled * [`AUTO_LISTEN`] - flag that enables/disables automatic listening (default: `true`) * [`LIMIT`] - controls the maximum request body size. Defaults to '100kb'. + * [`API_AUTH`] - flag that enables/disables the `/api/auth` routes, `OPENID_CONF` must then set `api_redirect_uri` (default: `false`) * [`JWT_CONF`] - JWT module config, see [JWT module](json-tokens.md) * [`OPENID_CONF`] - OpenID config, see [OpenID Connect module](openid.md) diff --git a/Framework/docs/guide/openid.md b/Framework/docs/guide/openid.md index 4c6f6a229..f7e828954 100644 --- a/Framework/docs/guide/openid.md +++ b/Framework/docs/guide/openid.md @@ -5,13 +5,14 @@ It defines how to handle service accounts. ### Instance ```js -new OpenId({secret: SECRET, id: ID, redirect_uri: REDIRECT_URI, well_known: WELL_KNOWN[, timeout: TIMEOUT, sa_whitelist: SA_WHITELIST, sa_role: SA_ROLE]}); +new OpenId({secret: SECRET, id: ID, redirect_uri: REDIRECT_URI, well_known: WELL_KNOWN[, api_redirect_uri: API_REDIRECT_URI, timeout: TIMEOUT, sa_whitelist: SA_WHITELIST, sa_role: SA_ROLE]}); ``` Where: * `SECRET` - Application secret * `ID` - Application ID * `REDIRECT_URI` - Authentication callback * `WELL_KNOWN` - So-called "well-known" endpoint defining OpenID configuration + * [`API_REDIRECT_URI`] - API authentication callback, required when HTTP server's `apiAuth` is enabled * [`TIMEOUT`] - OpenID timeout in milliseconds * [`SA_WHITELIST`] - IP address range to accept service accounts from * [`SA_ROLE`] - OpenID role that service account needs to access