Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions cypress.config.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
const { defineConfig } = require('cypress')
const { defineConfig } = require('cypress');

module.exports = defineConfig({
e2e: {
baseUrl: process.env.CYPRESS_BASE_URL ||'http://localhost:3000',
baseUrl: process.env.CYPRESS_BASE_URL || 'http://localhost:3000',
chromeWebSecurity: false, // Required for OIDC testing
},
})
});
50 changes: 32 additions & 18 deletions cypress/e2e/login.cy.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,33 @@
describe("Display finos UI",()=>{

beforeEach(() =>{
cy.visit('/login')
})
it('shoud find git proxy logo',() =>{
cy.get('[data-test="git-proxy-logo"]').should('exist')
})
it('shoud find username',() =>{
cy.get('[data-test="username"]').should('exist')
})
describe('Login page', () => {
beforeEach(() => {
cy.visit('/login');
});

it('shoud find passsword',() =>{
cy.get('[data-test="password"]').should('exist')
})
it('shoud find login button',() =>{
cy.get('[data-test="login"]').should('exist')
})
})
it('should have git proxy logo', () => {
cy.get('[data-test="git-proxy-logo"]').should('exist');
});

it('should have username input', () => {
cy.get('[data-test="username"]').should('exist');
});

it('should have passsword input', () => {
cy.get('[data-test="password"]').should('exist');
});

it('should have login button', () => {
cy.get('[data-test="login"]').should('exist');
});

describe('OIDC login button', () => {
it('should exist', () => {
cy.get('[data-test="oidc-login"]').should('exist');
});

// Validates that OIDC is configured correctly
it('should redirect to /oidc', () => {
cy.get('[data-test="oidc-login"]').click();
cy.url().should('include', '/oidc');
});
});
});
81 changes: 79 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"passport": "^0.7.0",
"passport-activedirectory": "^1.0.4",
"passport-local": "^1.0.0",
"passport-openidconnect": "^0.1.2",
"perfect-scrollbar": "^1.5.5",
"prop-types": "15.8.1",
"react": "^16.13.1",
Expand Down
1 change: 1 addition & 0 deletions src/db/file/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ module.exports.canUserCancelPush = pushes.canUserCancelPush;
module.exports.canUserApproveRejectPush = pushes.canUserApproveRejectPush;

module.exports.findUser = users.findUser;
module.exports.findUserByOIDC = users.findUserByOIDC;
module.exports.getUsers = users.getUsers;
module.exports.createUser = users.createUser;
module.exports.deleteUser = users.deleteUser;
Expand Down
16 changes: 16 additions & 0 deletions src/db/file/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,22 @@ exports.findUser = function (username) {
});
};

exports.findUserByOIDC = function (oidcId) {
return new Promise((resolve, reject) => {
db.findOne({ oidcId: oidcId }, (err, doc) => {
if (err) {
reject(err);
} else {
if (!doc) {
resolve(null);
} else {
resolve(doc);
}
}
});
});
};

exports.createUser = function (data) {
return new Promise((resolve, reject) => {
db.insert(data, (err) => {
Expand Down
16 changes: 13 additions & 3 deletions src/db/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,30 @@ if (config.getDatabase().type === 'mongo') {
sink = require('../db/file');
}

module.exports.createUser = async (username, password, email, gitAccount, admin = false) => {
module.exports.createUser = async (
username,
password,
email,
gitAccount,
admin = false,
oidcId = null,
) => {
console.log(
`creating user
user=${username},
gitAccount=${gitAccount}
email=${email},
admin=${admin}`,
admin=${admin}
oidcId=${oidcId}`,
);

const data = {
username: username,
password: await bcrypt.hash(password, 10),
password: oidcId ? null : await bcrypt.hash(password, 10),
gitAccount: gitAccount,
email: email,
admin: admin,
oidcId: oidcId,
};

if (username === undefined || username === null || username === '') {
Expand Down Expand Up @@ -56,6 +65,7 @@ module.exports.getPushes = sink.getPushes;
module.exports.writeAudit = sink.writeAudit;
module.exports.getPush = sink.getPush;
module.exports.findUser = sink.findUser;
module.exports.findUserByOIDC = sink.findUserByOIDC;
module.exports.getUsers = sink.getUsers;
module.exports.deleteUser = sink.deleteUser;
module.exports.updateUser = sink.updateUser;
Expand Down
14 changes: 10 additions & 4 deletions src/service/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,16 @@ const createApp = async () => {
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use('/', routes);
app.use('/', express.static(absBuildPath));
app.get('/*', (req, res) => {
res.sendFile(path.join(`${absBuildPath}/index.html`));
});

// In production, serves the static files from the build directory
if (process.env.NODE_ENV === 'production') {
app.use('/', express.static(absBuildPath));
app.get('/*', (req, res) => {
res.sendFile(path.join(`${absBuildPath}/index.html`));
});
} else {
console.log('Not serving static files');
}

return app;
};
Expand Down
4 changes: 4 additions & 0 deletions src/service/passport/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const local = require('./local');
const activeDirectory = require('./activeDirectory');
const oidc = require('./oidc');
const config = require('../../config');
const authenticationConfig = config.getAuthentication();
let _passport;
Expand All @@ -14,6 +15,9 @@ const configure = async () => {
case 'local':
_passport = await local.configure();
break;
case 'openidconnect':
_passport = await oidc.configure();
break;
default:
throw Error(`uknown authentication type ${type}`);
}
Expand Down
91 changes: 91 additions & 0 deletions src/service/passport/oidc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
const configure = async () => {
const passport = require('passport');
const { Strategy: OIDCStrategy } = require('passport-openidconnect');
const db = require('../../db');

const config = require('../../config').getAuthentication();
const oidcConfig = config.oidcConfig;

passport.use(
new OIDCStrategy(oidcConfig, async function verify(issuer, profile, cb) {
try {
const user = await db.findUserByOIDC(profile.id);

if (!user) {
const email = safelyExtractEmail(profile);
if (!email) {
return cb(new Error('No email found in OIDC profile'));
}

const username = getUsername(email);
const newUser = {
username: username,
email: email,
oidcId: profile.id,
};

await db.createUser(
newUser.username,
null,
newUser.email,
'Edit me',
false,
newUser.oidcId,
);

return cb(null, newUser);
}
return cb(null, user);
} catch (err) {
return cb(err);
}
}),
);

passport.serializeUser((user, cb) => {
cb(null, user.oidcId || user.username);
});

passport.deserializeUser(async (id, cb) => {
try {
const user = (await db.findUserByOIDC(id)) || (await db.findUser(id));
cb(null, user);
} catch (err) {
cb(err);
}
});

passport.type = 'openidconnect';
return passport;
};

module.exports.configure = configure;

/**
* Extracts email from OIDC profile.
* This function is necessary because OIDC providers have different ways of storing emails.
* @param {object} profile the profile object from OIDC provider
* @return {string | null} the email address
*/
const safelyExtractEmail = (profile) => {
if (profile.emails && profile.emails.length > 0) {
return profile.emails[0].value;
}

if (profile.email) {
return profile.email;
}
return null;
};

/**
* Generates a username from email address.
* This helps differentiate users within the specific OIDC provider.
* Note: This is incompatible with multiple providers. Ideally, users are identified by
* OIDC ID (requires refactoring the database).
* @param {string} email the email address
* @return {string} the username
*/
const getUsername = (email) => {
return email ? email.split('@')[0] : '';
};
Loading
Loading