dl
Version:
DreamLab Libs
652 lines (619 loc) • 19.8 kB
JavaScript
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;