@feathersjs/authentication-client
Version:
The authentication plugin for feathers-client
184 lines • 6.89 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AuthenticationClient = void 0;
const errors_1 = require("@feathersjs/errors");
const storage_1 = require("./storage");
class OauthError extends errors_1.FeathersError {
constructor(message, data) {
super(message, 'OauthError', 401, 'oauth-error', data);
}
}
const getMatch = (location, key) => {
const regex = new RegExp(`(?:\&?)${key}=([^&]*)`);
const match = location.hash ? location.hash.match(regex) : null;
if (match !== null) {
const [, value] = match;
return [value, regex];
}
return [null, regex];
};
class AuthenticationClient {
constructor(app, options) {
const socket = app.io;
const storage = new storage_1.StorageWrapper(app.get('storage') || options.storage);
this.app = app;
this.options = options;
this.authenticated = false;
this.app.set('storage', storage);
if (socket) {
this.handleSocket(socket);
}
}
get service() {
return this.app.service(this.options.path);
}
get storage() {
return this.app.get('storage');
}
handleSocket(socket) {
// When the socket disconnects and we are still authenticated, try to reauthenticate right away
// the websocket connection will handle timeouts and retries
socket.on('disconnect', () => {
if (this.authenticated) {
this.reAuthenticate(true);
}
});
}
/**
* Parse the access token or authentication error from the window location hash. Will remove it from the hash
* if found.
*
* @param location The window location
* @returns The access token if available, will throw an error if found, otherwise null
*/
getFromLocation(location) {
const [accessToken, tokenRegex] = getMatch(location, this.options.locationKey);
if (accessToken !== null) {
location.hash = location.hash.replace(tokenRegex, '');
return Promise.resolve(accessToken);
}
const [message, errorRegex] = getMatch(location, this.options.locationErrorKey);
if (message !== null) {
location.hash = location.hash.replace(errorRegex, '');
return Promise.reject(new OauthError(decodeURIComponent(message)));
}
return Promise.resolve(null);
}
/**
* Set the access token in storage.
*
* @param accessToken The access token to set
* @returns
*/
setAccessToken(accessToken) {
return this.storage.setItem(this.options.storageKey, accessToken);
}
/**
* Returns the access token from storage or the window location hash.
*
* @returns The access token from storage or location hash
*/
getAccessToken() {
return this.storage.getItem(this.options.storageKey).then((accessToken) => {
if (!accessToken && typeof window !== 'undefined' && window.location) {
return this.getFromLocation(window.location);
}
return accessToken || null;
});
}
/**
* Remove the access token from storage
* @returns The removed access token
*/
removeAccessToken() {
return this.storage.removeItem(this.options.storageKey);
}
/**
* Reset the internal authentication state. Usually not necessary to call directly.
*
* @returns null
*/
reset() {
this.app.set('authentication', null);
this.authenticated = false;
return Promise.resolve(null);
}
handleError(error, type) {
// For NotAuthenticated, PaymentError, Forbidden, NotFound, MethodNotAllowed, NotAcceptable
// errors, remove the access token
if (error.code > 400 && error.code < 408) {
const promise = this.removeAccessToken().then(() => this.reset());
return type === 'logout' ? promise : promise.then(() => Promise.reject(error));
}
return this.reset().then(() => Promise.reject(error));
}
/**
* Try to reauthenticate using the token from storage. Will do nothing if already authenticated unless
* `force` is true.
*
* @param force force reauthentication with the server
* @param strategy The name of the strategy to use. Defaults to `options.jwtStrategy`
* @param authParams Additional authentication parameters
* @returns The reauthentication result
*/
reAuthenticate(force = false, strategy, authParams) {
// Either returns the authentication state or
// tries to re-authenticate with the stored JWT and strategy
let authPromise = this.app.get('authentication');
if (!authPromise || force === true) {
authPromise = this.getAccessToken().then((accessToken) => {
if (!accessToken) {
return this.handleError(new errors_1.NotAuthenticated('No accessToken found in storage'), 'authenticate');
}
return this.authenticate({
strategy: strategy || this.options.jwtStrategy,
accessToken
}, authParams);
});
this.app.set('authentication', authPromise);
}
return authPromise;
}
/**
* Authenticate using a specific strategy and data.
*
* @param authentication The authentication data
* @param params Additional parameters
* @returns The authentication result
*/
authenticate(authentication, params) {
if (!authentication) {
return this.reAuthenticate();
}
const promise = this.service
.create(authentication, params)
.then((authResult) => {
const { accessToken } = authResult;
this.authenticated = true;
this.app.emit('login', authResult);
this.app.emit('authenticated', authResult);
return this.setAccessToken(accessToken).then(() => authResult);
})
.catch((error) => this.handleError(error, 'authenticate'));
this.app.set('authentication', promise);
return promise;
}
/**
* Log out the current user and remove their token. Will do nothing
* if not authenticated.
*
* @returns The log out result.
*/
logout() {
return Promise.resolve(this.app.get('authentication'))
.then(() => this.service.remove(null).then((authResult) => this.removeAccessToken()
.then(() => this.reset())
.then(() => {
this.app.emit('logout', authResult);
return authResult;
})))
.catch((error) => this.handleError(error, 'logout'));
}
}
exports.AuthenticationClient = AuthenticationClient;
//# sourceMappingURL=core.js.map