diff --git a/cypress/e2e/login.cy.js b/cypress/e2e/login.cy.js index 16da32f83..25d80e438 100644 --- a/cypress/e2e/login.cy.js +++ b/cypress/e2e/login.cy.js @@ -38,8 +38,10 @@ describe('Login page', () => { // Validates that OIDC is configured correctly it('should redirect to /oidc', () => { + // Set intercept first, since redirect on click can be quick + cy.intercept('GET', '/api/auth/oidc').as('oidcRedirect'); cy.get('[data-test="oidc-login"]').click(); - cy.url().should('include', '/oidc'); + cy.wait('@oidcRedirect'); }); }); }); diff --git a/package-lock.json b/package-lock.json index 2f57df5ad..3a2c99137 100644 --- a/package-lock.json +++ b/package-lock.json @@ -88,6 +88,7 @@ "mocha": "^10.8.2", "nyc": "^17.0.0", "prettier": "^3.0.0", + "proxyquire": "^2.1.3", "sinon": "^19.0.2", "ts-mocha": "^11.1.0", "ts-node": "^10.9.2", @@ -7258,6 +7259,20 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/fill-keys": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/fill-keys/-/fill-keys-1.0.2.tgz", + "integrity": "sha512-tcgI872xXjwFF4xgQmLxi76GnwJG3g/3isB1l4/G5Z4zrbddGpBjqZCO9oEAcB5wX0Hj/5iQB3toxfO7in1hHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-object": "~1.0.1", + "merge-descriptors": "~1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -8411,12 +8426,16 @@ } }, "node_modules/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "dev": true, + "license": "MIT", "dependencies": { - "hasown": "^2.0.0" + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -8597,6 +8616,16 @@ "node": ">=8" } }, + "node_modules/is-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.2.tgz", + "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-path-inside": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", @@ -10193,6 +10222,13 @@ "node": ">=10" } }, + "node_modules/module-not-found-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/module-not-found-error/-/module-not-found-error-1.0.1.tgz", + "integrity": "sha512-pEk4ECWQXV6z2zjhRZUongnLJNUeGQJ3w6OQ5ctGwD+i5o93qjRQUk2Rt6VdNeu3sEP0AB4LcfvdebpxBRVr4g==", + "dev": true, + "license": "MIT" + }, "node_modules/moment": { "version": "2.30.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", @@ -11338,6 +11374,39 @@ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, + "node_modules/proxyquire": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/proxyquire/-/proxyquire-2.1.3.tgz", + "integrity": "sha512-BQWfCqYM+QINd+yawJz23tbBM40VIGXOdDw3X344KcclI/gtBbdWF6SlQ4nK/bYhF9d27KYug9WzljHC6B9Ysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-keys": "^1.0.2", + "module-not-found-error": "^1.0.1", + "resolve": "^1.11.1" + } + }, + "node_modules/proxyquire/node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", diff --git a/package.json b/package.json index ad837a2fb..0f0e28b52 100644 --- a/package.json +++ b/package.json @@ -109,6 +109,7 @@ "mocha": "^10.8.2", "nyc": "^17.0.0", "prettier": "^3.0.0", + "proxyquire": "^2.1.3", "sinon": "^19.0.2", "ts-mocha": "^11.1.0", "ts-node": "^10.9.2", diff --git a/proxy.config.json b/proxy.config.json index 241811c3e..be7aa2327 100644 --- a/proxy.config.json +++ b/proxy.config.json @@ -53,6 +53,17 @@ "baseDN": "", "searchBase": "" } + }, + { + "type": "openidconnect", + "enabled": false, + "oidcConfig": { + "issuer": "", + "clientID": "", + "clientSecret": "", + "callbackURL": "", + "scope": "" + } } ], "api": { diff --git a/src/config/index.ts b/src/config/index.ts index 970a5c61e..17df09555 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -89,27 +89,29 @@ export const getDatabase = () => { throw Error('No database cofigured!'); }; -// Gets the configured authentication method, defaults to local -export const getAuthentication = () => { +/** + * Get the list of enabled authentication methods + * @return {Array} List of enabled authentication methods + */ +export const getAuthMethods = () => { if (_userSettings !== null && _userSettings.authentication) { _authentication = _userSettings.authentication; } - for (const ix in _authentication) { - if (!ix) continue; - const auth = _authentication[ix]; - if (auth.enabled) { - return auth; - } + + const enabledAuthMethods = _authentication.filter((auth) => auth.enabled); + + if (enabledAuthMethods.length === 0) { + throw new Error("No authentication method enabled"); } - throw Error('No authentication cofigured!'); + return enabledAuthMethods; }; // Log configuration to console export const logConfiguration = () => { console.log(`authorisedList = ${JSON.stringify(getAuthorisedList())}`); console.log(`data sink = ${JSON.stringify(getDatabase())}`); - console.log(`authentication = ${JSON.stringify(getAuthentication())}`); + console.log(`authentication = ${JSON.stringify(getAuthMethods())}`); console.log(`rateLimit = ${JSON.stringify(getRateLimit())}`); }; diff --git a/src/service/passport/activeDirectory.js b/src/service/passport/activeDirectory.js index 466f57b16..372868133 100644 --- a/src/service/passport/activeDirectory.js +++ b/src/service/passport/activeDirectory.js @@ -1,16 +1,19 @@ -const configure = () => { - const passport = require('passport'); - const ActiveDirectoryStrategy = require('passport-activedirectory'); - const config = require('../../config').getAuthentication(); - const adConfig = config.adConfig; +const ActiveDirectoryStrategy = require('passport-activedirectory'); +const ldaphelper = require('./ldaphelper'); + +const configure = (passport) => { const db = require('../../db'); - const userGroup = config.userGroup; - const adminGroup = config.adminGroup; - const domain = config.domain; + + // We can refactor this by normalizing auth strategy config and pass it directly into the configure() function, + // ideally when we convert this to TS. + const authMethods = require('../../config').getAuthMethods(); + const config = authMethods.find((method) => method.type.toLowerCase() === "activeDirectory"); + const adConfig = config.adConfig; + + const { userGroup, adminGroup, domain } = config; console.log(`AD User Group: ${userGroup}, AD Admin Group: ${adminGroup}`); - const ldaphelper = require('./ldaphelper'); passport.use( new ActiveDirectoryStrategy( { @@ -19,42 +22,47 @@ const configure = () => { ldap: adConfig, }, async function (req, profile, ad, done) { - profile.username = profile._json.sAMAccountName.toLowerCase(); - profile.email = profile._json.mail; - profile.id = profile.username; - req.user = profile; - - console.log( - `passport.activeDirectory: resolved login ${ - profile._json.userPrincipalName - }, profile=${JSON.stringify(profile)}`, - ); - // First check to see if the user is in the usergroups - const isUser = await ldaphelper.isUserInAdGroup(profile.username, domain, userGroup); - - if (!isUser) { - const message = `User it not a member of ${userGroup}`; - return done(message, null); - } + try { + profile.username = profile._json.sAMAccountName?.toLowerCase(); + profile.email = profile._json.mail; + profile.id = profile.username; + req.user = profile; - // Now check if the user is an admin - const isAdmin = await ldaphelper.isUserInAdGroup(profile.username, domain, adminGroup); + console.log( + `passport.activeDirectory: resolved login ${ + profile._json.userPrincipalName + }, profile=${JSON.stringify(profile)}`, + ); + // First check to see if the user is in the usergroups + const isUser = await ldaphelper.isUserInAdGroup(profile.username, domain, userGroup); - profile.admin = isAdmin; - console.log(`passport.activeDirectory: ${profile.username} admin=${isAdmin}`); + if (!isUser) { + const message = `User it not a member of ${userGroup}`; + return done(message, null); + } - const user = { - username: profile.username, - admin: isAdmin, - email: profile._json.mail, - displayName: profile.displayName, - title: profile._json.title, - }; + // Now check if the user is an admin + const isAdmin = await ldaphelper.isUserInAdGroup(profile.username, domain, adminGroup); - await db.updateUser(user); + profile.admin = isAdmin; + console.log(`passport.activeDirectory: ${profile.username} admin=${isAdmin}`); - return done(null, user); - }, + const user = { + username: profile.username, + admin: isAdmin, + email: profile._json.mail, + displayName: profile.displayName, + title: profile._json.title, + }; + + await db.updateUser(user); + + return done(null, user); + } catch (err) { + console.log(`Error authenticating AD user: ${err.message}`); + return done(err, null); + } + } ), ); @@ -69,4 +77,4 @@ const configure = () => { return passport; }; -module.exports.configure = configure; +module.exports = { configure }; diff --git a/src/service/passport/index.js b/src/service/passport/index.js index a2d7931ef..72918282f 100644 --- a/src/service/passport/index.js +++ b/src/service/passport/index.js @@ -1,33 +1,36 @@ +const passport = require("passport"); const local = require('./local'); const activeDirectory = require('./activeDirectory'); const oidc = require('./oidc'); const config = require('../../config'); -const authenticationConfig = config.getAuthentication(); -let _passport; + +// Allows obtaining strategy config function and type +// Keep in mind to add AuthStrategy enum when refactoring this to TS +const authStrategies = { + local: local, + activedirectory: activeDirectory, + openidconnect: oidc, +}; const configure = async () => { - const type = authenticationConfig.type.toLowerCase(); - - switch (type) { - case 'activedirectory': - _passport = await activeDirectory.configure(); - break; - case 'local': - _passport = await local.configure(); - break; - case 'openidconnect': - _passport = await oidc.configure(); - break; - default: - throw Error(`uknown authentication type ${type}`); + passport.initialize(); + + const authMethods = config.getAuthMethods(); + + for (const auth of authMethods) { + const strategy = authStrategies[auth.type.toLowerCase()]; + if (strategy && typeof strategy.configure === "function") { + await strategy.configure(passport); + } } - if (!_passport.type) { - _passport.type = type; + + if (authMethods.some(auth => auth.type.toLowerCase() === "local")) { + await local.createDefaultAdmin(); } - return _passport; -}; -module.exports.configure = configure; -module.exports.getPassport = () => { - return _passport; + return passport; }; + +const getPassport = () => passport; + +module.exports = { authStrategies, configure, getPassport }; diff --git a/src/service/passport/local.js b/src/service/passport/local.js index c75676577..8fc0b369c 100644 --- a/src/service/passport/local.js +++ b/src/service/passport/local.js @@ -1,53 +1,55 @@ -const bcrypt = require('bcryptjs'); -/* eslint-disable max-len */ -const configure = async () => { - const passport = require('passport'); - const Strategy = require('passport-local').Strategy; - const db = require('../../db'); +const bcrypt = require("bcryptjs"); +const LocalStrategy = require("passport-local").Strategy; +const db = require("../../db"); +const type = "local"; + +const configure = async (passport) => { passport.use( - new Strategy((username, password, cb) => { - db.findUser(username) - .then(async (user) => { - if (!user) { - return cb(null, false); - } - - const passwordCorrect = await bcrypt.compare(password, user.password); - - if (!passwordCorrect) { - return cb(null, false); - } - return cb(null, user); - }) - .catch((err) => { - return cb(err); - }); - }), + new LocalStrategy(async (username, password, done) => { + try { + const user = await db.findUser(username); + if (!user) { + return done(null, false, { message: "Incorrect username." }); + } + + const passwordCorrect = await bcrypt.compare(password, user.password); + if (!passwordCorrect) { + return done(null, false, { message: "Incorrect password." }); + } + + return done(null, user); + } catch (err) { + return done(err); + } + }) ); - passport.serializeUser(function (user, cb) { - cb(null, user.username); + passport.serializeUser((user, done) => { + done(null, user.username); }); - passport.deserializeUser(function (username, cb) { - db.findUser(username) - .then((user) => { - cb(null, user); - }) - .catch((err) => { - db(err, null); - }); + passport.deserializeUser(async (username, done) => { + try { + const user = await db.findUser(username); + done(null, user); + } catch (err) { + done(err, null); + } }); - const admin = await db.findUser('admin'); + passport.type = 'local'; + return passport; +}; +/** + * Create the default admin user if it doesn't exist + */ +const createDefaultAdmin = async () => { + const admin = await db.findUser("admin"); if (!admin) { - await db.createUser('admin', 'admin', 'admin@place.com', 'none', true); + await db.createUser("admin", "admin", "admin@place.com", "none", true); } - - passport.type = 'local'; - return passport; }; -module.exports.configure = configure; +module.exports = { configure, createDefaultAdmin, type }; diff --git a/src/service/passport/oidc.js b/src/service/passport/oidc.js index 904faff04..4954a4d7b 100644 --- a/src/service/passport/oidc.js +++ b/src/service/passport/oidc.js @@ -1,12 +1,14 @@ const passport = require('passport'); const db = require('../../db'); +let type; + const configure = async () => { // Temp fix for ERR_REQUIRE_ESM, will be changed when we refactor to ESM const { discovery, fetchUserInfo } = await import('openid-client'); const { Strategy } = await import('openid-client/passport'); - const config = require('../../config').getAuthentication(); - const { oidcConfig } = config; + const authMethods = require('../../config').getAuthMethods(); + const oidcConfig = authMethods.find((method) => method.type.toLowerCase() === "openidconnect")?.oidcConfig; const { issuer, clientID, clientSecret, callbackURL, scope } = oidcConfig; if (!oidcConfig || !oidcConfig.issuer) { @@ -49,23 +51,21 @@ const configure = async () => { done(err); } }) - passport.type = server.host; + console.log(`setting type to ${server.host}`) + type = server.host; return passport; } catch (error) { console.error('OIDC configuration failed:', error); throw error; } -} - - -module.exports.configure = configure; +}; /** * Handles user authentication with OIDC. - * @param {Object} userInfo the OIDC user info object - * @param {Function} done the callback function - * @return {Promise} a promise with the authenticated user or an error + * @param {*} userInfo the OIDC user info object + * @param {*} done the callback function + * @return {Promise} a promise with the authenticated user or an error */ const handleUserAuthentication = async (userInfo, done) => { try { @@ -112,3 +112,10 @@ const safelyExtractEmail = (profile) => { const getUsername = (email) => { return email ? email.split('@')[0] : ''; }; + +module.exports = { + configure, + get type() { + return type; + } +}; diff --git a/src/service/routes/auth.js b/src/service/routes/auth.js index b100b2f39..aaf2efa26 100644 --- a/src/service/routes/auth.js +++ b/src/service/routes/auth.js @@ -1,8 +1,8 @@ const express = require('express'); const router = new express.Router(); const passport = require('../passport').getPassport(); +const authStrategies = require('../passport').authStrategies; const db = require('../../db'); -const passportType = passport.type; const { GIT_PROXY_UI_HOST: uiHost = 'http://localhost', GIT_PROXY_UI_PORT: uiPort = 3000 } = process.env; router.get('/', (req, res) => { @@ -22,7 +22,7 @@ router.get('/', (req, res) => { }); }); -router.post('/login', passport.authenticate(passportType), async (req, res) => { +router.post('/login', passport.authenticate(authStrategies['local'].type), async (req, res) => { try { const currentUser = { ...req.user }; delete currentUser.password; @@ -42,10 +42,10 @@ router.post('/login', passport.authenticate(passportType), async (req, res) => { } }); -router.get('/oidc', passport.authenticate(passportType)); +router.get('/oidc', passport.authenticate(authStrategies['openidconnect'].type)); router.get('/oidc/callback', (req, res, next) => { - passport.authenticate(passportType, (err, user, info) => { + passport.authenticate(authStrategies['openidconnect'].type, (err, user, info) => { if (err) { console.error('Authentication error:', err); return res.status(401).end(); diff --git a/src/ui/components/RouteGuard/RouteGuard.tsx b/src/ui/components/RouteGuard/RouteGuard.tsx index d3146b63d..5004360f0 100644 --- a/src/ui/components/RouteGuard/RouteGuard.tsx +++ b/src/ui/components/RouteGuard/RouteGuard.tsx @@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react'; import { Navigate } from 'react-router-dom'; import { useAuth } from '../../auth/AuthProvider'; import { getUIRouteAuth } from '../../services/config'; +import CircularProgress from '@material-ui/core/CircularProgress'; interface RouteGuardProps { component: React.ComponentType; @@ -43,7 +44,7 @@ const RouteGuard = ({ component: Component, fullRoutePath }: RouteGuardProps) => }, [fullRoutePath]); if (!authChecked || isLoading) { - return
Loading...
; // TODO: Add a skeleton loader or spinner + return ; } if (loginRequired && !user) { diff --git a/src/ui/views/RepoList/Components/RepoOverview.jsx b/src/ui/views/RepoList/Components/RepoOverview.jsx index 9e98886df..826f78c97 100644 --- a/src/ui/views/RepoList/Components/RepoOverview.jsx +++ b/src/ui/views/RepoList/Components/RepoOverview.jsx @@ -1,6 +1,5 @@ import React, { useEffect } from 'react'; -import TableCell from '@material-ui/core/TableCell'; -import TableRow from '@material-ui/core/TableRow'; +import { Snackbar, TableCell, TableRow } from '@material-ui/core'; import GridContainer from '../../../components/Grid/GridContainer'; import GridItem from '../../../components/Grid/GridItem'; import { CodeReviewIcon, LawIcon, PeopleIcon } from '@primer/octicons-react'; @@ -572,6 +571,9 @@ import CodeActionButton from '../../../components/CustomButtons/CodeActionButton export default function Repositories(props) { const [github, setGitHub] = React.useState({}); + const [errorMessage, setErrorMessage] = React.useState(''); + const [snackbarOpen, setSnackbarOpen] = React.useState(false); + useEffect(() => { getGitHubRepository(); }, [props.data.project, props.data.name]); @@ -582,8 +584,9 @@ export default function Repositories(props) { .then((res) => { setGitHub(res.data); }) - .catch((err) => { - console.error(`Error fetching GitHub repository ${props.data.project}/${props.data.name}: ${err}`); + .catch((error) => { + setErrorMessage(`Error fetching GitHub repository ${props.data.project}/${props.data.name}: ${error}`); + setSnackbarOpen(true); }); }; @@ -672,6 +675,13 @@ export default function Repositories(props) { + setSnackbarOpen(false)} + message={errorMessage} + /> ); } diff --git a/test/testAuthMethods.test.js b/test/testAuthMethods.test.js new file mode 100644 index 000000000..013c79d8d --- /dev/null +++ b/test/testAuthMethods.test.js @@ -0,0 +1,61 @@ +const chai = require('chai'); +const config = require('../src/config'); +const sinon = require('sinon'); +const proxyquire = require('proxyquire'); + +chai.should(); +const expect = chai.expect; + +describe('auth methods', async () => { + it('should return a local auth method by default', async function () { + const authMethods = config.getAuthMethods(); + expect(authMethods).to.have.lengthOf(1); + expect(authMethods[0].type).to.equal('local'); + }); + + it('should return an error if no auth methods are enabled', async function () { + const newConfig = JSON.stringify({ + authentication: [ + { type: 'local', enabled: false }, + { type: 'ActiveDirectory', enabled: false }, + { type: 'openidconnect', enabled: false }, + ], + }); + + const fsStub = { + existsSync: sinon.stub().returns(true), + readFileSync: sinon.stub().returns(newConfig), + }; + + const config = proxyquire('../src/config', { + fs: fsStub, + }); + + expect(() => config.getAuthMethods()).to.throw(Error, 'No authentication method enabled'); + }); + + it('should return an array of enabled auth methods when overridden', async function () { + const newConfig = JSON.stringify({ + authentication: [ + { type: 'local', enabled: true }, + { type: 'ActiveDirectory', enabled: true }, + { type: 'openidconnect', enabled: true }, + ], + }); + + const fsStub = { + existsSync: sinon.stub().returns(true), + readFileSync: sinon.stub().returns(newConfig), + }; + + const config = proxyquire('../src/config', { + fs: fsStub, + }); + + const authMethods = config.getAuthMethods(); + expect(authMethods).to.have.lengthOf(3); + expect(authMethods[0].type).to.equal('local'); + expect(authMethods[1].type).to.equal('ActiveDirectory'); + expect(authMethods[2].type).to.equal('openidconnect'); + }) +}); diff --git a/test/testConfig.test.js b/test/testConfig.test.js index 23b3b1029..08d528e2d 100644 --- a/test/testConfig.test.js +++ b/test/testConfig.test.js @@ -11,8 +11,9 @@ describe('default configuration', function () { it('should use default values if no user-settings.json file exists', function () { const config = require('../src/config'); config.logConfiguration(); + const enabledMethods = defaultSettings.authentication.filter(method => method.enabled); - expect(config.getAuthentication()).to.be.eql(defaultSettings.authentication[0]); + expect(config.getAuthMethods()).to.deep.equal(enabledMethods); 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); @@ -48,9 +49,10 @@ describe('user configuration', function () { fs.writeFileSync(tempUserFile, JSON.stringify(user)); const config = require('../src/config'); + const enabledMethods = defaultSettings.authentication.filter(method => method.enabled); expect(config.getAuthorisedList()).to.be.eql(user.authorisedList); - expect(config.getAuthentication()).to.be.eql(defaultSettings.authentication[0]); + expect(config.getAuthMethods()).to.deep.equal(enabledMethods); expect(config.getDatabase()).to.be.eql(defaultSettings.sink[0]); expect(config.getTempPasswordConfig()).to.be.eql(defaultSettings.tempPassword); }); @@ -67,9 +69,13 @@ describe('user configuration', function () { fs.writeFileSync(tempUserFile, JSON.stringify(user)); const config = require('../src/config'); + const authMethods = config.getAuthMethods(); + const googleAuth = authMethods.find(method => method.type === 'google'); - expect(config.getAuthentication()).to.be.eql(user.authentication[0]); - expect(config.getAuthentication()).to.not.be.eql(defaultSettings.authentication[0]); + expect(googleAuth).to.not.be.undefined; + expect(googleAuth.enabled).to.be.true; + expect(config.getAuthMethods()).to.deep.include(user.authentication[0]); + expect(config.getAuthMethods()).to.not.be.eql(defaultSettings.authentication); expect(config.getDatabase()).to.be.eql(defaultSettings.sink[0]); expect(config.getTempPasswordConfig()).to.be.eql(defaultSettings.tempPassword); }); @@ -86,10 +92,11 @@ describe('user configuration', function () { fs.writeFileSync(tempUserFile, JSON.stringify(user)); const config = require('../src/config'); + const enabledMethods = defaultSettings.authentication.filter(method => method.enabled); expect(config.getDatabase()).to.be.eql(user.sink[0]); expect(config.getDatabase()).to.not.be.eql(defaultSettings.sink[0]); - expect(config.getAuthentication()).to.be.eql(defaultSettings.authentication[0]); + expect(config.getAuthMethods()).to.deep.equal(enabledMethods); expect(config.getTempPasswordConfig()).to.be.eql(defaultSettings.tempPassword); }); diff --git a/test/testLogin.test.js b/test/testLogin.test.js index 833184e0b..107bb7256 100644 --- a/test/testLogin.test.js +++ b/test/testLogin.test.js @@ -62,6 +62,22 @@ describe('auth', async () => { res.should.have.status(401); }); + + it('should fail to login with invalid username', async function () { + const res = await chai.request(app).post('/api/auth/login').send({ + username: 'invalid', + password: 'admin', + }); + res.should.have.status(401); + }); + + it('should fail to login with invalid password', async function () { + const res = await chai.request(app).post('/api/auth/login').send({ + username: 'admin', + password: 'invalid', + }); + res.should.have.status(401); + }); }); after(async function () { diff --git a/website/docs/quickstart/approve.mdx b/website/docs/quickstart/approve.mdx index 8f01e96a4..ebcd59ced 100644 --- a/website/docs/quickstart/approve.mdx +++ b/website/docs/quickstart/approve.mdx @@ -21,7 +21,7 @@ All pushes that flow through GitProxy require an approval (authorisation). Until Following on from [Push via GitProxy](/docs/quickstart/intercept#push-via-git-proxy), a unique & shareable link is generated: ``` -http://localhost:8080/admin/push/0000000000000000000000000000000000000000__79b4d8953cbc324bcc1eb53d6412ff89666c241f +http://localhost:8080/dashboard/push/0000000000000000000000000000000000000000__79b4d8953cbc324bcc1eb53d6412ff89666c241f ``` The `ID` for your push corresponds to the last part of the URL: @@ -174,7 +174,7 @@ Following on from [Push via GitProxy](/docs/quickstart/intercept#push-via-git-pr remote: GitProxy has received your push ✅ remote: remote: 🔗 Shareable Link -remote: http://localhost:8080/admin/push/000000__b12557 +remote: http://localhost:8080/dashboard/push/000000__b12557 ``` Insert the URL directly into your web browser. diff --git a/website/docs/quickstart/intercept.mdx b/website/docs/quickstart/intercept.mdx index d3b5534bc..1ac8e6016 100644 --- a/website/docs/quickstart/intercept.mdx +++ b/website/docs/quickstart/intercept.mdx @@ -93,7 +93,7 @@ remote: remote: GitProxy has received your push ✅ remote: remote: 🔗 Shareable Link -remote: http://localhost:8080/admin/push/000000__b12557 +remote: http://localhost:8080/dashboard/push/000000__b12557 remote: ```