0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-01-20 22:42:53 -05:00

Prevent regression / e2e tests from trying to use 2fa

This commit is contained in:
Sam Lord 2024-10-09 15:13:25 +01:00 committed by Kevin Ansfield
parent 1a05652b50
commit f772008c69
6 changed files with 62 additions and 30 deletions

View file

@ -4,6 +4,7 @@ const sessionFromToken = require('@tryghost/mw-session-from-token');
const createSessionMiddleware = require('./middleware');
const settingsCache = require('../../../../shared/settings-cache');
const {GhostMailer} = require('../../mail');
const {t} = require('../../i18n');
const expressSession = require('./express-session');
@ -38,10 +39,12 @@ const sessionService = createSessionService({
findUserById({id}) {
return models.User.findOne({id, status: 'active'});
},
getSecret(key) {
getSettingsCache(key) {
return settingsCache.get(key);
},
mailer
mailer,
urlUtils,
t
});
module.exports = createSessionMiddleware({sessionService});

View file

@ -30,7 +30,6 @@ Object {
"members": true,
"newEmailAddresses": true,
"outboundLinkTagging": true,
"staff2fa": true,
"stripeAutomaticTax": true,
"themeErrorsNotification": true,
"urlCache": true,

View file

@ -22,6 +22,9 @@ Object {
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"set-cookie": Array [
StringMatching /\\^ghost-admin-api-session=/,
],
"vary": "Accept-Version, Origin",
"x-powered-by": "Express",
}

View file

@ -713,7 +713,10 @@ const fixtures = {
},
async enableAllLabsFeatures() {
const labsValue = Object.fromEntries(labsService.WRITABLE_KEYS_ALLOWLIST.map(key => [key, true]));
const labsValue = Object.fromEntries(labsService.WRITABLE_KEYS_ALLOWLIST
// TODO: should test with 2fa enabled
.filter(key => key !== 'staff2fa')
.map(key => [key, true]));
const labsSetting = DataGenerator.forKnex.createSetting({
key: 'labs',
group: 'labs',

View file

@ -1,8 +1,9 @@
const {
BadRequestError
} = require('@tryghost/errors');
const {totp} = require('otplib');
const emailTemplate = require('../lib/emails/signin');
const {totp} = require('otplib');
totp.options = {
digits: 6,
step: 60,
@ -12,6 +13,7 @@ totp.options = {
/**
* @typedef {object} User
* @prop {string} id
* @prop {(attr: string) => string} get
*/
/**
@ -45,9 +47,10 @@ totp.options = {
* @param {(req: Req, res: Res) => Promise<Session>} deps.getSession
* @param {(data: {id: string}) => Promise<User>} deps.findUserById
* @param {(req: Req) => string} deps.getOriginOfRequest
* @param {(key: string) => string} deps.getSecret
* @param {(key: string) => string} deps.getSettingsCache
* @param {import('../../core/core/server/services/mail').GhostMailer} deps.mailer
*
* @param {import('../../core/core/server/services/i18n').t} deps.t
* @param {import('../../core/core/shared/url-utils')} deps.urlUtils
* @returns {SessionService}
*/
@ -55,8 +58,10 @@ module.exports = function createSessionService({
getSession,
findUserById,
getOriginOfRequest,
getSecret,
mailer
getSettingsCache,
mailer,
urlUtils,
t
}) {
/**
* cookieCsrfProtection
@ -112,8 +117,8 @@ module.exports = function createSessionService({
* @returns {Promise<string>}
*/
async function generateAuthCodeForUser(req, res) {
const session = await getSession(req, res); // Todo: Do we need to handle "No session found"?
const secret = getSecret('admin_session_secret') + session.user_id;
const session = await getSession(req, res);
const secret = getSettingsCache('admin_session_secret') + session.user_id;
const token = totp.generate(secret);
return token;
}
@ -126,8 +131,8 @@ module.exports = function createSessionService({
* @returns {Promise<boolean>}
*/
async function verifyAuthCodeForUser(req, res) {
const session = await getSession(req, res); // Todo: Do we need to handle "No session found"?
const secret = getSecret('admin_session_secret') + session.user_id;
const session = await getSession(req, res);
const secret = getSettingsCache('admin_session_secret') + session.user_id;
const isValid = totp.check(req.body.token, secret);
return isValid;
}
@ -140,19 +145,30 @@ module.exports = function createSessionService({
* @returns {Promise<void>}
*/
async function sendAuthCodeToUser(req, res) {
const session = await getSession(req, res); // eslint-disable-line
const token = await generateAuthCodeForUser(req, res);
const user = await getUserForSession(req, res);
if(!user) {
throw new BadRequestError({
message: 'Could not fetch user from the session.'
});
}
const recipient = user.get('email');
const siteTitle = getSettingsCache('title');
const siteUrl = urlUtils.urlFor('home', true);
const domain = urlUtils.urlFor('home', true).match(new RegExp('^https?://([^/:?#]+)(?:[/:?#]|$)', 'i'));
const siteDomain = (domain && domain[1]);
const email = emailTemplate({
t,
siteTitle: siteTitle,
email: recipient,
siteDomain: siteDomain,
siteUrl: siteUrl,
token
});
// TODO: Find email address for user associated with user requesting token
const recipient = 'TODO';
// TODO: Generate email
const email = `<html><body><p>Here is your token matey: ${token}</p></body></html>`;
// TODO: Send email
await mailer.send({
to: recipient,
subject: 'tokens4u',
subject: `Verification code: ${token}`,
html: email
});

View file

@ -199,13 +199,13 @@ describe('SessionService', function () {
};
const findUserById = sinon.spy(async ({id}) => ({id}));
const getOriginOfRequest = sinon.stub().returns('origin');
const getSecret = sinon.stub().returns('secret-key');
const getSettingsCache = sinon.stub().returns('secret-key');
const sessionService = SessionService({
getSession,
findUserById,
getOriginOfRequest,
getSecret
getSettingsCache
});
const req = Object.create(express.request, {
@ -227,8 +227,12 @@ describe('SessionService', function () {
const authCode = await sessionService.generateAuthCodeForUser(req, res);
should.exist(authCode);
req.body = {
token: authCode
};
// Verify the auth code
const isValid = await sessionService.verifyAuthCodeForUser(req, res, authCode);
const isValid = await sessionService.verifyAuthCodeForUser(req, res);
should.equal(isValid, true);
});
@ -245,13 +249,13 @@ describe('SessionService', function () {
};
const findUserById = sinon.spy(async ({id}) => ({id}));
const getOriginOfRequest = sinon.stub().returns('origin');
const getSecret = sinon.stub().returns('secret-key');
const getSettingsCache = sinon.stub().returns('secret-key');
const sessionService = SessionService({
getSession,
findUserById,
getOriginOfRequest,
getSecret
getSettingsCache
});
const req = Object.create(express.request, {
@ -273,8 +277,12 @@ describe('SessionService', function () {
const authCode = await sessionService.generateAuthCodeForUser(req, res);
should.exist(authCode);
req.body = {
token: 'wrong-code'
};
// Verify an incorrect auth code
const isValid = await sessionService.verifyAuthCodeForUser(req, res, 'wrong-code');
const isValid = await sessionService.verifyAuthCodeForUser(req, res);
should.equal(isValid, false);
});
@ -313,7 +321,7 @@ describe('SessionService', function () {
getSession,
findUserById,
getOriginOfRequest,
getSecret: getSecretFirst
getSettingsCache: getSecretFirst
});
const authCodeFirst = await sessionServiceFirst.generateAuthCodeForUser(req, res);
@ -324,7 +332,7 @@ describe('SessionService', function () {
getSession,
findUserById,
getOriginOfRequest,
getSecret: getSecretSecond
getSettingsCache: getSecretSecond
});
const authCodeSecond = await sessionServiceSecond.generateAuthCodeForUser(req, res);