Skip to content

Commit

Permalink
Tests for token expiry re-routing
Browse files Browse the repository at this point in the history
  • Loading branch information
philrenaud committed Nov 6, 2024
1 parent 37cd79f commit 372937d
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 3 deletions.
1 change: 1 addition & 0 deletions ui/app/components/forbidden-message.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { inject as service } from '@ember/service';
export default class ForbiddenMessage extends Component {
@service token;
@service store;
@service router;

get authMethods() {
return this.store.findAll('auth-method');
Expand Down
3 changes: 2 additions & 1 deletion ui/app/controllers/settings/tokens.js
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,8 @@ export default class Tokens extends Controller {
title: 'Successfully signed in',
message:
'You were redirected back to the page you were on before you were signed out.',
type: 'success',
color: 'success',
timeout: 10000,
});
}
}
Expand Down
1 change: 1 addition & 0 deletions ui/app/routes/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ export default class ApplicationRoute extends Route {
e.detail === 'ACL token not found'
)
) {
this.token.postExpiryPath = this.router.currentURL;
this.router.transitionTo('settings.tokens');
} else {
this.controllerFor('application').set('error', error);
Expand Down
4 changes: 2 additions & 2 deletions ui/app/templates/components/forbidden-message.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@
{{else}}
required
{{/if}}
<LinkTo @route="settings.tokens">permission</LinkTo> for this resource.<br /> Contact your administrator if this is an error.
<LinkTo data-test-permission-link @route="settings.tokens" {{on "click" (action (mut this.token.postExpiryPath) this.router.currentURL)}}>permission</LinkTo> for this resource.<br /> Contact your administrator if this is an error.
{{else}}
{{#if this.authMethods}}
Sign in with
{{#each this.authMethods as |authMethod|}}
<LinkTo @route="settings.tokens">{{authMethod.name}}</LinkTo>,
<LinkTo @route="settings.tokens">{{authMethod.name}}</LinkTo>,
{{/each}}
or
{{/if}}
Expand Down
112 changes: 112 additions & 0 deletions ui/tests/acceptance/token-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ let job;
let node;
let managementToken;
let clientToken;
let recentlyExpiredToken;
let soonExpiringToken;

module('Acceptance | tokens', function (hooks) {
setupApplicationTest(hooks);
Expand All @@ -53,6 +55,13 @@ module('Acceptance | tokens', function (hooks) {
job = server.create('job');
managementToken = server.create('token');
clientToken = server.create('token');
recentlyExpiredToken = server.create('token', {
expirationTime: moment().add(-5, 'm').toDate(),
});
soonExpiringToken = server.create('token', {
expirationTime: moment().add(1, 's').toDate(),
type: 'management',
});
});

test('it passes an accessibility audit', async function (assert) {
Expand Down Expand Up @@ -757,6 +766,109 @@ module('Acceptance | tokens', function (hooks) {
window.localStorage.nomadTokenSecret = null;
});

// Note: this differs from the 500-throwing errors above.
// In Nomad 1.5, errors for expired tokens moved from 500 "ACL token expired" to 403 "Permission Denied"
// In practice, the UI handles this differently: 403s can be either ACL-policy-denial or token-expired-denial related.
// As such, instead of an automatic redirect to the tokens page, like we did for a 500, we prompt the user with in-app
// error messages but otherwise keep them on their route, with actions to re-authenticate.
test('When a token expires with permission denial, the user is prompted to redirect to the token page (jobs page)', async function (assert) {
assert.expect(4);
window.localStorage.clear();

window.localStorage.nomadTokenSecret = recentlyExpiredToken.secretId; // simulate refreshing the page with an expired token
server.pretender.get('/v1/jobs/statuses', function () {
return [403, {}, 'Permission Denied'];
});

await visit('/jobs');

assert
.dom('[data-test-error]')
.exists('Error message is shown on the Jobs page');
await click('[data-test-permission-link]');
assert.equal(
currentURL(),
'/settings/tokens',
'Redirected to the tokens page'
);

server.pretender.get('/v1/jobs/statuses', function () {
return [200, {}, null];
});
await Tokens.visit();

await Tokens.secret(recentlyExpiredToken.secretId).submit();
assert.equal(currentURL(), '/jobs');

assert.dom('.flash-message.alert-success').exists();
});

// Evaluations page (and others) fall back to application.hbs handling of error messages
test('When a token expires with permission denial, the user is prompted to redirect to the token page (evaluations page)', async function (assert) {
window.localStorage.clear();
window.localStorage.nomadTokenSecret = recentlyExpiredToken.secretId; // simulate refreshing the page with an expired token
server.pretender.get('/v1/evaluations', function () {
return [403, {}, 'Permission Denied'];
});

await visit('/evaluations');

assert
.dom('[data-test-error]')
.exists('Error message is shown on the Evaluations page');
await click('[data-test-error-acl-link]');
assert.equal(
currentURL(),
'/settings/tokens',
'Redirected to the tokens page'
);

server.pretender.get('/v1/evaluations', function () {
return [200, {}, JSON.stringify([])];
});

await Tokens.secret(managementToken.secretId).submit();

assert.equal(currentURL(), '/evaluations');

assert.dom('.flash-message.alert-success').exists();
});

module('Token Expiry and redirect', function (hooks) {
hooks.beforeEach(function () {
window.localStorage.nomadTokenSecret = soonExpiringToken.secretId;
});

test('When a token expires while the user is on a page, the notification saves redirect route', async function (assert) {
// window.localStorage.nomadTokenSecret = soonExpiringToken.secretId;
await Jobs.visit();
assert.equal(currentURL(), '/jobs');

assert
.dom('.flash-message.alert-warning button')
.exists('A global alert exists and has a clickable button');

await click('.flash-message.alert-warning button');

assert.equal(
currentURL(),
'/settings/tokens',
'Redirected to tokens page on notification action'
);

assert
.dom('[data-test-token-expired]')
.exists('Notification is rendered');

await Tokens.secret(managementToken.secretId).submit();
assert.equal(
currentURL(),
'/jobs',
'Redirected to initial route on manager sign in'
);
});
});

function getHeader({ requestHeaders }, name) {
// Headers are case-insensitive, but object property look up is not
return (
Expand Down

0 comments on commit 372937d

Please sign in to comment.