fluro
Version:
Promise based HTTP Fluro client for the browser and node.js
814 lines (576 loc) • 28.8 kB
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>api/fluro.auth.js - Documentation</title>
<script src="scripts/prettify/prettify.js"></script>
<script src="scripts/prettify/lang-css.js"></script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<input type="checkbox" id="nav-trigger" class="nav-trigger" />
<label for="nav-trigger" class="navicon-button x">
<div class="navicon"></div>
</label>
<label for="nav-trigger" class="overlay"></label>
<nav >
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="FluroAccess.html">FluroAccess</a><ul class='methods'><li data-type='method'><a href="FluroAccess.html#.retrieveCurrentSession">retrieveCurrentSession</a></li><li data-type='method'><a href="FluroAccess.html#.setDefaultApplication">setDefaultApplication</a></li></ul></li><li><a href="FluroAPI.html">FluroAPI</a></li><li><a href="FluroAsset.html">FluroAsset</a><ul class='methods'><li data-type='method'><a href="FluroAsset.html#.avatarUrl">avatarUrl</a></li><li data-type='method'><a href="FluroAsset.html#.downloadUrl">downloadUrl</a></li><li data-type='method'><a href="FluroAsset.html#.filesize">filesize</a></li><li data-type='method'><a href="FluroAsset.html#.getUrl">getUrl</a></li><li data-type='method'><a href="FluroAsset.html#.imageUrl">imageUrl</a></li><li data-type='method'><a href="FluroAsset.html#.posterUrl">posterUrl</a></li></ul></li><li><a href="FluroAuth.html">FluroAuth</a><ul class='methods'><li data-type='method'><a href="FluroAuth.html#.addEventListener">addEventListener</a></li><li data-type='method'><a href="FluroAuth.html#.changeAccount">changeAccount</a></li><li data-type='method'><a href="FluroAuth.html#.getCurrentToken">getCurrentToken</a></li><li data-type='method'><a href="FluroAuth.html#.getCurrentUser">getCurrentUser</a></li><li data-type='method'><a href="FluroAuth.html#.login">login</a></li><li data-type='method'><a href="FluroAuth.html#.logout">logout</a></li><li data-type='method'><a href="FluroAuth.html#.refreshAccessToken">refreshAccessToken</a></li><li data-type='method'><a href="FluroAuth.html#.removeAllListeners">removeAllListeners</a></li><li data-type='method'><a href="FluroAuth.html#.removeEventListener">removeEventListener</a></li><li data-type='method'><a href="FluroAuth.html#.sendResetPasswordRequest">sendResetPasswordRequest</a></li><li data-type='method'><a href="FluroAuth.html#.set">set</a></li><li data-type='method'><a href="FluroAuth.html#.signup">signup</a></li></ul></li><li><a href="FluroCache.html">FluroCache</a><ul class='methods'><li data-type='method'><a href="FluroCache.html#.get">get</a></li><li data-type='method'><a href="FluroCache.html#.reset">reset</a></li></ul></li><li><a href="FluroContent.html">FluroContent</a><ul class='methods'><li data-type='method'><a href="FluroContent.html#.external">external</a></li><li data-type='method'><a href="FluroContent.html#.get">get</a></li><li data-type='method'><a href="FluroContent.html#.query">query</a></li><li data-type='method'><a href="FluroContent.html#.retrieve">retrieve</a></li><li data-type='method'><a href="FluroContent.html#.search">search</a></li><li data-type='method'><a href="FluroContent.html#.slug">slug</a></li></ul></li><li><a href="FluroCore.html">FluroCore</a></li><li><a href="FluroDate.html">FluroDate</a><ul class='methods'><li data-type='method'><a href="FluroDate.html#.localDate">localDate</a></li><li data-type='method'><a href="FluroDate.html#.readableEventDate">readableEventDate</a></li></ul></li><li><a href="FluroTypes.html">FluroTypes</a><ul class='methods'><li data-type='method'><a href="FluroTypes.html#.retrieve">retrieve</a></li></ul></li><li><a href="FluroUtils.html">FluroUtils</a><ul class='methods'><li data-type='method'><a href="FluroUtils.html#.arrayIDs">arrayIDs</a></li><li data-type='method'><a href="FluroUtils.html#.comma">comma</a></li><li data-type='method'><a href="FluroUtils.html#.errorMessage">errorMessage</a></li><li data-type='method'><a href="FluroUtils.html#.getStringID">getStringID</a></li><li data-type='method'><a href="FluroUtils.html#.mapParameters">mapParameters</a></li></ul></li></ul><h3>Modules</h3><ul><li><a href="module-fluro.html">fluro</a></li></ul>
</nav>
<div id="main">
<h1 class="page-title">api/fluro.auth.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>import axios from 'axios';
import {EventDispatcher} from './fluro.utils';
///////////////////////////////////////////////////
/**
* Creates a new FluroAsset instance.
* This module provides a number of helper functions for authentication, logging in, signing up, generating and refreshing tokens
*
* @constructor
* @param {FluroCore} fluro A reference to the parent instance of the FluroCore module. This module is usually created by a FluroCore instance that passes itself in as the first argument.
*/
var FluroAuth = function(fluro) {
if (!fluro.api) {
throw new Error(`Can't Instantiate FluroAuth before FluroAPI exists`);
}
//Keep track of any refresh requests
var inflightRefreshRequest;
///////////////////////////////////////////////////
var defaultStore = {};
var store = defaultStore;
///////////////////////////////////////////////////
///////////////////////////////////////////////////
var service = {}
//Create a new dispatcher
var dispatcher = new EventDispatcher();
dispatcher.bootstrap(service);
// console.log('New Dispatcher!', dispatcher)
///////////////////////////////////////////////////
///////////////////////////////////////////////////
function dispatch() {
//Get the current user
var user = store.user;
//Dispatch the change to the listeners
if (service.onChange) {
service.onChange(user);
}
//Dispatch the change event
console.log('Dispatch change!', user);
dispatcher.dispatch('change', user);
}
///////////////////////////////////////////////////
function log(message) {
if (service.debug) {
console.log(message);
}
}
///////////////////////////////////////////////////
/**
*
* Sets the current user data, often from localStorage or after new session data
* has been generated from the server after signing in
* @alias FluroAuth.set
* @param {Object} user The user session object
* @example
* FluroAsset.set({firstName:'Jeff', lastName:'Andrews', ...})
*/
service.set = function(user) {
store.user = user;
log('fluro.auth > user set');
return dispatch()
}
///////////////////////////////////////////////////
/**
*
* Deletes the user session object, clears all Fluro caches and tokens
* from memory
* @alias FluroAuth.logout
* @example
* FluroAuth.logout()
*/
service.logout = function() {
//Unauthenticated
// delete store.token;
delete store.user;
fluro.cache.reset();
// delete store.refreshToken;
// delete store.expires;
log('fluro.auth > user logout');
return dispatch()
}
///////////////////////////////////////////////////
/**
*
* Retrieves a new session object for a Fluro global user for a specified account
* This will only work if the user has a persona in that account
* @alias FluroAuth.changeAccount
* @param {String} accountID The _id of the account you wish to log in to
* @param {Object} options
* @param {Object} options.disableAutoAuthenticate By default this function will set the current user session
* to account you are changing in to.
* If you want to generate the session without affecting your current session you can set disableAutoAuthenticate to true
* @return {Promise} Resolves to the user session object, or rejects with the responding error
* @example
* FluroAuth.changeAccount('5be504eabf33991239599d63').then(function(userSession) {
* //New user session will be set automatically
* var newUserSession = FluroAuth.getCurrentUser();
* })
* FluroAuth.changeAccount('5be504eabf33991239599d63', {disableAutoAuthenticate:true}).then(function(userSession) {
* //Set the session manually
* FluroAuth.set(userSession)
* })
*/
service.changeAccount = function(accountID, options) {
console.log('Change account', accountID)
//Ensure we just have the ID
accountID = fluro.utils.getStringID(accountID);
//////////////////////////
if (!options) {
options = {};
}
//////////////////////////
//Change the users current tokens straight away
var autoAuthenticate = true;
if (options.disableAutoAuthenticate) {
autoAuthenticate = false;
}
//////////////////////////
var promise = fluro.api.post(`/token/account/${accountID}`)
promise.then(function(res) {
if (autoAuthenticate) {
fluro.cache.reset();
service.set(res.data);
}
}, function(err) {
console.log('ERROR', err);
});
return promise;
}
///////////////////////////////////////////////////
/**
* Logs the user in to Fluro and returns a new user session
* @alias FluroAuth.login
* @param {Object} credentials
* @param {String} credentials.username The email address of the user to login as
* @param {String} credentials.password The password for the user
* @param {Object} options Extra options and configuration for the request
* @param {Object} options.disableAutoAuthenticate Disable automatic authentication, if true, will not set the current user session
* @param {Object} options.application Whether to attempt to login to the current application as a managed user persona, if not set will login as a global Fluro user
* @return {Promise} Returns a promise that either resolves with the logged in user session, or rejects with the responding error from the server
*/
service.login = function(credentials, options) {
if (!options) {
options = {};
}
//////////////////////////
//Change the users current tokens straight away
var autoAuthenticate = true;
if (options.disableAutoAuthenticate) {
autoAuthenticate = false;
}
//////////////////////////////////////
var promise = new Promise(loginCheck)
function loginCheck(resolve, reject) {
if (!credentials) {
return reject({
message: 'Missing credentials!',
})
}
if (!credentials.username || !credentials.username.length) {
return reject({
message: 'Username was not provided',
})
}
if (!credentials.password || !credentials.password.length) {
return reject({
message: 'Password was not provided',
})
}
/////////////////////////////////////////////
var postOptions = {
bypassInterceptor: true
}
/////////////////////////////////////////////
var url = fluro.apiURL + '/token/login';
/////////////////////////////////////////////
//If we are authenticating as an application
if (options.application) {
//The url is relative to the domain
url = `${fluro.domain || ''}/fluro/application/login`;
}
/////////////////////////////////////////////
//If we are logging in to a managed account use a different endpoint
if (options.managedAccount) {
url = fluro.apiURL + '/managed/' + options.managedAccount + '/login';
}
//If we have a specified url
if (options.url) {
url = options.url;
}
/////////////////////////////////////////////
fluro.api.post(url, credentials, postOptions).then(function(res) {
if(autoAuthenticate) {
store.user = res.data;
dispatch();
// if (service.onChange) {
// service.onChange(store.user);
// }
}
resolve(res);
}, reject);
}
//////////////////////////////////////
return promise;
}
///////////////////////////////////////////////////
/**
* Signs up a new user to the current application, this will create a new managed user persona
* and automatically log in as that persona in the current application context. This function will
* only work when called in context of an application with the 'Application Token' authentication style.
* It will create a new user persona in the account of the application and return a session with all of the application's
* permissions and application's logged in user permissions
* @alias FluroAuth.signup
* @param {Object} credentials
* @param {String} credentials.firstName The first name for the new user persona
* @param {String} credentials.lastName The last name for the new user persona
* @param {String} credentials.username The email address for the new persona
* @param {String} credentials.password The password to set for the new persona
* @param {String} credentials.confirmPassword A double check to confirm the new password for the persona
* @param {Object} options Extra options and configuration for the request
* @return {Promise} Returns a promise that either resolves to the new authenticated session, or rejects with the responding error from the server
*/
service.signup = function(credentials, options) {
if (!options) {
options = {};
}
//////////////////////////
//Change the users current tokens straight away
var autoAuthenticate = true;
if (options.disableAutoAuthenticate) {
autoAuthenticate = false;
}
//////////////////////////////////////
var promise = new Promise(signupCheck)
function signupCheck(resolve, reject) {
if (!credentials) {
return reject({
message: 'No details provided',
})
}
if (!credentials.firstName || !credentials.firstName.length) {
return reject({
message: 'First Name was not provided',
})
}
if (!credentials.lastName || !credentials.lastName.length) {
return reject({
message: 'Last Name was not provided',
})
}
if (!credentials.username || !credentials.username.length) {
return reject({
message: 'Email/Username was not provided',
})
}
if (!credentials.password || !credentials.password.length) {
return reject({
message: 'Password was not provided',
})
}
if (!credentials.confirmPassword || !credentials.confirmPassword.length) {
return reject({
message: 'Confirm Password was not provided',
})
}
if (credentials.confirmPassword != credentials.password) {
return reject({
message: 'Your passwords do not match',
})
}
/////////////////////////////////////////////
var postOptions = {
bypassInterceptor: true
}
/////////////////////////////////////////////
var url = fluro.apiURL + '/token/signup';
/////////////////////////////////////////////
//If we are authenticating as an application
if (options.application) {
//The url is relative to the domain
url = `${fluro.domain || ''}/fluro/application/signup`;
}
//If we have a specified url
if (options.url) {
url = options.url;
}
/////////////////////////////////////////////
fluro.api.post(url, credentials, postOptions).then(function(res) {
if (autoAuthenticate) {
store.user = res.data;
dispatch();
}
resolve(res);
}, reject);
}
//////////////////////////////////////
return promise;
}
///////////////////////////////////////////////////
/**
* Triggers a new Reset Password email request to the specified user.
* @alias FluroAuth.sendResetPasswordRequest
* @param {Object} body
* @param {String} body.username The email address of the user to reset the password for
* @param {String} body.redirect If the request is in the context of a managed user persona authenticated with an application, then you need to provide the url to direct the user to when they click the reset password link
* This is usually something like '/reset' for the current application, when the user clicks the link the reset token will be appended with ?token=RESET_TOKEN and your application should
* be ready on that url to handle the token and allow the user to use the token to reset their password
* @param {Object} options Extra options and configuration for the request
* @param {Boolean} options.application If true will send a reset email from the context of a managed persona in the same account as the current application.
* If not specified or false, will send a password reset request for a global Fluro user account.
* @return {Promise} Returns a promise that either resolves if the password request was sent, or rejects if an error occurred
*/
service.sendResetPasswordRequest = function(body, options) {
if (!options) {
options = {};
}
//////////////////////////////////////
var promise = new Promise(signupCheck)
function signupCheck(resolve, reject) {
if (!body) {
return reject({
message: 'No details provided',
})
}
if (!body.username || !body.username.length) {
return reject({
message: 'Email/Username was not provided',
})
}
//Set username as the email address
body.email = body.username;
/////////////////////////////////////////////
var postOptions = {
bypassInterceptor: true
}
/////////////////////////////////////////////
//If a full fledged Fluro User
//then send directly to the API
var url = fluro.apiURL + '/resend';
/////////////////////////////////////////////
//If we are authenticating as an application
if (options.application) {
//The url is relative to the domain
url = `${fluro.domain || ''}/fluro/application/forgot`;
}
//If we have a specified url
if (options.url) {
url = options.url;
}
/////////////////////////////////////////////
fluro.api.post(url, body, postOptions).then(resolve, reject);
}
//////////////////////////////////////
return promise;
}
///////////////////////////////////////////////////
/**
* Helper function to refresh an access token for an authenticated user session. This is usually handled automatically
* from the FluroAuth service itself
* @alias FluroAuth.refreshAccessToken
* @param {String} refreshToken The refresh token to reactivate
* @param {Boolean} isManagedSession Whether or not the refresh token is for a managed persona session or a global Fluro user session
* @return {Promise} A promise that either resolves with the refreshed token details or rejects with the responding error from the server
*/
service.refreshAccessToken = function(refreshToken, isManagedSession) {
//If there is already a request in progress
if (inflightRefreshRequest) {
return inflightRefreshRequest;
}
/////////////////////////////////////////////////////
//Create an refresh request
inflightRefreshRequest = new Promise(function(resolve, reject) {
log('fluro.auth > refresh token');
//Bypass the interceptor on all token refresh calls
//Because we don't need to add the access token etc onto it
fluro.api.post('token/refresh', {
refreshToken: refreshToken,
managed: isManagedSession,
}, {
bypassInterceptor: true
})
.then(function tokenRefreshComplete(res) {
//Update the user with any changes
//returned back from the refresh request
Object.assign(store.user, res.data);
log('fluro.auth > token refreshed');
// if (service.onChange) {
// service.onChange(store.user);
// }
dispatch();
//Resolve with the new token
resolve(res.data.token);
//Remove the inflight request
inflightRefreshRequest = null;
}, reject);
});
//Return the refresh request
return inflightRefreshRequest;
}
/////////////////////////////////////////////////////
/**
* Returns the current user's access token
* @alias FluroAuth.getCurrentToken
* @return {String} The Fluro access token for the current user session
*/
service.getCurrentToken = function() {
return _.get(store, 'user.token') || fluro.applicationToken;
}
/////////////////////////////////////////////////////
/**
* Returns the current user's session data
* @alias FluroAuth.getCurrentUser
* @return {Object} The current user session
*/
service.getCurrentUser = function() {
return _.get(store, 'user');
}
/////////////////////////////////////////////////////
fluro.api.interceptors.request.use(function(config) {
//If we want to bypass the interceptor
//then just return the request
if (config.bypassInterceptor) {
return config;
}
//////////////////////////////
//Get the original request
var originalRequest = config;
//If we aren't logged in or don't have a token
var token = _.get(store, 'user.token');
var refreshToken = _.get(store, 'user.refreshToken');
//////////////////////////////
//If there is a user token
if (token) {
//Set the token of the request as the user's access token
originalRequest.headers['Authorization'] = 'Bearer ' + token;
log('fluro.auth > using user token');
} else if (fluro.applicationToken && fluro.applicationToken.length) {
//If there is a static application token
//For example we have logged out from a website
//that has public content also
originalRequest.headers['Authorization'] = 'Bearer ' + fluro.applicationToken;
log('fluro.auth > using app token');
return originalRequest;
} else {
//Return the original request without a token
log('fluro.auth > no token');
return originalRequest;
}
/////////////////////////////////////////////////////
//If no refresh token
if (!refreshToken) {
log('fluro.auth > no refresh token');
//Continue with the original request
return originalRequest;
}
/////////////////////////////////////////////////////
//We have a refresh token so we need to check
//whether our access token is stale and needs to be refreshed
var now = new Date();
//Give us a bit of buffer incase some of our images
//are still loading
now.setSeconds(now.getSeconds() + 10);
var expiryDate = _.get(store, 'user.expires');
var expires = new Date(expiryDate);
//If we are not debugging
if (!service.debug) {
//If the token is still fresh
if (now < expires) {
//Return the original request
return originalRequest;
}
}
/////////////////////////////////////////////////////
var isManagedUser = _.get(store, 'user.accountType') == 'managed';
//The token is stale by this point
log('fluro.auth > token expired');
return new Promise(function(resolve, reject) {
//Refresh the token
service.refreshAccessToken(refreshToken, isManagedUser)
.then(function(newToken) {
log('Token refreshed', isManagedUser);
//Update the original request with our new token
originalRequest.headers['Authorization'] = 'Bearer ' + newToken;
//And continue onward
return resolve(originalRequest);
}, reject);
});
}, function(error) {
return Promise.reject(error);
})
/////////////////////////////////////////////////////
fluro.api.interceptors.response.use(function(response) {
return response;
}, function(err) {
//Get the response status
var status = _.get(err, 'response.status') || err.status;
log('fluro.auth > error', status);
switch (status) {
case 401:
service.logout();
break;
default:
//Some other error
break;
}
/////////////////////////////////////////////////////
///
return Promise.reject(err);
})
/**
* @name FluroAuth.addEventListener
* @description Adds a callback that will be triggered whenever the specified event occurs
* @function
* @param {String} event The event to listen for
* @param {Function} callback The function to fire when this event is triggered
* @example
* //Listen for when the user session changes
* FluroAuth.addEventListener('change', function(userSession) {})
*/
/**
* @name FluroAuth.removeEventListener
* @description Removes all a callback from the listener list
* @function
* @param {String} event The event to stop listening for
* @param {Function} callback The function to remove from the listener list
* @example
* //Stop listening for the change event
* FluroAuth.removeEventListener('change', myFunction)
*/
/**
* @name FluroAuth.removeAllListeners
* @description Removes all listening callbacks for all events
* @function
* @example
* FluroAuth.removeAllListeners()
*/
return service;
}
export default FluroAuth;</code></pre>
</article>
</section>
</div>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Wed Apr 24 2019 19:46:45 GMT+1000 (Australian Eastern Standard Time) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
</footer>
<script>prettyPrint();</script>
<script src="scripts/polyfill.js"></script>
<script src="scripts/linenumber.js"></script>
</body>
</html>