diff --git a/.env.development b/.env.development index d3c376d7..6bc281bf 100644 --- a/.env.development +++ b/.env.development @@ -1,2 +1,4 @@ VITE_API_URI=http://localhost:8080 -VITE_SERVER_URI=http://localhost:8000 \ No newline at end of file +VITE_SERVER_URI=http://localhost:8000 + +COOKIE_SECRET="cookie secret" \ No newline at end of file diff --git a/.env.production b/.env.production index 2a19752c..06b4819a 100644 --- a/.env.production +++ b/.env.production @@ -1 +1,3 @@ -VITE_API_URI= \ No newline at end of file +VITE_API_URI= + +COOKIE_SECRET= \ No newline at end of file diff --git a/config.schema.json b/config.schema.json index a220099a..735d2359 100644 --- a/config.schema.json +++ b/config.schema.json @@ -6,7 +6,6 @@ "type": "object", "properties": { "proxyUrl": { "type": "string" }, - "cookieSecret": { "type": "string" }, "sessionMaxAgeHours": { "type": "number" }, "api": { "description": "Third party APIs", diff --git a/proxy.config.json b/proxy.config.json index df082a2e..bb66a253 100644 --- a/proxy.config.json +++ b/proxy.config.json @@ -1,6 +1,5 @@ { "proxyUrl": "https://github.com", - "cookieSecret": "cookie secret", "sessionMaxAgeHours": 12, "tempPassword": { "sendEmail": false, diff --git a/scripts/doc-schema.js b/scripts/doc-schema.js index bb74820a..da2f8c9a 100644 --- a/scripts/doc-schema.js +++ b/scripts/doc-schema.js @@ -19,8 +19,10 @@ try { ]).toString('utf-8'); console.log(genDocOutput); - const schemaDoc = readFileSync(`${tempdir}${sep}schema.md`, 'utf-8') - .replace(/# GitProxy configuration file/g, '# Schema Reference'); // https://github.com/finos/git-proxy/pull/327#discussion_r1377343213 + const schemaDoc = readFileSync(`${tempdir}${sep}schema.md`, 'utf-8').replace( + /# GitProxy configuration file/g, + '# Schema Reference', + ); // https://github.com/finos/git-proxy/pull/327#discussion_r1377343213 const docString = `--- title: Schema Reference description: JSON schema reference documentation for GitProxy diff --git a/src/config/env.js b/src/config/env.js index 709f8bbb..94f5ab58 100644 --- a/src/config/env.js +++ b/src/config/env.js @@ -1,3 +1,7 @@ -const { GIT_PROXY_SERVER_PORT = 8000, GIT_PROXY_HTTPS_SERVER_PORT = 8443, GIT_PROXY_UI_PORT = 8080 } = process.env; +const { + GIT_PROXY_SERVER_PORT = 8000, + GIT_PROXY_HTTPS_SERVER_PORT = 8443, + GIT_PROXY_UI_PORT = 8080, +} = process.env; exports.Vars = { GIT_PROXY_SERVER_PORT, GIT_PROXY_HTTPS_SERVER_PORT, GIT_PROXY_UI_PORT }; diff --git a/src/config/index.js b/src/config/index.js index a1ebbceb..823b6fa5 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -13,7 +13,7 @@ let _authentication = defaultSettings.authentication; let _tempPassword = defaultSettings.tempPassword; let _proxyUrl = defaultSettings.proxyUrl; let _api = defaultSettings.api; -let _cookieSecret = defaultSettings.cookieSecret; +const _cookieSecret = process.env.COOKIE_SECRET; let _sessionMaxAgeHours = defaultSettings.sessionMaxAgeHours; let _sslKeyPath = defaultSettings.sslKeyPemPath; let _sslCertPath = defaultSettings.sslCertPemPath; @@ -100,8 +100,8 @@ const getAPIs = () => { }; const getCookieSecret = () => { - if (_userSettings && _userSettings.cookieSecret) { - _cookieSecret = _userSettings.cookieSecret; + if (!_cookieSecret) { + throw new Error('COOKIE_SECRET is not defined in the environment variables.'); } return _cookieSecret; }; @@ -167,7 +167,7 @@ const getPlugins = () => { _plugins = _userSettings.plugins; } return _plugins; -} +}; const getSSLKeyPath = () => { if (_userSettings && _userSettings.sslKeyPemPath) { diff --git a/src/plugin.js b/src/plugin.js index b02e3bb8..7fa80d1a 100644 --- a/src/plugin.js +++ b/src/plugin.js @@ -12,9 +12,11 @@ function isCompatiblePlugin(obj, propertyName = 'isGitProxyPlugin') { // valid plugin objects will have the appropriate property set to true // if the prototype chain is exhausted, return false while (obj != null) { - if (Object.prototype.hasOwnProperty.call(obj, propertyName) && + if ( + Object.prototype.hasOwnProperty.call(obj, propertyName) && obj.isGitProxyPlugin && - Object.keys(obj).includes('exec')) { + Object.keys(obj).includes('exec') + ) { return true; } obj = Object.getPrototypeOf(obj); @@ -34,7 +36,7 @@ function isCompatiblePlugin(obj, propertyName = 'isGitProxyPlugin') { class PluginLoader { constructor(targets) { /** - * List of Node module specifiers to load as plugins. It can be a relative path, an + * List of Node module specifiers to load as plugins. It can be a relative path, an * absolute path, or a module name (which can include scoped packages like '@bar/baz'). * @type {string[]} * @public @@ -64,25 +66,25 @@ class PluginLoader { */ async load() { try { - const modulePromises = this.targets.map(target => - this._loadPluginModule(target).catch(error => { + const modulePromises = this.targets.map((target) => + this._loadPluginModule(target).catch((error) => { console.error(`Failed to load plugin: ${error}`); // TODO: log.error() return Promise.reject(error); // Or return an error object to handle it later - }) + }), ); const moduleResults = await Promise.allSettled(modulePromises); const loadedModules = moduleResults - .filter(result => result.status === 'fulfilled' && result.value !== null) - .map(result => result.value); + .filter((result) => result.status === 'fulfilled' && result.value !== null) + .map((result) => result.value); console.log(`Found ${loadedModules.length} plugin modules`); // TODO: log.debug() - const pluginTypeResultPromises = loadedModules.map(mod => - this._getPluginObjects(mod).catch(error => { + const pluginTypeResultPromises = loadedModules.map((mod) => + this._getPluginObjects(mod).catch((error) => { console.error(`Failed to cast plugin objects: ${error}`); // TODO: log.error() return Promise.reject(error); // Or return an error object to handle it later - }) + }), ); const settledPluginTypeResults = await Promise.allSettled(pluginTypeResultPromises); @@ -90,16 +92,16 @@ class PluginLoader { * @type {PluginTypeResult[]} List of resolved PluginTypeResult objects */ const pluginTypeResults = settledPluginTypeResults - .filter(result => result.status === 'fulfilled' && result.value !== null) - .map(result => result.value); + .filter((result) => result.status === 'fulfilled' && result.value !== null) + .map((result) => result.value); for (const result of pluginTypeResults) { - this.pushPlugins.push(...result.pushAction) - this.pullPlugins.push(...result.pullAction) + this.pushPlugins.push(...result.pushAction); + this.pullPlugins.push(...result.pullAction); } const combinedPlugins = [...this.pushPlugins, ...this.pullPlugins]; - combinedPlugins.forEach(plugin => { + combinedPlugins.forEach((plugin) => { console.log(`Loaded plugin: ${plugin.constructor.name}`); }); } catch (error) { @@ -137,7 +139,9 @@ class PluginLoader { console.log('found pull plugin', potentialModule.constructor.name); plugins.pullAction.push(potentialModule); } else { - console.error(`Error: Object ${potentialModule.constructor.name} does not seem to be a compatible plugin type`); + console.error( + `Error: Object ${potentialModule.constructor.name} does not seem to be a compatible plugin type`, + ); } } @@ -145,7 +149,7 @@ class PluginLoader { // `module.exports = new ProxyPlugin()` in CJS or `exports default new ProxyPlugin()` in ESM // the "module" is a single object that could be a plugin if (isCompatiblePlugin(pluginModule)) { - handlePlugin(pluginModule) + handlePlugin(pluginModule); } else { // handle the typical case of a module which exports multiple objects // module.exports = { x, y } (CJS) or multiple `export ...` statements (ESM) @@ -173,20 +177,20 @@ class ProxyPlugin { * A plugin which executes a function when receiving a git push request. */ class PushActionPlugin extends ProxyPlugin { -/** - * Wrapper class which contains at least one function executed as part of the action chain for git push operations. - * The function must be called `exec` and take in two parameters: an Express Request (req) and the current Action - * executed in the chain (action). This function should return a Promise that resolves to an Action. - * - * Optionally, child classes which extend this can simply define the `exec` function as their own property. - * This is the preferred implementation when a custom plugin (subclass) has its own state or additional methods - * that are required. - * - * @param {function} exec - A function that: - * - Takes in an Express Request object as the first parameter (`req`). - * - Takes in an Action object as the second parameter (`action`). - * - Returns a Promise that resolves to an Action. - */ + /** + * Wrapper class which contains at least one function executed as part of the action chain for git push operations. + * The function must be called `exec` and take in two parameters: an Express Request (req) and the current Action + * executed in the chain (action). This function should return a Promise that resolves to an Action. + * + * Optionally, child classes which extend this can simply define the `exec` function as their own property. + * This is the preferred implementation when a custom plugin (subclass) has its own state or additional methods + * that are required. + * + * @param {function} exec - A function that: + * - Takes in an Express Request object as the first parameter (`req`). + * - Takes in an Action object as the second parameter (`action`). + * - Returns a Promise that resolves to an Action. + */ constructor(exec) { super(); this.isGitProxyPushActionPlugin = true; @@ -202,11 +206,11 @@ class PullActionPlugin extends ProxyPlugin { * Wrapper class which contains at least one function executed as part of the action chain for git pull operations. * The function must be called `exec` and take in two parameters: an Express Request (req) and the current Action * executed in the chain (action). This function should return a Promise that resolves to an Action. - * + * * Optionally, child classes which extend this can simply define the `exec` function as their own property. * This is the preferred implementation when a custom plugin (subclass) has its own state or additional methods * that are required. - * + * * @param {function} exec - A function that: * - Takes in an Express Request object as the first parameter (`req`). * - Takes in an Action object as the second parameter (`action`). @@ -224,4 +228,4 @@ module.exports = { PushActionPlugin, PullActionPlugin, isCompatiblePlugin, -} +}; diff --git a/src/proxy/actions/index.js b/src/proxy/actions/index.js index 173bb158..f763f6fb 100644 --- a/src/proxy/actions/index.js +++ b/src/proxy/actions/index.js @@ -4,4 +4,4 @@ const { Step } = require('./Step'); module.exports = { Action, Step, -} +}; diff --git a/src/proxy/index.js b/src/proxy/index.js index 3bd38740..b2934253 100644 --- a/src/proxy/index.js +++ b/src/proxy/index.js @@ -1,9 +1,9 @@ const proxyApp = require('express')(); const bodyParser = require('body-parser'); -const http = require("http"); -const https = require("https"); +const http = require('http'); +const https = require('https'); const fs = require('fs'); -const path = require("path"); +const path = require('path'); const router = require('./routes').router; const config = require('../config'); const db = require('../db'); @@ -17,7 +17,7 @@ const options = { limit: '100000kb', type: '*/*', key: fs.readFileSync(path.join(__dirname, config.getSSLKeyPath())), - cert: fs.readFileSync(path.join(__dirname, config.getSSLCertPath())) + cert: fs.readFileSync(path.join(__dirname, config.getSSLCertPath())), }; // Setup the proxy middleware @@ -25,10 +25,10 @@ proxyApp.use(bodyParser.raw(options)); proxyApp.use('/', router); const start = async () => { - const plugins = config.getPlugins(); - const pluginLoader = new PluginLoader(plugins); - await pluginLoader.load(); - chain.chainPluginLoader = pluginLoader; + const plugins = config.getPlugins(); + const pluginLoader = new PluginLoader(plugins); + await pluginLoader.load(); + chain.chainPluginLoader = pluginLoader; // Check to see if the default repos are in the repo list const defaultAuthorisedRepoList = config.getAuthorisedList(); const allowedList = await db.getRepos(); diff --git a/src/ui/views/Login/Login.jsx b/src/ui/views/Login/Login.jsx index 46c989ca..16789ea7 100644 --- a/src/ui/views/Login/Login.jsx +++ b/src/ui/views/Login/Login.jsx @@ -104,7 +104,7 @@ export default function UserProfile() { width={'150px'} src={logo} alt='logo' - data-test ="git-proxy-logo" + data-test='git-proxy-logo' /> @@ -119,7 +119,7 @@ export default function UserProfile() { value={username} onChange={(e) => setUsername(e.target.value)} autoFocus={true} - data-test ='username' + data-test='username' /> @@ -133,7 +133,7 @@ export default function UserProfile() { type='password' value={password} onChange={(e) => setPassword(e.target.value)} - data-test ='password' + data-test='password' /> @@ -141,7 +141,13 @@ export default function UserProfile() { {!isLoading ? ( - ) : ( diff --git a/test/chain.test.js b/test/chain.test.js index 33d5750a..7e7afb41 100644 --- a/test/chain.test.js +++ b/test/chain.test.js @@ -63,7 +63,7 @@ describe('proxy chain', function () { // Re-require the chain module after stubbing processors chain = require('../src/proxy/chain'); - chain.chainPluginLoader = new PluginLoader([]) + chain.chainPluginLoader = new PluginLoader([]); }); afterEach(() => { @@ -108,7 +108,11 @@ describe('proxy chain', function () { mockPushProcessors.checkUserPushPermission.resolves(continuingAction); // this stops the chain from further execution - mockPushProcessors.checkIfWaitingAuth.resolves({ type: 'push', continue: () => false, allowPush: false }); + mockPushProcessors.checkIfWaitingAuth.resolves({ + type: 'push', + continue: () => false, + allowPush: false, + }); const result = await chain.executeChain(req); expect(mockPreProcessors.parseAction.called).to.be.true; @@ -136,7 +140,11 @@ describe('proxy chain', function () { mockPushProcessors.checkAuthorEmails.resolves(continuingAction); mockPushProcessors.checkUserPushPermission.resolves(continuingAction); // this stops the chain from further execution - mockPushProcessors.checkIfWaitingAuth.resolves({ type: 'push', continue: () => true, allowPush: true }); + mockPushProcessors.checkIfWaitingAuth.resolves({ + type: 'push', + continue: () => true, + allowPush: true, + }); const result = await chain.executeChain(req); expect(mockPreProcessors.parseAction.called).to.be.true; @@ -232,5 +240,5 @@ describe('proxy chain', function () { expect(mockPushProcessors.checkRepoInAuthorisedList.called).to.be.false; expect(mockPushProcessors.parsePush.called).to.be.false; expect(result).to.deep.equal(action); - }) + }); }); diff --git a/test/fixtures/baz.js b/test/fixtures/baz.js index 6829bdac..a1c32ac7 100644 --- a/test/fixtures/baz.js +++ b/test/fixtures/baz.js @@ -1,4 +1,4 @@ module.exports = { foo: 'bar', baz: {}, -} \ No newline at end of file +}; diff --git a/test/fixtures/proxy.config.invalid-1.json b/test/fixtures/proxy.config.invalid-1.json index 541044ab..6a4d937c 100644 --- a/test/fixtures/proxy.config.invalid-1.json +++ b/test/fixtures/proxy.config.invalid-1.json @@ -6,4 +6,4 @@ "url": "https://www.github.com/finos/git-proxy.git" } ] -} \ No newline at end of file +} diff --git a/test/fixtures/proxy.config.invalid-2.json b/test/fixtures/proxy.config.invalid-2.json index fa3e48bb..ee1795a1 100644 --- a/test/fixtures/proxy.config.invalid-2.json +++ b/test/fixtures/proxy.config.invalid-2.json @@ -6,4 +6,4 @@ "link": "https://www.github.com/finos/git-proxy" } ] -} \ No newline at end of file +} diff --git a/test/fixtures/proxy.config.valid-1.json b/test/fixtures/proxy.config.valid-1.json index 9e26dfee..0967ef42 100644 --- a/test/fixtures/proxy.config.valid-1.json +++ b/test/fixtures/proxy.config.valid-1.json @@ -1 +1 @@ -{} \ No newline at end of file +{} diff --git a/test/fixtures/proxy.config.valid-2.json b/test/fixtures/proxy.config.valid-2.json index c18dc1ea..6c2c9262 100644 --- a/test/fixtures/proxy.config.valid-2.json +++ b/test/fixtures/proxy.config.valid-2.json @@ -11,4 +11,4 @@ "url": "git@github.com:finos/git-proxy-test.git" } ] -} \ No newline at end of file +} diff --git a/test/fixtures/test-package/multiple-export.js b/test/fixtures/test-package/multiple-export.js index 3963c025..b953ce39 100644 --- a/test/fixtures/test-package/multiple-export.js +++ b/test/fixtures/test-package/multiple-export.js @@ -1,6 +1,5 @@ const { PushActionPlugin, PullActionPlugin } = require('@osp0/finos-git-proxy/plugin'); - module.exports = { foo: new PushActionPlugin(async (req, action) => { console.log('PushActionPlugin: ', action); @@ -10,4 +9,4 @@ module.exports = { console.log('PullActionPlugin: ', action); return action; }), -} \ No newline at end of file +}; diff --git a/test/plugin.test.js b/test/plugin.test.js index cba96fa7..e542835c 100644 --- a/test/plugin.test.js +++ b/test/plugin.test.js @@ -27,9 +27,8 @@ describe('loading plugins from packages', function () { const loader = new PluginLoader([join(testPackagePath, 'default-export.js')]); await loader.load(); expect(loader.pushPlugins.length).to.equal(1); - expect(loader.pushPlugins.every(p => isCompatiblePlugin(p))).to.be.true; - expect(loader.pushPlugins[0]) - .to.be.an.instanceOf(PushActionPlugin); + expect(loader.pushPlugins.every((p) => isCompatiblePlugin(p))).to.be.true; + expect(loader.pushPlugins[0]).to.be.an.instanceOf(PushActionPlugin); }).timeout(10000); it('should load multiple plugins from a module that match the plugin class (module.exports = { pluginFoo, pluginBar })', async function () { @@ -37,9 +36,11 @@ describe('loading plugins from packages', function () { await loader.load(); expect(loader.pushPlugins.length).to.equal(1); expect(loader.pullPlugins.length).to.equal(1); - expect(loader.pushPlugins.every(p => isCompatiblePlugin(p))).to.be.true; - expect(loader.pushPlugins.every(p => isCompatiblePlugin(p, 'isGitProxyPushActionPlugin'))).to.be.true; - expect(loader.pullPlugins.every(p => isCompatiblePlugin(p, 'isGitProxyPullActionPlugin'))).to.be.true; + expect(loader.pushPlugins.every((p) => isCompatiblePlugin(p))).to.be.true; + expect(loader.pushPlugins.every((p) => isCompatiblePlugin(p, 'isGitProxyPushActionPlugin'))).to + .be.true; + expect(loader.pullPlugins.every((p) => isCompatiblePlugin(p, 'isGitProxyPullActionPlugin'))).to + .be.true; expect(loader.pushPlugins[0]).to.be.instanceOf(PushActionPlugin); expect(loader.pullPlugins[0]).to.be.instanceOf(PullActionPlugin); }).timeout(10000); @@ -48,8 +49,9 @@ describe('loading plugins from packages', function () { const loader = new PluginLoader([join(testPackagePath, 'subclass.js')]); await loader.load(); expect(loader.pushPlugins.length).to.equal(1); - expect(loader.pushPlugins.every(p => isCompatiblePlugin(p))).to.be.true; - expect(loader.pushPlugins.every(p => isCompatiblePlugin(p, 'isGitProxyPushActionPlugin'))).to.be.true; + expect(loader.pushPlugins.every((p) => isCompatiblePlugin(p))).to.be.true; + expect(loader.pushPlugins.every((p) => isCompatiblePlugin(p, 'isGitProxyPushActionPlugin'))).to + .be.true; expect(loader.pushPlugins[0]).to.be.instanceOf(PushActionPlugin); }).timeout(10000); diff --git a/test/testConfig.test.js b/test/testConfig.test.js index 125ee7b4..925af039 100644 --- a/test/testConfig.test.js +++ b/test/testConfig.test.js @@ -16,8 +16,8 @@ describe('default configuration', function () { expect(config.getDatabase()).to.be.eql(defaultSettings.sink[0]); expect(config.getTempPasswordConfig()).to.be.eql(defaultSettings.tempPassword); expect(config.getAuthorisedList()).to.be.eql(defaultSettings.authorisedList); - expect(config.getSSLKeyPath()).to.be.eql("../../certs/key.pem"); - expect(config.getSSLCertPath()).to.be.eql("../../certs/cert.pem"); + expect(config.getSSLKeyPath()).to.be.eql('../../certs/key.pem'); + expect(config.getSSLCertPath()).to.be.eql('../../certs/cert.pem'); }); after(function () { delete require.cache[require.resolve('../src/config')]; @@ -94,8 +94,8 @@ describe('user configuration', function () { it('should override default settings for SSL certificate', function () { const user = { - sslKeyPemPath: "my-key.pem", - sslCertPemPath: "my-cert.pem" + sslKeyPemPath: 'my-key.pem', + sslCertPemPath: 'my-cert.pem', }; fs.writeFileSync(tempUserFile, JSON.stringify(user)); @@ -116,21 +116,14 @@ describe('validate config files', function () { const config = require('../src/config/file'); it('all valid config files should pass validation', function () { - const validConfigFiles = [ - 'proxy.config.valid-1.json', - 'proxy.config.valid-2.json', - ]; + const validConfigFiles = ['proxy.config.valid-1.json', 'proxy.config.valid-2.json']; for (const testConfigFile of validConfigFiles) { - expect(config.validate(path.join(__dirname, fixtures, testConfigFile))).to - .be.true; + expect(config.validate(path.join(__dirname, fixtures, testConfigFile))).to.be.true; } }); it('all invalid config files should fail validation', function () { - const invalidConfigFiles = [ - 'proxy.config.invalid-1.json', - 'proxy.config.invalid-2.json', - ]; + const invalidConfigFiles = ['proxy.config.invalid-1.json', 'proxy.config.invalid-2.json']; for (const testConfigFile of invalidConfigFiles) { const test = function () { config.validate(path.join(__dirname, fixtures, testConfigFile)); diff --git a/test/testRouteFilter.js b/test/testRouteFilter.js index 8a0262d1..e9ec026b 100644 --- a/test/testRouteFilter.js +++ b/test/testRouteFilter.js @@ -1,8 +1,7 @@ /* eslint-disable max-len */ const chai = require('chai'); const validGitRequest = require('../src/proxy/routes').validGitRequest; -const stripGitHubFromGitPath = - require('../src/proxy/routes').stripGitHubFromGitPath; +const stripGitHubFromGitPath = require('../src/proxy/routes').stripGitHubFromGitPath; chai.should(); @@ -10,11 +9,9 @@ const expect = chai.expect; describe('url filters for proxying ', function () { it('stripGitHubFromGitPath should return the sanitized URL with owner & repo removed', function () { - expect( - stripGitHubFromGitPath( - '/octocat/hello-world.git/info/refs?service=git-upload-pack', - ), - ).eq('/info/refs?service=git-upload-pack'); + expect(stripGitHubFromGitPath('/octocat/hello-world.git/info/refs?service=git-upload-pack')).eq( + '/info/refs?service=git-upload-pack', + ); }); it('stripGitHubFromGitPath should return undefined if the url', function () {