UNPKG

dl

Version:

DreamLab Libs

652 lines (619 loc) 19.8 kB
var Event = require('core').event.Event, ErrorEvent = require('core').event.ErrorEvent, Loader = require('core').http.Loader, BinaryData = require('core').data.BinaryData, JsonRpcResponse = require('core').jsonrpc.JsonRpcResponse, OpalRequest = require('../opal/OpalRequest.js').OpalRequest, OpalLoader = require('../opal/OpalLoader.js').OpalLoader, UidProfile = require('./UidProfile.js').UidProfile; /** * User Id sessions API * * @param {core.http.Request} request HTTP request * @param {core.http.Response} response HTTP response * @param {String} [cookieParams.name="onet_uid"] Cookie name * @param {String} [cookieParams.path="/"] Path on the domain where the cookie will work * @param {String} [cookieParams.domain] Cookie domain * @param {Boolean} [cookieParams.secure=false] If true cookie will only be sent over secure connections * @param {Boolean} [cookieParams.httponly=true] If set to true then it will be sent the httponly flag * when setting the cookie * @constructor */ var UidManager = function (request, response, cookieParams) { this._request = request; this._response = response; this._cookieParams = { name: 'onet_uid', path: '/', domain: null, secure: false, httponly: true }; if (cookieParams) { Object.keys(this._cookieParams).forEach(function (name) { if (cookieParams.hasOwnProperty(name)) { this._cookieParams[name] = cookieParams[name]; } }, this); } this._accessToken = this._request.getCookie(this._cookieParams.name); this._profile = null; if (!this.hasOwnProperty('_portalId')) { this._portalId = 'ONET'; } if (!this.hasOwnProperty('_clientId')) { this._clientId = this._request.getHost() || this._request.getConnectionHost(); } }; /** * Sets portal identifier. * Initial value: "ONET" * * @param {String} portalId Portal identifier * @type UidManager */ UidManager.prototype.setPortalId = function (portalId) { this._portalId = portalId; return this; }; /** * Sets client identifier. * Initial value: current domain name * * @param {String} clientId Client identifier * @type UidManager */ UidManager.prototype.setClientId = function (clientId) { this._clientId = clientId; return this; }; /** * Returns user profile object. * If necessary performs redirection to OAuth 2.0 Authorization Endpoint. * * @param {String} [redirectURI] OAuth 2.0 redirect_uri parameter (absolute or relative URL address) * @param {String} [state] OAuth 2.0 state parameter * @param {Function} callback Callback function with parameters: * {core.event.ErrorEvent} - error, * {UidProfile} - user profile data * @type UidManager * @throws {URIError} UidManager.Exception.INVALID_URL * @throws {Error} UidManager.Exception.UNAUTHORIZED */ UidManager.prototype.getProfile = function (redirectURI, state, callback) { var that = this; this._disableCaching(); if (arguments.length < 2) { callback = arguments[0]; state = undefined; redirectURI = undefined; } else if (arguments.length < 3) { callback = arguments[1]; state = undefined; } if (redirectURI) { redirectURI = this._getRedirectURI(redirectURI); } if (this._profile) { callback(null, this._profile); } else { if (redirectURI) { redirectURI = UidManager.AUTHORIZATION_ENDPOINT + (UidManager.AUTHORIZATION_ENDPOINT.indexOf('?') < 0 ? '?' : '&') + 'response_type=code' + '&client_id=' + encodeURIComponent(this._clientId) + '&redirect_uri=' + encodeURIComponent(redirectURI); if (state) { redirectURI += '&state=' + encodeURIComponent(state); } } if (!this._accessToken) { if (redirectURI) { this._redirect(redirectURI); } throw new Error(UidManager.Exception.UNAUTHORIZED); } this._load( { url: UidManager.TOKEN_ENDPOINT, method: 'get_profile', params: { portal_id: this._portalId, client_id: this._clientId } }, function (err, data) { if (err) { if (err.code == UidManager.Error.INVALID_TOKEN) { that._accessToken = null; that._setCookie(); if (redirectURI) { that._redirect(redirectURI); } } callback(err); return; } that._profile = new UidProfile(data); callback(null, that._profile); } ); } return this; }; /** * Exchanges given auth code to access token. * * @param {Function} callback Callback function with parameters: * {core.event.ErrorEvent} - error, * {Object} - OAuth 2.0 Access Token Response * @type UidManager * @throws {ReferenceError} UidManager.Exception.INVALID_ACCESS_TOKEN_REQUEST */ UidManager.prototype.oauthExchangeToken = function (callback) { var code = this._request.getQueryParam('code'), that = this; this._disableCaching(); if (code === null) { throw new ReferenceError(UidManager.Exception.INVALID_ACCESS_TOKEN_REQUEST); } this._load( { url: UidManager.TOKEN_ENDPOINT, method: 'oauth_exchange_token', params: { portal_id: this._portalId, grant_type: 'authorization_code', client_id: this._clientId, code: decodeURIComponent(code) } }, function (err, data) { if (err) { callback(err); return; } that._accessToken = data.access_token; that._setCookie(data.expires_in); callback(null, data); } ); return this; }; /** * Signs user out. * * @param {Function} callback Callback function with parameters: * {core.event.ErrorEvent} - error * @type UidManager * @throws {Error} UidManager.Exception.UNAUTHORIZED */ UidManager.prototype.logout = function (callback) { var that = this; this._disableCaching(); if (!this._accessToken) { throw new Error(UidManager.Exception.UNAUTHORIZED); } this._load( { url: UidManager.TOKEN_ENDPOINT, method: 'logout', params: { portal_id: this._portalId, client_id: this._clientId } }, function (err, data) { if (err) { callback(err); return; } that._accessToken = null; that._setCookie(); callback(null, data); } ); return this; }; /** * Identifies user by e-mail address. * * @param {String} email E-mail address * @param {String} redirectURI OAuth 2.0 redirect_uri parameter (absolute or relative URL address) * @param {Function} callback Callback function with parameters: * {core.event.ErrorEvent} - error * @type UidManager * @throws {Error} UidManager.Exception.INVALID_EMAIL * @throws {URIError} UidManager.Exception.INVALID_URL */ UidManager.prototype.identifyByEmail = function (email, redirectURI, callback) { if (!UidManager.EMAIL_PATTERN.test(email)) { throw new Error(UidManager.Exception.INVALID_EMAIL); } redirectURI = this._getRedirectURI(redirectURI); this._load( { url: UidManager.TOKEN_ENDPOINT, method: 'identify_by_email', params: { portal_id: this._portalId, email: email, client_id: this._clientId, redirect_uri: redirectURI } }, callback ); return this; }; /** * Gets facebook.com Login Dialog URL. * * @param {String} redirectURI OAuth 2.0 redirect_uri parameter (absolute or relative URL address) * @param {String} [state] OAuth 2.0 state parameter * @param {String} [params.display="page"] Set to "popup" if you want to open Login Dialog in popup window * @param {String} [params.auth_type] Requested authentication features as a comma-separated list: * "rerequest" - re-asking for Declined Permissions (always added automatically), * "https" - checks for the presence of a secure Facebook session and asks for re-authentication if it is not present, * "reauthenticate" - asks the person to re-authenticate unconditionally * @type String * @throws {URIError} UidManager.Exception.INVALID_URL */ UidManager.prototype.getFacebookLoginURL = function (redirectURI, state, params) { var url = UidManager.FACEBOOK_AUTHORIZATION_ENDPOINT + (UidManager.FACEBOOK_AUTHORIZATION_ENDPOINT.indexOf('?') < 0 ? '?' : '&'); redirectURI = this._getRedirectURI(redirectURI); if (!params) { params = { auth_type: 'rerequest' }; } else if (!params.hasOwnProperty('auth_type')) { params.auth_type = 'rerequest'; } else if (!/(^|,\s*)rerequest(\s*,|$)/.test(params.auth_type)) { params.auth_type += ',rerequest'; } Object.keys(params).forEach(function (name) { url += encodeURIComponent(name) + '=' + encodeURIComponent(params[name]) + '&'; }); url += 'client_id=' + encodeURIComponent(UidManager.FACEBOOK_CLIENT_ID) + '&redirect_uri=' + encodeURIComponent( UidManager.FACEBOOK_REDIRECT_URI + (UidManager.FACEBOOK_REDIRECT_URI.indexOf('?') < 0 ? '?' : '&') + 'client_id=' + encodeURIComponent(this._clientId) + '&response_type=code' + '&redirect_uri=' + encodeURIComponent(redirectURI) + (state ? '&state=' + encodeURIComponent(state) : '') ) + '&scope=email' + '&response_type=code'; return url; }; /** * Disables HTTP caching. * * @type UidManager * @private */ UidManager.prototype._disableCaching = function () { var date = new Date(); this._response.setHeader('last-modified', date.toUTCString(), true); date.setDate(date.getDate() - 365); this._response.setHeader('expires', date.toUTCString(), true); this._response.setHeader('cache-control', 'no-store, no-cache, must-revalidate', true); this._response.setHeader('cache-control', 'post-check=0, pre-check=0', false); this._response.setHeader('pragma', 'no-cache', true); this._response.removeHeader('x-onet-squid'); return this; }; /** * Sets session cookie. * * @param {Number} [lifetime=0] Lifetime of the cookie, defined in seconds * @type UidManager * @private */ UidManager.prototype._setCookie = function (lifetime) { var cookie, domain; if (!lifetime) { lifetime = 0; } if ( this._accessToken || (!this._accessToken || lifetime < 0) && this._request.getCookie(this._cookieParams.name) !== null ) { cookie = this._cookieParams.name + '='; if (this._accessToken) { cookie += encodeURIComponent(this._accessToken); if (lifetime) { cookie += '; expires=' + new Date(new Date().getTime() + lifetime * 1000).toUTCString(); } } else { cookie += '; expires=' + new Date(new Date().getTime() - 1000000).toUTCString(); } if (this._cookieParams.path) { cookie += '; path=' + this._cookieParams.path; } if (this._cookieParams.domain) { cookie += '; domain=' + this._cookieParams.domain; } else if (this._cookieParams.secure || this._cookieParams.httponly) { domain = this._request.getHost() || this._request.getConnectionHost(); if (domain) { cookie += '; domain=' + domain; } } if (this._cookieParams.secure) { cookie += '; secure'; } if (this._cookieParams.httponly) { cookie += '; httponly'; } this._response.headers.setHeader( 'p3p', 'CP="ALL DSP COR IVD IVA PSD PSA TEL TAI CUS ADM CUR CON SAM OUR IND"', true ); this._response.setCookie(cookie); } return this; }; /** * Normalizes URL address to OAuth 2.0 redirect_uri parameter. * Makes relative URL absolute. * * @param {String} url Absolute or relative URL address * @type String * @throws {URIError} UidManager.Exception.INVALID_URL * @private */ UidManager.prototype._getRedirectURI = function (url) { var re = /(^[a-z]+:(\/\/)?[^\/?#]+)\/.*/i; if (/^\/\//.test(url)) { url = this._request.getProto() + url; } else if (/^\//.test(url)) { url = this._request.getUrl().replace(re, '$1') + url; } else if (/^\?/.test(url)) { url = this._request.getUrl().replace(re, '$1') + this._request.getPath() + url; } else if (/^#/.test(url)) { url = this._request.getUrl().replace(re, '$1') + this._request.getPath() + this._request.getQueryString() + url; } else if (!/^[a-z]+:/i.test(url)) { url = this._request.getUrl().replace(re, '$1') + this._request.getPath().replace(/\/[^\/]+$/, '/') + url; } if (!UidManager.REDIRECT_URI_PATTERN.test(url)) { throw URIError(UidManager.Exception.INVALID_URL); } return url; }; /** * Makes redirection. * * @param {String} url Location * @param {Number} [status=302] HTTP status code * @type UidManager * @throws URIError UidManager.Exception.INVALID_URL * @private */ UidManager.prototype._redirect = function (url, status) { if (!UidManager.REDIRECT_URI_PATTERN.test(url)) { throw URIError(UidManager.Exception.INVALID_URL); } this._response.setHeader('location', url, true); this._setResponse(status || 302); return this; }; /** * Loads data from OPAL. * * @param {Object} data OpalRequest data * @param {Function} callback Callback function * @type UidManager * @private */ UidManager.prototype._load = function (data, callback) { var request = new OpalRequest(data), headers = [ 'accept', 'accept-charset', 'accept-encoding', 'accept-language', 'cookie', 'referer', 'user-agent', 'x-forwarded-for' ], loader; headers.forEach(function (name) { var value = this._request.getHeader(name); if (value !== null) { request.setHeader(name, value, true); } }, this); if (this._accessToken) { request.setHeader('authorization', 'Bearer ' + this._accessToken, true); } if (UidManager.OPAL_CONNECTION_HOST) { request.setConnectionHost(UidManager.OPAL_CONNECTION_HOST); } loader = new OpalLoader(request).setTimeout(UidManager.TIMEOUT); loader.addEventListener(OpalLoader.Event.JSON_RESPONSE, function (e) { var body = e.data.getBody(), error; if (body.isError()) { error = body.getError(); loader.dispatchEvent(new ErrorEvent( Loader.Event.ERROR, error.getData(), error.getCode(), error.getMessage() )); return; } callback(null, body.getResult()); }); loader.addEventListener(Loader.Event.ERROR, function (e) { var severity = this._isServerError(e.code) ? 'ERROR' : 'WARNING', referer = this._request.getHeader('referer'); console[severity == 'ERROR' ? 'error': 'warn']( severity + ' Uid: ' + this._accessToken + ' ' + JSON.stringify(e.toJson()) + (process.env.OPAL_IDENTITY ? ' application=' + process.env.OPAL_IDENTITY + ';' : '') + ' url=' + this._request.getUrl() + (referer ? '; referer=' + referer : '') ); callback(e); }, this); loader.addEventListener(OpalLoader.Event.HTTP_RESPONSE, function (e) { var json = e.data.getBody().getBody(), body; try { json = json.toUTF8String(); body = new JsonRpcResponse(JSON.parse(json)); } catch (err) { loader.dispatchEvent(new ErrorEvent(Loader.Event.ERROR, { json: json.toString(), error: (err.message || err) }, -4, 'JSON parse error' )); return; } e.data.setBody(body); loader.dispatchEvent(new Event(OpalLoader.Event.JSON_RESPONSE, e.data)); }); loader.load(); return this; }; /** * Checks whether service is temporarily unavilable. * * @param {Number} code Code number * @type {Boolean} * @private */ UidManager.prototype._isServerError = function (code) { return code >= -32099 && code <= -32000 || [-5, -3, -1].indexOf(code) >= 0; }; /** * Sets HTTP response. * * @param {Number} statusCode HTTP status code * @param {String} [body=""] HTTP body * @type UidManager * @private */ UidManager.prototype._setResponse = function (statusCode, body) { this._response.setStatusCode(statusCode); this._response.setHeader('content-length', body ? body.length : 0, true); this._response.setBody(new BinaryData(body || '', BinaryData.Encoding.TEXT, BinaryData.CharacterEncoding.UTF8)); return this; }; UidManager.Exception = {}; /** * Invalid URL format * * @static * @constant */ UidManager.Exception.INVALID_URL = 'Invalid URL format'; /** * Invalid OAuth 2.0 Access Token Request * * @static * @constant */ UidManager.Exception.INVALID_ACCESS_TOKEN_REQUEST = 'Invalid OAuth 2.0 Access Token Request'; /** * Invalid e-mail format * * @static * @constant */ UidManager.Exception.INVALID_EMAIL = 'Invalid e-mail format'; /** * Unauthorized * * @static * @constant */ UidManager.Exception.UNAUTHORIZED = 'Unauthorized'; UidManager.Error = {}; /** * Invalid code * * @static * @constant */ UidManager.Error.INVALID_CODE = -31000; /** * Invalid token * * @static * @constant */ UidManager.Error.INVALID_TOKEN = -31001; /** * Valid e-mail address RegExp * * @static * @constant */ UidManager.EMAIL_PATTERN = /^[^@]+@([^@.]+\.)+[^@.]{2,}$/; /** * Valid redirect_uri RegExp * * @static * @constant */ UidManager.REDIRECT_URI_PATTERN = /^https?:\/\/(([^.\/?&#\r\n]+\.)*(grupaonet|onet|vod)\.pl|([^.\/?&#\r\n]+\.)+onet)(:\d+)?([\/?&#][^\r\n]*|$)/i; /** * OAuth 2.0 Token Endpoint * * @static * @constant */ UidManager.TOKEN_ENDPOINT = 'idmanager.authorisation.onetapi.pl'; /** * OAuth 2.0 Authorization Endpoint * * @static */ UidManager.AUTHORIZATION_ENDPOINT = 'https://uid.grupaonet.pl/oauth_generate_code.html'; /** * OAuth 2.0 facebook.com redirect_uri * * @static */ UidManager.FACEBOOK_REDIRECT_URI = 'https://uid.grupaonet.pl/authenticate_by_facebook.html'; /** * OAuth 2.0 facebook.com client_id * * @static * @constant */ UidManager.FACEBOOK_CLIENT_ID = '600559720002758'; /** * OAuth 2.0 facebook.com Authorization Endpoint * * @static * @constant */ UidManager.FACEBOOK_AUTHORIZATION_ENDPOINT = 'https://www.facebook.com/dialog/oauth'; /** * OPAL connection host * Leave it empty to apply default behavior (recommended). * * @static */ UidManager.OPAL_CONNECTION_HOST = null; /** * {core.http.Loader} timeout [ms] * * @static */ UidManager.TIMEOUT = 900; exports.UidManager = UidManager;