Files
threshold-ui/addons/auth/addon/authenticators/password.js
T

133 lines
4.6 KiB
JavaScript

import BaseAuthenticator from 'ember-simple-auth/authenticators/base';
import { resolve, reject } from 'rsvp';
import fetch from 'fetch';
/**
*
* This authenticator is intended for use with Ember Simple Auth.
*
* Simple id/password base authenticator sends credentials to an endpoint
* specified by the `buildAuthEndpointURL`. If the HTTP response code is in the
* success range, authentication resolves. Otherwise it rejects.
*
* 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`, `buildDeauthEndpointURL`, or
* `buildTokenValidationEndpointURL` of its own.
* To use, generate an application authenticator in your app
* `authenticators/application.js` and extend this class.
*
* @example
*
* import PasswordAuthenticator from 'auth/authenticators/password';
* export default class ApplicationAuthenticator extends PasswordAuthenticator {
* authEndpoint = '/api/authenticate';
* deauthEndpoint = '/api/deauthenticate';
* }
*
*/
export default class PasswordAuthenticator extends BaseAuthenticator {
// =methods
/**
* 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}
*/
async restore(data) {
if (!data) return reject();
const tokenID = data.id;
const token = data.token;
const tokenValidationURL = this.buildTokenValidationEndpointURL(tokenID);
const response = await fetch(tokenValidationURL, {
method: 'get',
headers: {
// TODO: this is temporary to get the call working, but generating
// the auth header originated in the application authenticator.
// It should probably be factored out into a method on the base.
'Authorization': `Bearer ${token}`
}
});
// 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(this.normalizeData(data));
}
/**
* Posts credentials to the URL specified in `authEndpoint` and resolves
* if a success HTTP status code is received, otherwise rejects.
*
* If requesting cookies, be sure to use the cookie session store. Otherwise
* use the local storage session store.
*
* @override
* @param {object} creds
* @param {string} creds.identification is sent to server as `login_name`
* @param {string} creds.password
* @param {boolean} requestCookies request cookie tokens (default `true`)
* @return {Promise}
*/
async authenticate(
{ identification: login_name, password },
requestCookies = true,
options
) {
const body = JSON.stringify({
token_type: requestCookies ? 'cookie' : null,
credentials: { login_name, password },
});
const authEndpointURL = this.buildAuthEndpointURL(options);
const response = await fetch(authEndpointURL, { method: 'post', body });
const json = await response.json();
return response.status < 400
? resolve(this.normalizeData(json, login_name))
: reject();
}
/**
* Normalizes the auth data. Adds convenience booleans depending on the
* scope within which the session is authenticated: isGlobal, isOrg.
* If a `username` is provided, appends this to the data.
* @param {object} data
* @param {string} username
* @return {object}
*/
normalizeData(data, username) {
// Add booleans indicated the scope type
data.isGlobal = (data?.scope?.type === 'global');
data.isOrg = (data?.scope?.type === 'org');
if (username) data.username = username;
return data;
}
/**
* Posts to the `deauthEntpoint` on a best-effort basis and then returns.
* Deauthentication with the server is not guaranteed and request failures
* are ignored.
*
* @override
* @return {Promise}
*/
invalidate(options) {
const deauthEndpointURL = this.buildDeauthEndpointURL(options);
fetch(deauthEndpointURL, { method: 'post' }).catch(() => {
/* no op */
});
return super.invalidate(...arguments);
}
}