UNPKG

plug-login

Version:
374 lines (311 loc) 8.89 kB
import fetch from 'node-fetch'; /*! * cookie * Copyright(c) 2012-2014 Roman Shtylman * Copyright(c) 2015 Douglas Christopher Wilson * MIT Licensed */ /** * Module exports. * @public */ var parse_1 = parse; var serialize_1 = serialize; /** * Module variables. * @private */ var decode = decodeURIComponent; var encode = encodeURIComponent; var pairSplitRegExp = /; */; /** * RegExp to match field-content in RFC 7230 sec 3.2 * * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] * field-vchar = VCHAR / obs-text * obs-text = %x80-FF */ var fieldContentRegExp = /^[\u0009\u0020-\u007e\u0080-\u00ff]+$/; /** * Parse a cookie header. * * Parse the given cookie header string into an object * The object has the various cookies as keys(names) => values * * @param {string} str * @param {object} [options] * @return {object} * @public */ function parse(str, options) { if (typeof str !== 'string') { throw new TypeError('argument str must be a string'); } var obj = {}; var opt = options || {}; var pairs = str.split(pairSplitRegExp); var dec = opt.decode || decode; for (var i = 0; i < pairs.length; i++) { var pair = pairs[i]; var eq_idx = pair.indexOf('='); // skip things that don't look like key=value if (eq_idx < 0) { continue; } var key = pair.substr(0, eq_idx).trim(); var val = pair.substr(++eq_idx, pair.length).trim(); // quoted values if ('"' == val[0]) { val = val.slice(1, -1); } // only assign once if (undefined == obj[key]) { obj[key] = tryDecode(val, dec); } } return obj; } /** * Serialize data into a cookie header. * * Serialize the a name value pair into a cookie string suitable for * http headers. An optional options object specified cookie parameters. * * serialize('foo', 'bar', { httpOnly: true }) * => "foo=bar; httpOnly" * * @param {string} name * @param {string} val * @param {object} [options] * @return {string} * @public */ function serialize(name, val, options) { var opt = options || {}; var enc = opt.encode || encode; if (typeof enc !== 'function') { throw new TypeError('option encode is invalid'); } if (!fieldContentRegExp.test(name)) { throw new TypeError('argument name is invalid'); } var value = enc(val); if (value && !fieldContentRegExp.test(value)) { throw new TypeError('argument val is invalid'); } var str = name + '=' + value; if (null != opt.maxAge) { var maxAge = opt.maxAge - 0; if (isNaN(maxAge)) throw new Error('maxAge should be a Number'); str += '; Max-Age=' + Math.floor(maxAge); } if (opt.domain) { if (!fieldContentRegExp.test(opt.domain)) { throw new TypeError('option domain is invalid'); } str += '; Domain=' + opt.domain; } if (opt.path) { if (!fieldContentRegExp.test(opt.path)) { throw new TypeError('option path is invalid'); } str += '; Path=' + opt.path; } if (opt.expires) { if (typeof opt.expires.toUTCString !== 'function') { throw new TypeError('option expires is invalid'); } str += '; Expires=' + opt.expires.toUTCString(); } if (opt.httpOnly) { str += '; HttpOnly'; } if (opt.secure) { str += '; Secure'; } if (opt.sameSite) { var sameSite = typeof opt.sameSite === 'string' ? opt.sameSite.toLowerCase() : opt.sameSite; switch (sameSite) { case true: str += '; SameSite=Strict'; break; case 'lax': str += '; SameSite=Lax'; break; case 'strict': str += '; SameSite=Strict'; break; default: throw new TypeError('option sameSite is invalid'); } } return str; } /** * Try decoding a string using a decoding function. * * @param {string} str * @param {function} decode * @private */ function tryDecode(str, decode) { try { return decode(str); } catch (e) { return str; } } var promiseProps = function(obj) { var awaitables = []; var keys = Object.keys(obj); for (var i = 0; i < keys.length; i++) { var key = keys[i]; var a$ = obj[key]; awaitables.push(a$); } return Promise.all(awaitables).then(function (results) { var byName = {}; for (var i = 0; i < keys.length; i++) { var key = keys[i]; byName[key] = results[i]; } return byName; }); }; var DEFAULT_HOST = 'https://plug.dj'; // Enhance a `fetch` options object to use a JSON body when sending data. function json (opts) { return Object.assign({}, opts, {headers: Object.assign({}, opts.headers, {'content-type': 'application/json'}), body: JSON.stringify(opts.body)}) } // Create an HTTP response error. function error (response, status, message) { var e = new Error(`${status}: ${message}`); e.response = response; e.status = status; return e } // Get the JSON response from the plug.dj API, throwing if it is an error response. function getJSON (response) { return response.json().then((body) => { if (body.status !== 'ok') { throw error(response, body.status, body.data[0]) } return body }) } // Extract the session cookie value from an array of set-cookie headers. function getSessionCookie (headers) { if (!headers) { return } var cookie$$1 = parse_1(headers); if (cookie$$1.session) { return cookie$$1.session } } // Build a "Cookie:" header value with a session cookie. function makeSessionCookieHeader (session) { return serialize_1('session', session, { // Should not URL-encode: plug.dj only accepts unencoded "|" characters. encode: value => value }) } function addCookieToHeaders (opts, session) { if (!opts.headers || !opts.headers.cookie) { opts.headers = Object.assign(opts.headers || {}, { cookie: makeSessionCookieHeader(session) }); } return opts } // Get a CSRF token and session cookie for logging into plug.dj from their main // page. Without the CSRF token, login requests will be rejected. function getCsrf (opts) { // for testing if (opts._simulateMaintenance) { return new Promise((resolve, reject) => { setTimeout(() => { reject(new Error('Could not find CSRF token')); }, 300); }) } return fetch(`${opts.host}/_/mobile/init`, json(opts)) .then((response) => promiseProps({ csrf: getJSON(response).then((body) => body.data[0].c), session: getSessionCookie(response.headers.get('set-cookie')) })) } // Log in to plug.dj with an email address and password. // `opts` must contain headers with a session cookie. function doLogin (opts, csrf, email, password) { return fetch(`${opts.host}/_/auth/login`, json(Object.assign({}, opts, {method: 'post', body: { csrf, email, password }}))).then((response) => promiseProps({ session: getSessionCookie(response.headers.get('set-cookie')), body: getJSON(response) })) } function getAuthToken (opts) { opts = normalizeOptions(opts); return fetch(`${opts.host}/_/auth/token`, json(opts)) .then((response) => getJSON(response)) .then((body) => body.data[0]) } function normalizeOptions (maybeOpts) { if ( maybeOpts === void 0 ) maybeOpts = {}; var opts = Object.assign({}, maybeOpts); if (!opts.host) { opts.host = DEFAULT_HOST; } else { // Trim slashes opts.host = opts.host.replace(/\/+$/, ''); } return opts } function guest (opts) { opts = normalizeOptions(opts); return fetch(`${opts.host}/plug-socket-test`, opts).then((response) => { if (!response.ok) { throw error(response, response.status, response.statusText) } return response.text().then((body) => { if (/<title>maintenance mode/.test(body)) { throw error(response, 'maintenanceMode', 'The site is in maintenance mode') } var session = getSessionCookie(response.headers.get('set-cookie')); opts = addCookieToHeaders(opts, session); return promiseProps({ session, cookie: makeSessionCookieHeader(session), token: opts.authToken ? getAuthToken(opts) : null }) }) }) } function user (email, password, opts) { opts = normalizeOptions(opts); return getCsrf(opts) .then((ref) => { var csrf = ref.csrf; var session = ref.session; opts = addCookieToHeaders(opts, session); return doLogin(opts, csrf, email, password) }) .then((result) => promiseProps({ session: result.session, cookie: makeSessionCookieHeader(result.session), token: opts.authToken ? getAuthToken(opts) : null })) } function login (email, password, opts) { if (typeof email === 'string') { return user(email, password, opts) } else { return guest(email) } } login.user = user; login.guest = guest; login.getAuthToken = getAuthToken; export default login; //# sourceMappingURL=index.es.js.map