From 3a811f1865ae23dfd489a8be0b4a26a6a6a61840 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Tue, 22 Sep 2020 10:55:32 -0400 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20token=20validation?= =?UTF-8?q?=20step=20to=20authenticator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/auth/addon/authenticators/password.js | 26 ++++++++++-- .../dummy/app/authenticators/password.js | 4 ++ .../unit/authenticators/password-test.js | 41 +++++++++++++++++++ 3 files changed, 67 insertions(+), 4 deletions(-) diff --git a/addons/auth/addon/authenticators/password.js b/addons/auth/addon/authenticators/password.js index 67c8ce422..eb383d6c8 100644 --- a/addons/auth/addon/authenticators/password.js +++ b/addons/auth/addon/authenticators/password.js @@ -13,8 +13,14 @@ import fetch from 'fetch'; * Upon session invalidation, deauthentication is attempted at the URL generated * by `buildDeauthEndpointURL`, but is not guaranteed. * + * When a session is restored, a call is made to the endpoint specified by + * `buildTokenValidationEndpointURL`. If this endpoint responds with 401 or + * 404, then the session is invalid and the restoration is rejected. All other + * responses will resolve the session restoration successfully. + * * This authenticator should not be used directly because it does not specify - * an `buildAuthEndpointURL` or `buildDeauthEndpointURL` of its own. + * an `buildAuthEndpointURL`, `buildDeauthEndpointURL`, or + * `buildTokenValidationEndpointURL` of its own. * To use, generate an application authenticator in your app * `authenticators/application.js` and extend this class. * @@ -31,13 +37,25 @@ export default class PasswordAuthenticator extends BaseAuthenticator { // =methods /** - * Restores the session if data is present. Otherwise rejects. + * Restores the session if data is present and token validation succeeds + * (any response other than 401 or 404 === success). Otherwise rejects. * @override * @param {object} data * @return {Promise} */ - restore(data) { - return data ? resolve(data) : reject(); + async restore(data) { + if (!data) return reject(); + const tokenID = data.id; + const tokenValidationURL = this.buildTokenValidationEndpointURL(tokenID); + const response = await fetch(tokenValidationURL, { method: 'get' }); + // 401 and 404 responses mean the token is invalid, whereas other types of + // error responses do not tell us about the validity of the token. + switch (response.status) { + case 401: + case 404: + return reject(); + } + return resolve(data); } /** diff --git a/addons/auth/tests/dummy/app/authenticators/password.js b/addons/auth/tests/dummy/app/authenticators/password.js index 1953a8f5d..06d437263 100644 --- a/addons/auth/tests/dummy/app/authenticators/password.js +++ b/addons/auth/tests/dummy/app/authenticators/password.js @@ -1,6 +1,10 @@ import PasswordAuthenticator from 'auth/authenticators/password'; export default class ApplicationPasswordAuthenticator extends PasswordAuthenticator { + buildTokenValidationEndpointURL(id) { + return `/validate/${id}`; + } + buildAuthEndpointURL() { return '/authenticate'; } diff --git a/addons/auth/tests/unit/authenticators/password-test.js b/addons/auth/tests/unit/authenticators/password-test.js index 873277469..040ce3758 100644 --- a/addons/auth/tests/unit/authenticators/password-test.js +++ b/addons/auth/tests/unit/authenticators/password-test.js @@ -96,4 +96,45 @@ module('Unit | Authenticator | password', function (hooks) { .then(() => assert.ok(true)) .catch(() => assert.notOk(true, 'uh oh, this should not happen')); }); + + test('it passes token validation on restoration if receiving 200', async function (assert) { + assert.expect(1); + const mockData = { id: 'token_123' }; + const authenticator = this.owner.lookup('authenticator:password'); + this.server.get(authenticator.buildTokenValidationEndpointURL(mockData.id), () => { + assert.ok(true, 'token validation was requested'); + return new Response(200); + }); + await authenticator.restore(mockData); + }); + + test('it fails token validation on restoration if receiving 401', async function (assert) { + assert.expect(2); + const mockData = { id: 'token_123' }; + const authenticator = this.owner.lookup('authenticator:password'); + this.server.get(authenticator.buildTokenValidationEndpointURL(mockData.id), () => { + assert.ok(true, 'token validation was requested'); + return new Response(401); + }); + try { + await authenticator.restore(mockData); + } catch (e) { + assert.ok(true, 'restoration failed'); + } + }); + + test('it fails token validation on restoration if receiving 404', async function (assert) { + assert.expect(2); + const mockData = { id: 'token_123' }; + const authenticator = this.owner.lookup('authenticator:password'); + this.server.get(authenticator.buildTokenValidationEndpointURL(mockData.id), () => { + assert.ok(true, 'token validation was requested'); + return new Response(404); + }); + try { + await authenticator.restore(mockData); + } catch (e) { + assert.ok(true, 'restoration failed'); + } + }); });