feat: 🎸 add token validation step to authenticator
This commit is contained in:
3 files changed
+67
-4
No files matched your search
@@ -13,8 +13,14 @@ import fetch from 'fetch';
|
|||||||
* Upon session invalidation, deauthentication is attempted at the URL generated
|
* Upon session invalidation, deauthentication is attempted at the URL generated
|
||||||
* by `buildDeauthEndpointURL`, but is not guaranteed.
|
* 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
|
* 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
|
* To use, generate an application authenticator in your app
|
||||||
* `authenticators/application.js` and extend this class.
|
* `authenticators/application.js` and extend this class.
|
||||||
*
|
*
|
||||||
@@ -31,13 +37,25 @@ export default class PasswordAuthenticator extends BaseAuthenticator {
|
|||||||
// =methods
|
// =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
|
* @override
|
||||||
* @param {object} data
|
* @param {object} data
|
||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
*/
|
*/
|
||||||
restore(data) {
|
async restore(data) {
|
||||||
return data ? resolve(data) : reject();
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import PasswordAuthenticator from 'auth/authenticators/password';
|
import PasswordAuthenticator from 'auth/authenticators/password';
|
||||||
|
|
||||||
export default class ApplicationPasswordAuthenticator extends PasswordAuthenticator {
|
export default class ApplicationPasswordAuthenticator extends PasswordAuthenticator {
|
||||||
|
buildTokenValidationEndpointURL(id) {
|
||||||
|
return `/validate/${id}`;
|
||||||
|
}
|
||||||
|
|
||||||
buildAuthEndpointURL() {
|
buildAuthEndpointURL() {
|
||||||
return '/authenticate';
|
return '/authenticate';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,4 +96,45 @@ module('Unit | Authenticator | password', function (hooks) {
|
|||||||
.then(() => assert.ok(true))
|
.then(() => assert.ok(true))
|
||||||
.catch(() => assert.notOk(true, 'uh oh, this should not happen'));
|
.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');
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
Reference in new issue
Block a user