From 7a18e829c5fbbf24610baeaa4ef02079356afc30 Mon Sep 17 00:00:00 2001 From: Michael Barrett Date: Wed, 9 Oct 2024 13:32:16 +0100 Subject: [PATCH] Added endpoints for supporting 2FA no refs - Added `POST /session/verify` to send the user a verification code - Added `PUT /session/verify` to verify the user's verification code --- .../core/core/server/api/endpoints/session.js | 7 +++- .../server/services/auth/session/index.js | 6 +++- .../services/auth/session/middleware.js | 18 +++++++++- .../server/web/api/endpoints/admin/routes.js | 4 +-- ghost/session-service/lib/session-service.js | 34 +++++++++++++++---- 5 files changed, 57 insertions(+), 12 deletions(-) diff --git a/ghost/core/core/server/api/endpoints/session.js b/ghost/core/core/server/api/endpoints/session.js index fd33bdbc91..0cc60998c3 100644 --- a/ghost/core/core/server/api/endpoints/session.js +++ b/ghost/core/core/server/api/endpoints/session.js @@ -65,10 +65,15 @@ const controller = { auth.session.logout(req, res, next); }); }, - verify() { + sendVerification() { return Promise.resolve(function sendAuthCodeMw(req, res, next) { auth.session.sendAuthCode(req, res, next); }); + }, + verify() { + return Promise.resolve(function verifyAuthCodeMw(req, res, next) { + auth.session.verifyAuthCode(req, res, next); + }); } }; diff --git a/ghost/core/core/server/services/auth/session/index.js b/ghost/core/core/server/services/auth/session/index.js index 5825e11f41..a7427b3108 100644 --- a/ghost/core/core/server/services/auth/session/index.js +++ b/ghost/core/core/server/services/auth/session/index.js @@ -3,6 +3,7 @@ const createSessionService = require('@tryghost/session-service'); const sessionFromToken = require('@tryghost/mw-session-from-token'); const createSessionMiddleware = require('./middleware'); const settingsCache = require('../../../../shared/settings-cache'); +const {GhostMailer} = require('../../mail'); const expressSession = require('./express-session'); @@ -29,6 +30,8 @@ function getOriginOfRequest(req) { return null; } +const mailer = new GhostMailer(); + const sessionService = createSessionService({ getOriginOfRequest, getSession: expressSession.getSession, @@ -37,7 +40,8 @@ const sessionService = createSessionService({ }, getSecret(key) { return settingsCache.get(key); - } + }, + mailer }); module.exports = createSessionMiddleware({sessionService}); diff --git a/ghost/core/core/server/services/auth/session/middleware.js b/ghost/core/core/server/services/auth/session/middleware.js index 7c1250eb2a..e4064fe639 100644 --- a/ghost/core/core/server/services/auth/session/middleware.js +++ b/ghost/core/core/server/services/auth/session/middleware.js @@ -34,17 +34,33 @@ function SessionMiddleware({sessionService}) { async function sendAuthCode(req, res, next) { try { await sessionService.sendAuthCodeToUser(req, res); + res.sendStatus(201); } catch (err) { next(err); } } + async function verifyAuthCode(req, res, next) { + try { + const verified = await sessionService.verifyAuthCodeForUser(req, res); + + if (verified) { + res.sendStatus(200); + } else { + res.sendStatus(401); + } + } catch (err) { + next(err); + } + } + return { createSession: createSession, logout: logout, authenticate: authenticate, - sendAuthCode: sendAuthCode + sendAuthCode: sendAuthCode, + verifyAuthCode: verifyAuthCode }; } diff --git a/ghost/core/core/server/web/api/endpoints/admin/routes.js b/ghost/core/core/server/web/api/endpoints/admin/routes.js index dfe35051c8..2ba6834df7 100644 --- a/ghost/core/core/server/web/api/endpoints/admin/routes.js +++ b/ghost/core/core/server/web/api/endpoints/admin/routes.js @@ -243,8 +243,8 @@ module.exports = function apiRoutes() { http(api.session.add) ); router.del('/session', mw.authAdminApi, http(api.session.delete)); - // resending verification code for 2FA - router.post('/session/verify', mw.authAdminApi, http(api.session.verify)); + router.post('/session/verify', http(api.session.sendVerification)); + router.put('/session/verify', http(api.session.verify)); // ## Identity router.get('/identities', mw.authAdminApi, http(api.identities.read)); diff --git a/ghost/session-service/lib/session-service.js b/ghost/session-service/lib/session-service.js index e49221c45a..864ac4bf60 100644 --- a/ghost/session-service/lib/session-service.js +++ b/ghost/session-service/lib/session-service.js @@ -30,8 +30,7 @@ const {totp} = require('otplib'); * @prop {(req: Req, res: Res, user: User) => Promise} createSessionForUser * @prop {(req: Req, res: Res) => Promise} verifySession * @prop {(req: Req, res: Res) => Promise} sendAuthCodeToUser - * @prop {(req: Req, res: Res) => string} generateAuthCodeForUser - * @prop {(req: Req, res: Res) => Promise} verifyAuthCodeForUser + * @prop {(req: Req, res: Res) => Promise} verifyAuthCodeForUser */ /** @@ -40,11 +39,18 @@ const {totp} = require('otplib'); * @param {(data: {id: string}) => Promise} deps.findUserById * @param {(req: Req) => string} deps.getOriginOfRequest * @param {(key: string) => string} deps.getSecret + * @param {import('../../core/core/server/services/mail').GhostMailer} deps.mailer * * @returns {SessionService} */ -module.exports = function createSessionService({getSession, findUserById, getOriginOfRequest, getSecret}) { +module.exports = function createSessionService({ + getSession, + findUserById, + getOriginOfRequest, + getSecret, + mailer +}) { /** * cookieCsrfProtection * @@ -117,10 +123,10 @@ module.exports = function createSessionService({getSession, findUserById, getOri * @param {Res} res * @returns {Promise} */ - async function verifyAuthCodeForUser(req, res, token) { + 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 isValid = totp.check(token, secret); + const isValid = totp.check(req.body.token, secret); return isValid; } @@ -133,8 +139,22 @@ module.exports = function createSessionService({getSession, findUserById, getOri */ async function sendAuthCodeToUser(req, res) { const session = await getSession(req, res); // eslint-disable-line - generateAuthCodeForUser(); - // send auth code to user + const token = await generateAuthCodeForUser(req, res); + + // TODO: Find email address for user associated with user requesting token + const recipient = 'TODO'; + + // TODO: Generate email + const email = `

Here is your token matey: ${token}

`; + + // TODO: Send email + await mailer.send({ + to: recipient, + subject: 'tokens4u', + html: email + }); + + return Promise.resolve(); } /**