UNPKG

pryv

Version:

Pryv JavaScript library

270 lines (234 loc) 7.76 kB
/** * @license * [BSD-3-Clause](https://github.com/pryv/lib-js/blob/master/LICENSE) */ const utils = require('../utils'); const AuthStates = require('./AuthStates'); const Messages = require('./LoginMessages'); /** * @private */ class AuthController { /** * * @param {*} settings * @param {*} service */ constructor (settings, service, loginButton) { this.settings = settings; validateSettings.call(this, settings); this.stateChangeListeners = []; if (this.settings.onStateChange) { this.stateChangeListeners.push(this.settings.onStateChange); } this.service = service; // probably remove this.languageCode = this.settings.authRequest.languageCode || 'en'; this.messages = Messages(this.languageCode); this.loginButton = loginButton; function validateSettings (settings) { if (!settings) { throw new Error('settings cannot be null'); } // -- settings if (!settings.authRequest) { throw new Error('Missing settings.authRequest'); } // -- Extract returnURL settings.authRequest.returnURL = this.getReturnURL(settings.authRequest.returnURL); if (!settings.authRequest.requestingAppId) { throw new Error('Missing settings.authRequest.requestingAppId'); } if (!settings.authRequest.requestedPermissions) { throw new Error('Missing settings.authRequest.requestedPermissions'); } } } /** * async function to call right after instanciating object * * @returns {Service} */ async init () { this.serviceInfo = this.service.infoSync(); this.state = { status: AuthStates.LOADING }; this.assets = await loadAssets(this); const loginButton = this.loginButton; // initialize human interaction interface if (loginButton != null) { this.stateChangeListeners.push(loginButton.onStateChange.bind(loginButton)); // autologin needs cookies/storage implemented in human interaction interface await checkAutoLogin(this); } // if auto login is not prompted if (this.state.status !== AuthStates.AUTHORIZED) { this.state = { status: AuthStates.INITIALIZED, serviceInfo: this.serviceInfo }; } if (loginButton != null && loginButton.finishAuthProcessAfterRedirection != null) { await loginButton.finishAuthProcessAfterRedirection(this); } return this.service; } /** * Stops poll for auth request */ stopAuthRequest (msg) { this.state = { status: AuthStates.ERROR, message: msg }; } /** * Triggered when button is pressed */ async handleClick () { if (isAuthorized.call(this)) { this.state = { status: AuthStates.SIGNOUT }; } else if (isInitialized.call(this)) { this.startAuthRequest(); } else if (isNeedSignIn.call(this)) { // reopen popup (HACK for now: set to private property to avoid self-assignment) this.state = this._state; } else { console.log('Unhandled action in "handleClick()" for status:', this.state.status); } function isAuthorized () { return this.state.status === AuthStates.AUTHORIZED; } function isInitialized () { return this.state.status === AuthStates.INITIALIZED; } function isNeedSignIn () { return this.state.status === AuthStates.NEED_SIGNIN; } } /** * Used only to retrieve returnUrl in browser environments * * @param {*} returnURL * @param {*} windowLocationForTest * @param {*} navigatorForTests */ getReturnURL ( returnURL, windowLocationForTest, navigatorForTests ) { const RETURN_URL_AUTO = 'auto'; returnURL = returnURL || RETURN_URL_AUTO + '#'; // check the trailer const trailer = returnURL.slice(-1); if ('#&?'.indexOf(trailer) < 0) { throw new Error('Pryv access: Last character of --returnURL setting-- is not ' + '"?", "&" or "#": ' + returnURL); } // auto mode for desktop if (returnUrlIsAuto(returnURL) && !utils.browserIsMobileOrTablet(navigatorForTests)) { return false; // auto mode for mobile or self } else if ((returnUrlIsAuto(returnURL) && utils.browserIsMobileOrTablet(navigatorForTests)) || returnURL.indexOf('self') === 0) { // set self as return url? // eventually clean-up current url from previous pryv returnURL const locationHref = windowLocationForTest || window.location.href; returnURL = locationHref + returnURL.substring(4); } return utils.cleanURLFromPrYvParams(returnURL); function returnUrlIsAuto (returnURL) { return returnURL.indexOf(RETURN_URL_AUTO) === 0; } } async startAuthRequest () { this.state = await postAccess.call(this); await doPolling.call(this); async function postAccess () { try { const res = await utils.superagent .post(this.serviceInfo.access) .send(this.settings.authRequest); return res.body; } catch (e) { this.state = { status: AuthStates.ERROR, message: 'Requesting access', error: e }; throw e; // forward error } } async function doPolling () { if (this.state.status !== AuthStates.NEED_SIGNIN) { return; } const pollResponse = await pollAccess(this.state.poll); if (pollResponse.status === AuthStates.NEED_SIGNIN) { setTimeout(await doPolling.bind(this), this.state.poll_rate_ms); } else { this.state = pollResponse; } async function pollAccess (pollUrl) { try { const res = await utils.superagent.get(pollUrl); return res.body; } catch (e) { if (e.response && e.response.status === 403 && e.response.body && e.response.body.status === 'REFUSED') { return { status: AuthStates.INITIALIZED }; } else { return { status: AuthStates.ERROR, message: 'Error while polling for auth request', error: e }; } } } } } // -------------- state listeners --------------------- set state (newState) { // retro-compatibility for lib-js < 2.0.9 newState.id = newState.status; this._state = newState; this.stateChangeListeners.forEach((listener) => { try { listener(this.state); } catch (e) { console.log('Error during set state ()', e); } }); } get state () { return this._state; } } // ----------- private methods ------------- async function checkAutoLogin (authController) { const loginButton = authController.loginButton; if (loginButton == null) { return; } const storedCredentials = await loginButton.getAuthorizationData(); if (storedCredentials != null) { authController.state = Object.assign({}, { status: AuthStates.AUTHORIZED }, storedCredentials); } } // ------------------ ACTIONS ----------- // async function loadAssets (authController) { let loadedAssets = {}; try { loadedAssets = await authController.service.assets(); if (typeof location !== 'undefined') { await loadedAssets.loginButtonLoadCSS(); const thisMessages = await loadedAssets.loginButtonGetMessages(); if (thisMessages.LOADING) { authController.messages = Messages(authController.languageCode, thisMessages); } else { console.log('WARNING Messages cannot be loaded using defaults: ', thisMessages); } } } catch (e) { authController.state = { status: AuthStates.ERROR, message: 'Cannot fetch button visuals', error: e }; throw e; // forward error } return loadedAssets; } module.exports = AuthController;