@qikdev/sdk
Version:
Promise based Javascript SDK
1,045 lines (784 loc) • 42.2 kB
HTML
<html lang="en">
<head>
<meta charset="utf-8">
<title>qik.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">
<script src="scripts/nav.js" defer></script>
<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="access.html">access</a><ul class='methods'><li data-type='method'><a href="access.html#.actionableScopes">actionableScopes</a></li><li data-type='method'><a href="access.html#.allPermissions">allPermissions</a></li><li data-type='method'><a href="access.html#.allPermissionTypes">allPermissionTypes</a></li><li data-type='method'><a href="access.html#.allUserScopes">allUserScopes</a></li><li data-type='method'><a href="access.html#.canCreate">canCreate</a></li><li data-type='method'><a href="access.html#.canDeleteItem">canDeleteItem</a></li><li data-type='method'><a href="access.html#.canEditItem">canEditItem</a></li><li data-type='method'><a href="access.html#.canEraseItem">canEraseItem</a></li><li data-type='method'><a href="access.html#.canKnowOf">canKnowOf</a></li><li data-type='method'><a href="access.html#.canListItem">canListItem</a></li><li data-type='method'><a href="access.html#.canRestoreItem">canRestoreItem</a></li><li data-type='method'><a href="access.html#.canViewItem">canViewItem</a></li><li data-type='method'><a href="access.html#.checkActionAccess">checkActionAccess</a></li><li data-type='method'><a href="access.html#.getAllDescendants">getAllDescendants</a></li><li data-type='method'><a href="access.html#.has">has</a></li><li data-type='method'><a href="access.html#.hasFeature">hasFeature</a></li><li data-type='method'><a href="access.html#.hashPermissions">hashPermissions</a></li><li data-type='method'><a href="access.html#.isAdministrator">isAdministrator</a></li><li data-type='method'><a href="access.html#.isAssignee">isAssignee</a></li><li data-type='method'><a href="access.html#.isOwner">isOwner</a></li></ul></li><li><a href="api.html">api</a><ul class='methods'><li data-type='method'><a href="api.html#.generateEndpointURL">generateEndpointURL</a></li></ul></li><li><a href="auth.html">auth</a><ul class='methods'><li data-type='method'><a href="auth.html#.changeOrganisation">changeOrganisation</a></li><li data-type='method'><a href="auth.html#.ensureValidToken">ensureValidToken</a></li><li data-type='method'><a href="auth.html#.getCurrentToken">getCurrentToken</a></li><li data-type='method'><a href="auth.html#.getCurrentUser">getCurrentUser</a></li><li data-type='method'><a href="auth.html#.impersonate">impersonate</a></li><li data-type='method'><a href="auth.html#.login">login</a></li><li data-type='method'><a href="auth.html#.logout">logout</a></li><li data-type='method'><a href="auth.html#.retrieveUserFromResetToken">retrieveUserFromResetToken</a></li><li data-type='method'><a href="auth.html#.sendResetPasswordRequest">sendResetPasswordRequest</a></li><li data-type='method'><a href="auth.html#.set">set</a></li><li data-type='method'><a href="auth.html#.sync">sync</a></li><li data-type='method'><a href="auth.html#.updateUserWithToken">updateUserWithToken</a></li></ul></li><li><a href="cache.html">cache</a><ul class='methods'><li data-type='method'><a href="cache.html#.get">get</a></li><li data-type='method'><a href="cache.html#.reset">reset</a></li></ul></li><li><a href="content.html">content</a><ul class='methods'><li data-type='method'><a href="content.html#.create">create</a></li><li data-type='method'><a href="content.html#.delete">delete</a></li><li data-type='method'><a href="content.html#.get">get</a></li><li data-type='method'><a href="content.html#.getFromSlug">getFromSlug</a></li><li data-type='method'><a href="content.html#.glossary">glossary</a></li><li data-type='method'><a href="content.html#.list">list</a></li><li data-type='method'><a href="content.html#.patch">patch</a></li><li data-type='method'><a href="content.html#.restore">restore</a></li><li data-type='method'><a href="content.html#.scopeGlossary">scopeGlossary</a></li><li data-type='method'><a href="content.html#.update">update</a></li><li data-type='method'><a href="content.html#.validateField">validateField</a></li><li data-type='method'><a href="content.html#.variables">variables</a></li></ul></li><li><a href="files.html">files</a><ul class='methods'><li data-type='method'><a href="files.html#.downloadUrl">downloadUrl</a></li><li data-type='method'><a href="files.html#.duration">duration</a></li><li data-type='method'><a href="files.html#.filesize">filesize</a></li><li data-type='method'><a href="files.html#.getBinaryTypeFromMime">getBinaryTypeFromMime</a></li><li data-type='method'><a href="files.html#.mediaUrl">mediaUrl</a></li></ul></li><li><a href="filter.html">filter</a><ul class='methods'><li data-type='method'><a href="filter.html#.activeFilterComparators">activeFilterComparators</a></li><li data-type='method'><a href="filter.html#.activeFilterKeys">activeFilterKeys</a></li><li data-type='method'><a href="filter.html#.activeFilterOperators">activeFilterOperators</a></li><li data-type='method'><a href="filter.html#.activeFilters">activeFilters</a></li><li data-type='method'><a href="filter.html#.activeFilterValues">activeFilterValues</a></li><li data-type='method'><a href="filter.html#.filterChangeString">filterChangeString</a></li><li data-type='method'><a href="filter.html#.isValidFilter">isValidFilter</a></li></ul></li><li><a href="geo.html">geo</a><ul class='methods'><li data-type='method'><a href="geo.html#.distance">distance</a></li><li data-type='method'><a href="geo.html#.getDefaultCountry">getDefaultCountry</a></li><li data-type='method'><a href="geo.html#.getDefaultTimezone">getDefaultTimezone</a></li><li data-type='method'><a href="geo.html#.getPosition">getPosition</a></li><li data-type='method'><a href="geo.html#.stopWatchPosition">stopWatchPosition</a></li><li data-type='method'><a href="geo.html#.watchPosition">watchPosition</a></li></ul></li><li><a href="module-QikStorage-CookieStorageAdapter.html">CookieStorageAdapter</a><ul class='methods'><li data-type='method'><a href="module-QikStorage-CookieStorageAdapter.html#getCookieOptions">getCookieOptions</a></li><li data-type='method'><a href="module-QikStorage-CookieStorageAdapter.html#isCookieMode">isCookieMode</a></li></ul></li><li><a href="module-QikStorage-LocalStorageAdapter.html">LocalStorageAdapter</a></li><li><a href="module-QikStorage-StorageAdapter.html">StorageAdapter</a><ul class='methods'><li data-type='method'><a href="module-QikStorage-StorageAdapter.html#clearUser">clearUser</a></li><li data-type='method'><a href="module-QikStorage-StorageAdapter.html#getAccessToken">getAccessToken</a></li><li data-type='method'><a href="module-QikStorage-StorageAdapter.html#getRefreshToken">getRefreshToken</a></li><li data-type='method'><a href="module-QikStorage-StorageAdapter.html#getTokenExpiry">getTokenExpiry</a></li><li data-type='method'><a href="module-QikStorage-StorageAdapter.html#getUser">getUser</a></li><li data-type='method'><a href="module-QikStorage-StorageAdapter.html#isAvailable">isAvailable</a></li><li data-type='method'><a href="module-QikStorage-StorageAdapter.html#setUser">setUser</a></li></ul></li><li><a href="socket.html">socket</a><ul class='methods'><li data-type='method'><a href="socket.html#.channel">channel</a></li><li data-type='method'><a href="socket.html#.close">close</a></li><li data-type='method'><a href="socket.html#.connect">connect</a></li><li data-type='method'><a href="socket.html#.reconnect">reconnect</a></li><li data-type='method'><a href="socket.html#.subscribe">subscribe</a></li><li data-type='method'><a href="socket.html#.unsubscribe">unsubscribe</a></li></ul></li><li><a href="system.html">system</a><ul class='methods'><li data-type='method'><a href="system.html#.countries">countries</a></li></ul></li><li><a href="utils.html">utils</a><ul class='methods'><li data-type='method'><a href="utils.html#.cleanValue">cleanValue</a></li><li data-type='method'><a href="utils.html#.comma">comma</a></li><li data-type='method'><a href="utils.html#.errorMessage">errorMessage</a></li><li data-type='method'><a href="utils.html#.exists">exists</a></li><li data-type='method'><a href="utils.html#.extractFromArray">extractFromArray</a></li><li data-type='method'><a href="utils.html#.formatCurrency">formatCurrency</a></li><li data-type='method'><a href="utils.html#.getTypeFromID">getTypeFromID</a></li><li data-type='method'><a href="utils.html#.guid">guid</a></li><li data-type='method'><a href="utils.html#.hash">hash</a></li><li data-type='method'><a href="utils.html#.id">id</a></li><li data-type='method'><a href="utils.html#.ids">ids</a></li><li data-type='method'><a href="utils.html#.isValidEmailAddress">isValidEmailAddress</a></li><li data-type='method'><a href="utils.html#.loadExternalScript">loadExternalScript</a></li><li data-type='method'><a href="utils.html#.loadExternalStyle">loadExternalStyle</a></li><li data-type='method'><a href="utils.html#.machineName">machineName</a></li><li data-type='method'><a href="utils.html#.mapParameters">mapParameters</a></li><li data-type='method'><a href="utils.html#.matchInArray">matchInArray</a></li><li data-type='method'><a href="utils.html#.parseBoolean">parseBoolean</a></li><li data-type='method'><a href="utils.html#.parseDate">parseDate</a></li><li data-type='method'><a href="utils.html#.parseEmail">parseEmail</a></li><li data-type='method'><a href="utils.html#.parseInt">parseInt</a></li><li data-type='method'><a href="utils.html#.parseNumber">parseNumber</a></li><li data-type='method'><a href="utils.html#.parseURL">parseURL</a></li></ul></li></ul><h3>Modules</h3><ul><li><a href="module-QikStorage.html">QikStorage</a><ul class='methods'><li data-type='method'><a href="module-QikStorage.html#.createStorageAdapter">createStorageAdapter</a></li></ul></li></ul><h3>Global</h3><ul><li><a href="global.html#EventDispatcher">EventDispatcher</a></li></ul>
</nav>
<div id="main">
<h1 class="page-title">qik.auth.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>import { EventDispatcher } from './qik.utils.js';
import { createStorageAdapter } from './qik.storage.js';
///////////////////////////////////////////////////
/**
* Creates a new instance of QikAuth a module of the SDK
* that contains all helper functions to do with authentication and user session management
* @alias auth
* @constructor
* @hideconstructor
* @param {QikAPI} qik A reference to the parent instance of the QikCore module. This module is usually created by a QikCore instance that passes itself in as the first argument.
*/
var QikAuth = function(qik) {
if (!qik.api) {
throw new Error(`Please ensure that QikAPI exists before QikAuth`);
}
//Keep track of any refresh requests
var inflightRefreshRequest;
///////////////////////////////////////////////////
// Create storage adapter based on configuration
const storageAdapter = createStorageAdapter({
useHttpOnlyCookies: qik.useHttpOnlyCookies,
cookieConfig: qik.cookieConfig
});
const tokenBufferSeconds = 10;
///////////////////////////////////////////////////
const service = {
debug: false,
storageAdapter: storageAdapter,
}
// Backwards compatibility - keep store reference
Object.defineProperty(service, 'store', {
get: function() {
return { user: storageAdapter.getUser() };
},
enumerable: true
});
//Create a new dispatcher
const dispatcher = new EventDispatcher();
dispatcher.bootstrap(service);
///////////////////////////////////////////////////
function dispatch(parameters) {
//Get the current user
var user = storageAdapter.getUser();
//Dispatch the change to the listeners
if (service.onChange) {
service.onChange(user);
}
//Dispatch the change event
dispatcher.dispatch('change', user, parameters);
}
///////////////////////////////////////////////////
/**
* @alias auth.set
* @description Manually set current user session
* @param {Object} user The user session object to set as the current user session
* @param {Object} parameters Additional parameters to dispatch
* @param {Boolean} stopDispatch Whether to supress dispatching a 'change' event.
* @example
*
* const userSession = {_id:'61eca4746971e75c1fc670cf', firstName:'Daffy', lastName:'Duck' ...};
* sdk.auth.set(userSession);
*/
service.set = function(user, parameters, stopDispatch) {
const currentUser = storageAdapter.getUser();
if (JSON.stringify(currentUser) != JSON.stringify(user)) {
storageAdapter.setUser(user);
if (!stopDispatch) {
return dispatch(parameters)
}
}
}
///////////////////////////////////////////////////
/**
* @alias auth.logout
* @description Clear the current user session from memory and erase all caches
* @example
* sdk.auth.logout();
*/
service.logout = function() {
storageAdapter.clearUser();
qik.cache.reset();
return dispatch()
}
///////////////////////////////////////////////////
/**
* @alias auth.changeOrganisation
* @description Manually set current user session
* @param {(String|Object)} organisation The id of the organisation to switch into
* @param {Object} options Additional options
* @param {Boolean} options.disableAutoAuthentication By default when switching organisation, the current user session
* will be updated to reflect a session in the new organisation, you can use this option to disable that behavior and instead return the
* user session without dispatching any events
* @example
*
* sdk.auth.changeOrganisation('61eca4746971e75c1fc670cf');
* // Current user session will be automatically updated
*
* const newSession = await sdk.auth.changeOrganisation('61eca4746971e75c1fc670cf', {disableAutoAuthentication:true});
* // Current user session will not be updated
* sdk.auth.set(newSession);
*/
service.changeOrganisation = function(organisationID, options) {
//Ensure we just have the ID
organisationID = qik.utils.id(organisationID);
//////////////////////////
if (!options) {
options = {};
}
//////////////////////////
//Change the users current tokens straight away
var autoAuthenticate = true;
if (options.disableAutoAuthentication) {
autoAuthenticate = false;
}
//////////////////////////
return new Promise(function(resolve, reject) {
qik.api.post(`/user/switch/${organisationID}`)
.then(function(response) {
if (autoAuthenticate) {
qik.cache.reset();
service.set(response.data);
}
resolve(response.data);
})
.catch(reject)
})
}
///////////////////////////////////////////////////
/**
* @alias auth.impersonate
* @description Impersonate another user within your organisation
* @param {(String|Object)} persona The id of the user persona you want to impersonate
* @param {Object} options Additional options
* @param {Boolean} options.disableAutoAuthentication By default when impersonating a user, the current user session
* will be updated automatically to reflect the new session, you can use this option to disable that behavior and instead return the
* new impersonation user session without dispatching any events
* @example
*
* sdk.auth.impersonate('61eca4746971e75c1fc670cf');
* // Current user session will be automatically updated
*
* const newSession = await sdk.auth.impersonate('61eca4746971e75c1fc670cf', {disableAutoAuthentication:true});
* // Current user session will not be updated
* sdk.auth.set(newSession);
*/
service.impersonate = function(personaID, options) {
//Ensure we just have the ID
personaID = qik.utils.id(personaID);
//////////////////////////
if (!options) {
options = {};
}
//////////////////////////
//Change the users current tokens straight away
var autoAuthenticate = true;
if (options.disableAutoAuthentication) {
autoAuthenticate = false;
}
//////////////////////////
var promise = qik.api.post(`/user/impersonate/${personaID}`)
promise.then(function(res) {
if (autoAuthenticate) {
qik.cache.reset();
service.set(res.data);
}
}, function(err) {});
return promise;
}
///////////////////////////////////////////////////
/**
* @alias auth.login
* @description Login and authenticate as a user
* @param {Object} credentials The credentials used to login
* @param {String} credentials.email The email address to login to
* @param {String} credentials.password The password to login with
* @param {String} credentials.mfa The MFA (Multi Factor Authentication) code
* @param {Object} options Additional options
* @param {Boolean} options.disableAutoAuthentication By default when logging in the current user session
* will be updated automatically to reflect the new session, you can use this option to disable that behavior and instead return the
* session that was logged in to without dispatching any events
* @example
*
* const credentials = {
* email:'me@email.com',
* password:'******',
* mfa:'1234',
* }
*
* sdk.auth.login(credentials);
* // Current user session will be automatically updated
*
* const newSession = await sdk.auth.login(credentials, {disableAutoAuthentication:true});
* // Current user session will not be updated
* sdk.auth.set(newSession);
*/
service.login = async function(credentials, options) {
if (!options) {
options = {};
}
//////////////////////////
//Change the users current tokens straight away
var autoAuthenticate = true;
if (options.disableAutoAuthentication) {
autoAuthenticate = false;
}
//////////////////////////////////////
var promise = new Promise(loginCheck)
function loginCheck(resolve, reject) {
if (!credentials) {
return reject({
message: 'Login requires an email and password',
})
}
if (!credentials.email || !credentials.email.length) {
return reject({
message: 'An email address is required to log in',
})
}
if (!credentials.password || !credentials.password.length) {
return reject({
message: 'A password is required to log in',
})
}
/////////////////////////////////////////////
var postOptions = {
bypassInterceptor: true
}
// Check if we're in cookie mode
const isCookieMode = storageAdapter.isCookieMode && storageAdapter.isCookieMode();
if (isCookieMode) {
// Signal to backend that we want cookie-based auth
credentials.useCookies = true;
postOptions.withCredentials = true;
}
/////////////////////////////////////////////
var url = `${qik.apiURL}/user/login`;
/////////////////////////////////////////////
//If we have a specified url
if (options.url) {
url = options.url;
}
/////////////////////////////////////////////
qik.api.post(url, credentials, postOptions).then(function(res) {
if (autoAuthenticate) {
service.set(res.data);
}
resolve(res);
}, reject);
}
//////////////////////////////////////
return promise;
}
///////////////////////////////////////////////////
service.signup = async function(credentials, options) {
if (!options) {
options = {};
}
//////////////////////////
//Change the users current tokens straight away
var autoAuthenticate = true;
if (options.disableAutoAuthentication) {
autoAuthenticate = false;
}
//////////////////////////////////////
var promise = new Promise(signupCheck)
function signupCheck(resolve, reject) {
if (!credentials) {
return reject({
message: 'Signup details are required',
})
}
if (!credentials.firstName || !credentials.firstName.length) {
return reject({
message: 'Please provide a first name',
})
}
if (!credentials.lastName || !credentials.lastName.length) {
return reject({
message: 'Please provide a last name',
})
}
if (!credentials.email || !credentials.email.length) {
return reject({
message: 'An email address is required to sign up',
})
}
if (!credentials.password || !credentials.password.length) {
return reject({
message: 'A password is required to sign up',
})
}
if (!credentials.confirmPassword || !credentials.confirmPassword.length) {
return reject({
message: 'Please confirm your password',
})
}
if (credentials.confirmPassword != credentials.password) {
return reject({
message: 'Password and confirmation do not match',
})
}
/////////////////////////////////////////////
var postOptions = {
bypassInterceptor: true
}
// Check if we're in cookie mode
const isCookieMode = storageAdapter.isCookieMode && storageAdapter.isCookieMode();
if (isCookieMode) {
// Signal to backend that we want cookie-based auth
credentials.useCookies = true;
postOptions.withCredentials = true;
}
/////////////////////////////////////////////
var url = `${qik.apiURL}/user/signup`;
/////////////////////////////////////////////
//If we are authenticating as an application
if (options.application) {
//The url is relative to the domain
url = `${qik.domain || ''}/qik/application/signup`;
}
//If we have a specified url
if (options.url) {
url = options.url;
}
/////////////////////////////////////////////
qik.api.post(url, credentials, postOptions).then(function(res) {
if (autoAuthenticate) {
service.set(res.data);
}
resolve(res);
}, reject);
}
//////////////////////////////////////
return promise;
}
///////////////////////////////////////////////////
/**
* @alias auth.retrieveUserFromResetToken
* @description Retrieve user session through use of a valid reset token,
* Reset tokens are short lived tokens that can be generated when a user has forgotten their password or their
* password has been reset by an administrator
* @param {String} resetToken The token to use to authenticate
* @param {Object} options Additional options for the request
* @example
*
* const resetToken = 'XXX-324623-$$...';
*
* // Retrieve the user session by providing a reset token
* const user = await sdk.auth.retrieveUserFromResetToken(resetToken);
*/
service.retrieveUserFromResetToken = async function(resetToken, options) {
if (!options) {
options = {};
}
var postOptions = {
bypassInterceptor: true
}
return new Promise(function(resolve, reject) {
qik.api.get(options.url || `${qik.apiURL}/user/reset/${resetToken}`, postOptions).then(function(res) {
return resolve(res.data);
}, reject);
});
}
///////////////////////////////////////////////////
/**
* @alias auth.updateUserWithToken
* @description Update a user's credentials through use of a reset token
* @param {String} resetToken The token to use to authenticate
* @param {Object} body Updates to be made to the user
* @param {Object} options Additional options for the request
* @param {Boolean} options.disableAutoAuthentication By default the current user session
* will be updated automatically to reflect the new updated session, you can use this option to disable that behavior
* and instead return the result without dispatching any events
* @example
*
* const resetToken = 'XXX-324623-$$...';
*
* // Retrieve the user session by providing a reset token
* const user = await sdk.auth.retrieveUserFromResetToken(resetToken);
*/
service.updateUserWithToken = async function(resetToken, body, options) {
if (!options) {
options = {};
}
//////////////////////////
//Change the users current tokens straight away
var autoAuthenticate = true;
if (options.disableAutoAuthentication) {
autoAuthenticate = false;
}
//////////////////////////////////////
return new Promise(function(resolve, reject) {
var postOptions = {
bypassInterceptor: true
}
qik.api.post(options.url || `${qik.apiURL}/user/reset/${resetToken}`, body, postOptions)
.then(function(res) {
//If we should automatically authenticate
//once the request is successful
//Then clear caches and update the session
if (autoAuthenticate) {
qik.cache.reset();
service.set(res.data);
}
return resolve(res.data);
}, reject);
});
}
/**
* @alias auth.sendResetPasswordRequest
* @description This function allows a reset token to be generated and emailed to the requesting user
* allowing them to modify their user details
* @param {Object} body Details for the reset request
* @param {String} body.email The email of the user to generate a token for
* @example
*
* const resetToken = 'XXX-324623-$$...';
*
* // Retrieve the user session by providing a reset token
* const user = await sdk.auth.retrieveUserFromResetToken(resetToken);
*/
service.sendResetPasswordRequest = function(details, options) {
if (!options) {
options = {};
}
if (!details) {
return Promise.reject({
message: 'Please provide your account details to reset your password',
})
}
if (!details.email || !details.email.length) {
return Promise.reject({
message: 'An email address is required for password reset',
})
}
return new Promise(function(resolve, reject) {
var postOptions = {
bypassInterceptor: true
}
qik.api.post(options.url || `${qik.apiURL}/user/forgot`, details, postOptions).then(resolve, reject);
})
}
///////////////////////////////////////////////////
/**
* @alias auth.ensureValidToken
* @description This function forces a check to ensure that the current access token has not expired.
* If the token has expired, the user session will be refreshed with a new token.
* @param {Boolean} forceRefresh Whether to force the current token to be refreshed, even if it has not yet expired.
* @example
*
* // Check to ensure that the current access token is valid
* sdk.auth.ensureValidToken();
*
* // Force the token to be refreshed, even if the current token in use has not yet expired.
* sdk.auth.ensureValidToken(true);
*/
service.ensureValidToken = async function(forceRefresh) {
var currentUser = service.getCurrentUser();
if (!currentUser) {
return Promise.reject('No user');
}
// Check if we're in cookie mode
const isCookieMode = storageAdapter.isCookieMode && storageAdapter.isCookieMode();
if (isCookieMode) {
// In cookie mode, we can't check token expiry from JavaScript
// Just attempt a refresh if forced, otherwise let the server handle it
if (forceRefresh) {
return await service.refreshAccessToken();
} else {
// Return a placeholder - server will handle token validation
return 'cookie_mode_token';
}
}
// localStorage mode - existing token validation logic
var { token } = currentUser;
if (!token) {
return Promise.reject('No token');
}
//Check our date
var now = new Date();
//Give us a bit of buffer so that the backend doesn't beat us to
//retiring the token
now.setSeconds(now.getSeconds() + tokenBufferSeconds);
var expires = new Date(token.expires);
if (forceRefresh) {
return await service.refreshAccessToken(token.refreshToken);
}
//If the token is still fresh
if (now < expires) {
return token;
} else {
return await service.refreshAccessToken(token.refreshToken);
}
}
///////////////////////////////////////////////////
const refreshContext = {};
service.refreshAccessToken = async function(refreshToken) {
//If there is already a request in progress
if (refreshContext.inflightRefreshRequest) {
return refreshContext.inflightRefreshRequest;
}
/////////////////////////////////////////////////////
// Check if we're in cookie mode
const isCookieMode = storageAdapter.isCookieMode && storageAdapter.isCookieMode();
//Create a refresh request
refreshContext.inflightRefreshRequest = new Promise(function(resolve, reject) {
let requestBody = {};
let requestOptions = {
bypassInterceptor: true,
withoutToken: true,
};
if (isCookieMode) {
// In cookie mode, refresh token is sent via httpOnly cookie
// No need to include it in the request body
requestOptions.withCredentials = true;
} else {
// In localStorage mode, send refresh token in request body
if (!refreshToken) {
refreshContext.inflightRefreshRequest = null;
return reject(new Error('No refresh token provided for localStorage mode'));
}
requestBody.refreshToken = refreshToken;
}
qik.api.post(`/user/refresh`, requestBody, requestOptions)
.then(function tokenRefreshComplete(res) {
//Update the user with any changes
//returned back from the refresh request
if (!res || !res.data) {
refreshContext.inflightRefreshRequest = null;
return reject(new Error('Invalid refresh response'));
}
//Update with our new session
service.set(res.data);
dispatch();
// Return appropriate response based on mode
if (isCookieMode) {
// In cookie mode, just indicate success - tokens are in cookies
resolve('cookie_token_refreshed');
} else {
// In localStorage mode, return the actual access token
const newToken = res.data.token?.accessToken;
if (newToken) {
resolve(newToken);
} else {
reject(new Error('No access token in refresh response'));
}
}
//Remove the inflight request
setTimeout(function() {
refreshContext.inflightRefreshRequest = null;
});
})
.catch(function(err) {
setTimeout(function() {
refreshContext.inflightRefreshRequest = null;
});
reject(err);
});
});
//Return the refresh request
return refreshContext.inflightRefreshRequest;
}
///////////////////////////////////////////////////
/**
* @alias auth.sync
* @description A useful function to sync the current user session with the server.
* @example
*
* // Makes request to the API and updates the current user session to match the response
* sdk.auth.sync();
*/
service.sync = function() {
return qik.api.get('/user')
.then(function(res) {
if (res.data) {
const currentUser = storageAdapter.getUser();
if (currentUser) {
const updatedUser = { ...currentUser };
if (updatedUser.session) {
Object.assign(updatedUser.session, res.data);
} else {
updatedUser.session = res.data;
}
service.set(updatedUser);
} else {
service.set(res.data);
}
} else {
service.set(null);
}
dispatch();
})
.catch(function(err) {
service.set(null);
dispatch();
});
}
/////////////////////////////////////////////////////
/**
* @alias auth.getCurrentUser
* @description Retrieves the current user session
* @example
*
* const me = sdk.auth.getCurrentUser();
*/
service.getCurrentUser = function() {
return storageAdapter.getUser();
}
/**
* @alias auth.getCurrentToken
* @description Retrieves the current access token. If the user is authenticated
* the response will be the current user's access token, otherwise will fall back to the
* applications token.
* @example
* const currentAccessToken = sdk.auth.getCurrentToken();
*/
service.getCurrentToken = function() {
// First try to get token from storage adapter
var accessToken = storageAdapter.getAccessToken();
if (accessToken) {
return accessToken;
}
// For localStorage mode, check user object for token
var user = service.getCurrentUser();
//User is not logged in
if (!user) {
//But there is an application token
if (qik.applicationToken) {
//use that instead
return qik.applicationToken;
}
//No token
return;
}
var { token } = user;
if (!token) {
return;
}
return token.accessToken;
}
/////////////////////////////////////////////////////
qik.api.interceptors.request.use(async 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;
//////////////////////////////
// Check if we're in cookie mode
const isCookieMode = storageAdapter.isCookieMode && storageAdapter.isCookieMode();
if (isCookieMode) {
// In cookie mode, browser automatically includes httpOnly cookies
// We don't need to manually add Authorization headers or handle token refresh
// Just ensure withCredentials is set for cross-origin requests
if (!originalRequest.withCredentials) {
originalRequest.withCredentials = true;
}
return originalRequest;
}
// localStorage mode - existing logic for manual token management
var userDetails = service.getCurrentUser();
var accessToken;
var refreshToken;
var expiryDate;
if (userDetails) {
var { token } = userDetails;
if (token) {
accessToken = token.accessToken;
refreshToken = token.refreshToken;
expiryDate = token.expires;
}
}
//////////////////////////////
//If there is a user token
if (accessToken) {
//Set the token of the request as the user's access token
originalRequest.headers['Authorization'] = `Bearer ${accessToken}`;
} else {
//Return the original request without a token
return originalRequest;
}
/////////////////////////////////////////////////////
//If no refresh token
if (!refreshToken) {
//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 so that the backend doesn't beat us to
//retiring the token
now.setSeconds(now.getSeconds() + tokenBufferSeconds);
/////////////////////////////////////////////////////
var expires = new Date(expiryDate);
//If the token is still fresh
if (now < expires) {
//Return the original request
return originalRequest;
}
/////////////////////////////////////////////////////
return new Promise(async function(resolve, reject) {
//Refresh the token
await service.refreshAccessToken(refreshToken)
.then(function(newToken) {
//Update the original request with our new token
originalRequest.headers['Authorization'] = `Bearer ${newToken}`;
//And continue onward
return resolve(originalRequest);
})
.catch(function(err) {
return reject(err);
});
});
},
function(error) {
return Promise.reject(error);
})
/////////////////////////////////////////////////////
qik.api.interceptors.response.use(function(response) {
return response;
}, function(err) {
//////////////////////////////
//Get the response status
var status = (err && err.response && err.response.status) || err.status;
switch (status) {
case 401:
service.logout();
break;
default:
//Some other error
break;
}
/////////////////////////////////////////////////////
return Promise.reject(err);
})
return service;
}
export default QikAuth;
</code></pre>
</article>
</section>
</div>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.6.11</a> on Mon Jul 13 2026 18:49:48 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>