0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-03-11 02:12:21 -05:00

Updated 2fa flow copy

closes https://linear.app/tryghost/issue/ENG-1654
closes https://linear.app/tryghost/issue/ENG-1656
closes https://linear.app/tryghost/issue/ENG-1657

- updated copy
- improved error handling when verification fails
- refactored some duplication of steps in Admin authentication tests
This commit is contained in:
Kevin Ansfield 2024-10-17 15:50:54 +01:00
parent 85d305ebf7
commit 6c4de6a937
3 changed files with 76 additions and 46 deletions

View file

@ -3,6 +3,7 @@ import Controller from '@ember/controller';
import DS from 'ember-data';
import {TrackedArray} from 'tracked-built-ins';
import {action} from '@ember/object';
import {isUnauthorizedError} from 'ember-ajax/errors';
import {inject as service} from '@ember/service';
import {task} from 'ember-concurrency';
import {tracked} from '@glimmer/tracking';
@ -77,11 +78,14 @@ export default class SigninVerifyController extends Controller {
yield this.session.authenticate('authenticator:cookie', {token: this.verifyData.token});
return true;
} catch (error) {
if (error && error.payload && error.payload.errors) {
if (isUnauthorizedError(error)) {
this.flowErrors = 'Your verification code is incorrect.';
} else if (error && error.payload && error.payload.errors) {
this.flowErrors = error.payload.errors[0].message;
} else {
this.flowErrors = 'There was a problem with the verification token.';
this.flowErrors = 'There was a problem verifying the code. Please try again.';
}
return false;
}
}

View file

@ -4,12 +4,12 @@
<form id="login" method="post" action="javascript:void(0)" class="gh-signin" novalidate="novalidate" {{on "submit" (perform this.verifyTokenTask)}}>
<header>
<div class="gh-site-icon" style={{site-icon-style}}></div>
<h1>New browser detected</h1>
<h1>Verify it's really you</h1>
</header>
<p>
For security, you need to verify your sign-in.
An email has been sent to you with a 6-digit code, please enter it below.
We don't recognize this device.
To keep your account safe, we've sent a 6-digit verification code to your email to make sure it's you.
</p>
<GhFormGroup @errors={{this.verifyData.errors}} @hasValidated={{this.verifyData.hasValidated}} @property="token">
<label for="token">Verification code</label>
@ -49,7 +49,7 @@
</GhFormGroup>
<GhTaskButton
@buttonText="Verify sign-in &rarr;"
@buttonText="Verify signin &rarr;"
@task={{this.verifyTokenTask}}
@showSuccess={{false}}
@class="login gh-btn gh-btn-login gh-btn-block gh-btn-icon"

View file

@ -36,6 +36,28 @@ describe('Acceptance: Authentication', function () {
describe('general page', function () {
let newLocation;
function setupVerificationRequired(server) {
server.post('/session', function () {
return new Response(403, {}, {
errors: [{
code: '2FA_TOKEN_REQUIRED'
}]
});
});
}
function setupVerificationSuccess(server) {
server.put('/session/verify', function () {
return new Response(201);
});
}
function setupVerificationFailed(server) {
server.put('/session/verify', function () {
return new Response(401, {}, null);
});
}
async function completeSignIn() {
await invalidateSession();
await visit('/signin');
@ -44,6 +66,15 @@ describe('Acceptance: Authentication', function () {
await click('[data-test-button="sign-in"]');
}
async function completeVerification() {
await fillIn('[data-test-input="token"]', 123456);
await click('[data-test-button="verify"]');
}
function testMainErrorMessage(expectedMessage) {
expect(find('[data-test-flow-notification]')).to.have.trimmed.text(expectedMessage);
}
beforeEach(function () {
originalReplaceLocation = windowProxy.replaceLocation;
windowProxy.replaceLocation = function (url) {
@ -104,7 +135,6 @@ describe('Acceptance: Authentication', function () {
it('doesn\'t show navigation menu on invalid url when not authenticated', async function () {
await invalidateSession();
await visit('/');
expect(currentURL(), 'current url').to.equal('/signin');
@ -127,59 +157,57 @@ describe('Acceptance: Authentication', function () {
});
it('has 2fa code happy path', async function () {
this.server.post('/session', function () {
return new Response(403, {}, {
errors: [{
code: '2FA_TOKEN_REQUIRED'
}]
});
});
this.server.put('/session/verify', function () {
return new Response(201);
});
setupVerificationRequired(this.server);
setupVerificationSuccess(this.server);
await completeSignIn();
expect(currentURL(), 'url after email+password submit').to.equal('/signin/verify');
await fillIn('[data-test-input="token"]', 123456);
await click('[data-test-button="verify"]');
await completeVerification();
expect(currentURL()).to.equal('/dashboard');
});
it('handles 2fa code verification errors', async function () {
this.server.post('/session', function () {
return new Response(403, {}, {
errors: [{
code: '2FA_TOKEN_REQUIRED'
}]
});
});
it('handles 2fa code verification failure', async function () {
setupVerificationRequired(this.server);
setupVerificationFailed(this.server);
await completeSignIn();
await completeVerification();
testMainErrorMessage('Your verification code is incorrect.');
});
it('handles known 2fa code verification error', async function () {
setupVerificationRequired(this.server);
this.server.put('/session/verify', function () {
return new Response(401, {}, {
return new Response(422, {}, {
errors: [{
message: 'Invalid or expired token'
message: 'Could not find session. Please try to signin again.'
}]
});
});
await completeSignIn();
await completeVerification();
await fillIn('[data-test-input="token"]', 123456);
await click('[data-test-button="verify"]');
expect(find('[data-test-flow-notification]')).to.have.trimmed.text('Invalid or expired token');
testMainErrorMessage('Could not find session. Please try to signin again.');
});
it('handles 2fa-required on a 2xx response', async function () {
it('handles unknown 2fa code verification error', async function () {
setupVerificationRequired(this.server);
this.server.put('/session/verify', function () {
return new Response(400);
});
await completeSignIn();
await completeVerification();
testMainErrorMessage('There was a problem verifying the code. Please try again.');
});
it('handles 2fa-required on a 2xx response from signin', async function () {
this.server.post('/session', function () {
return new Response(200, {}, {
errors: [{
code: '2FA_TOKEN_REQUIRED'
}]
errors: [{code: '2FA_TOKEN_REQUIRED'}]
});
});
@ -188,19 +216,17 @@ describe('Acceptance: Authentication', function () {
expect(currentURL(), 'url after email+password submit').to.equal('/signin/verify');
});
it('handles non-2fa 403 response', async function () {
it('handles non-2fa 403 response on signin', async function () {
this.server.post('/session', function () {
return new Response(403, {}, {
errors: [{
message: 'Insufficient permissions'
}]
errors: [{message: 'Insufficient permissions'}]
});
});
await completeSignIn();
expect(currentURL(), 'url after email+password submit').to.equal('/signin');
expect(find('[data-test-flow-notification]')).to.have.trimmed.text('Insufficient permissions');
testMainErrorMessage('Insufficient permissions');
});
});