From 3ed1f6a8ca698571cf7d9b3963272068e54cd10a Mon Sep 17 00:00:00 2001 From: Sam Lord Date: Wed, 16 Oct 2024 15:12:10 +0100 Subject: [PATCH] Added tests for sessions API with 2fa enabled --- .../admin/__snapshots__/session.test.js.snap | 68 ++++++++++++++ ghost/core/test/e2e-api/admin/session.test.js | 90 ++++++++++++++++++- ghost/session-service/lib/session-service.js | 2 +- 3 files changed, 158 insertions(+), 2 deletions(-) diff --git a/ghost/core/test/e2e-api/admin/__snapshots__/session.test.js.snap b/ghost/core/test/e2e-api/admin/__snapshots__/session.test.js.snap index 1bbe95e736..ac808e9e9e 100644 --- a/ghost/core/test/e2e-api/admin/__snapshots__/session.test.js.snap +++ b/ghost/core/test/e2e-api/admin/__snapshots__/session.test.js.snap @@ -1,5 +1,73 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`Sessions API Staff 2FA can verify a session with 2FA code 1: [body] 1`] = ` +Object { + "errors": Array [ + Object { + "code": "2FA_TOKEN_REQUIRED", + "context": null, + "details": null, + "ghostErrorCode": null, + "help": null, + "id": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + "message": "User must verify session to login.", + "property": null, + "type": "Needs2FAError", + }, + ], +} +`; + +exports[`Sessions API Staff 2FA can verify a session with 2FA code 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "http://127.0.0.1:2369", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "236", + "content-type": "application/json; charset=utf-8", + "content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/, + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "set-cookie": Array [ + "ghost-admin-api-session=s%3AMgHbIHAiCElZ5ifcN6r2oqSZwQNZV9Px.AtWhxBoc2aXXmX8GdWikxDyflgYWJNdHqr1twJRN3oU; Path=/ghost; Expires=Thu, 17 Apr 2025 02:10:39 GMT; HttpOnly; SameSite=Lax", + ], + "vary": "Accept-Version, Origin, Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Sessions API Staff 2FA sends verification email if labs flag enabled 1: [body] 1`] = ` +Object { + "errors": Array [ + Object { + "code": "2FA_TOKEN_REQUIRED", + "context": null, + "details": null, + "ghostErrorCode": null, + "help": null, + "id": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + "message": "User must verify session to login.", + "property": null, + "type": "Needs2FAError", + }, + ], +} +`; + +exports[`Sessions API Staff 2FA sends verification email if labs flag enabled 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "http://127.0.0.1:2369", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "236", + "content-type": "application/json; charset=utf-8", + "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, Accept-Encoding", + "x-powered-by": "Express", +} +`; + exports[`Sessions API can create session (log in) 1: [headers] 1`] = ` Object { "access-control-allow-origin": "http://127.0.0.1:2369", diff --git a/ghost/core/test/e2e-api/admin/session.test.js b/ghost/core/test/e2e-api/admin/session.test.js index 0bed0e2957..e2bc1a4baf 100644 --- a/ghost/core/test/e2e-api/admin/session.test.js +++ b/ghost/core/test/e2e-api/admin/session.test.js @@ -1,5 +1,6 @@ const {agentProvider, fixtureManager, matchers} = require('../../utils/e2e-framework'); -const {anyContentVersion, anyEtag, anyErrorId, stringMatching, anyISODateTime} = matchers; +const {mockLabsEnabled, mockLabsDisabled, mockMail, assert, restore} = require('../../utils/e2e-framework-mock-manager'); +const {anyContentVersion, anyEtag, anyErrorId, stringMatching, anyISODateTime, anyUuid} = matchers; describe('Sessions API', function () { let agent; @@ -73,4 +74,91 @@ describe('Sessions API', function () { etag: anyEtag }); }); + + describe('Staff 2FA', function () { + let mail; + + beforeEach(async function () { + mockLabsEnabled('staff2fa'); + mail = mockMail(); + + // Setup the agent & fixtures again, to ensure no cookies are set + agent = await agentProvider.getAdminAPIAgent(); + await fixtureManager.init(); + }); + + afterEach(async function () { + mockLabsDisabled('staff2fa'); + restore(); + }); + + it('sends verification email if labs flag enabled', async function () { + const owner = await fixtureManager.get('users', 0); + await agent + .post('session/') + .body({ + grant_type: 'password', + username: owner.email, + password: owner.password + }) + .expectStatus(403) + .matchBodySnapshot({ + errors: [{ + code: '2FA_TOKEN_REQUIRED', + id: anyUuid, + message: 'User must verify session to login.', + type: 'Needs2FAError' + }] + }) + .matchHeaderSnapshot({ + 'content-version': anyContentVersion, + etag: anyEtag, + 'set-cookie': [ + stringMatching(/^ghost-admin-api-session=/) + ] + }); + + mail.assertSentEmailCount(1); + }); + + it('can verify a session with 2FA code', async function () { + const owner = await fixtureManager.get('users', 0); + await agent + .post('session/') + .body({ + grant_type: 'password', + username: owner.email, + password: owner.password + }) + .expectStatus(403) + .matchBodySnapshot({ + errors: [{ + code: '2FA_TOKEN_REQUIRED', + id: anyUuid, + message: 'User must verify session to login.', + type: 'Needs2FAError' + }] + }) + .matchHeaderSnapshot({ + 'content-version': anyContentVersion, + etag: anyEtag, + 'set-cookie': [ + stringMatching(/^ghost-admin-api-session=/) + ] + }); + + const email = assert.sentEmail({ + subject: /[0-9]{6} is your Ghost sign in verification code/ + }); + + const token = email.subject.match(/[0-9]{6}/)[0]; + await agent + .post('session/verify') + .body({ + token + }) + .expectStatus(200) + .expectEmptyBody(); + }); + }); }); diff --git a/ghost/session-service/lib/session-service.js b/ghost/session-service/lib/session-service.js index b4f0d42b3b..8dd02e8337 100644 --- a/ghost/session-service/lib/session-service.js +++ b/ghost/session-service/lib/session-service.js @@ -253,7 +253,7 @@ module.exports = function createSessionService({ await mailer.send({ to: recipient, - subject: `${token} is your Ghost signin verification code`, + subject: `${token} is your Ghost sign in verification code`, html: email }); }