diff --git a/index.js b/index.js new file mode 100644 index 0000000..d28583b --- /dev/null +++ b/index.js @@ -0,0 +1,152 @@ + + +var _ = require("underscore"); +var Cookie = function(options){ + this.path = "/"; + this.maxAge = null; + this.httpOnly = true; + //if (options) _.merge(this, options); +} + +var encode = encodeURIComponent; +var decode = decodeURIComponent; + + +Cookie.prototype.serialize = function(key, val){ + + var serialize = function(name, val, opt){ + opt = opt || {}; + var enc = opt.encode || encode; + var pairs = [name + '=' + enc(val)]; + + if (opt.maxAge) pairs.push('Max-Age=' + opt.maxAge); + if (opt.domain) pairs.push('Domain=' + opt.domain); + if (opt.path) pairs.push('Path=' + opt.path); + if (opt.expires) pairs.push('Expires=' + opt.expires.toUTCString()); + if (opt.httpOnly) pairs.push('HttpOnly'); + if (opt.secure) pairs.push('Secure'); + + return pairs.join('; '); +}; + return serialize(key, val, this); +} + +var setCurrentParseUser = function(userSession){ + if (!userSession || !userSession.id || !userSession.sessionToken) { + // Force cleanup if invalid + Parse.User.logOut(); + return; + } + Parse.User._currentUser = Parse.Object._create("_User"); + Parse.User._currentUser._isCurrentUser = true; + Parse.User._currentUser.id = userSession.id; + Parse.User._currentUser._sessionToken = userSession.sessionToken; + Parse.User._currentUser._synchronizeAllAuthData(); + Parse.User._currentUser._refreshCache(); + Parse.User._currentUser._opSetQueue = [{}]; +} + +var getCurrentParseUserSession = function(){ + var u = Parse.User.current(); + if (!u) { + return; + } + return {id:u.id, sessionToken: u.getSessionToken()}; +} + + +module.exports = function (options) { + options = options || {}; + var key = options.key || 'parse.sess'; + var cookieOptions = options.cookie || {}; + var forcedCookieOptions = { httpOnly: true, secure: true }; + // forcedCookieOptions will overwrite same keys in cookieOptions + cookieOptions = _.defaults(forcedCookieOptions, cookieOptions); + + return function parseExpressCookieSession(req, res, next) { + + //////////////////////// + // Request path logic + + // Expect express.cookieParser to set req.secret before this middleware. + var signatureSecret = req.secret; + if (Parse._.isEmpty(signatureSecret)) { + throw new Error('express.cookieParser middleware must be included' + + 'before this, and initialized with a signing secret'); + } + var cookie = new Cookie(cookieOptions); + + // Ignore if cookie path does not match. + if (req.originalUrl.indexOf(cookie.path) !== 0) { + return next(); + } + + var encryptionSecret = Parse.Cloud._expressCookieEncryptionKey(); + + // Decrypt and parse the signed cookie. + // Assume express.cookieParser already verified the signature and put the + // cookie's contents at req.signedCookies[key]. + var reqParseUserSession; + var reqCookieJson; // Used later to determine whether to set response cookie. + var reqCookieBody = req.cookies[key]; + if (!Parse._.isEmpty(reqCookieBody)) { + try { + + reqCookieJson = JSON.parse(reqCookieBody); + //reqParseUserSession = JSON.parse(reqCookieBody);//decrypt(reqCookieBody, encryptionSecret); + if (reqCookieJson && !reqCookieJson.id || !reqCookieJson.sessionToken) { + throw "Invalid session"; + } + /*if (!Parse._.isEmpty(reqCookieJson)) { + reqParseUserSession = utils.parseJSONCookie(reqCookieJson); + }*/ + } catch (e) { + // Catch any decryption and JSON parsing exceptions. + console.warn("Invalid Parse session cookie"); + } + } + setCurrentParseUser(reqCookieJson); + //////////////////////// + // Response path logic + res.on('header', function() { + var resParseUserSession = getCurrentParseUserSession(); + // If user is logged out, clear cookie. + if (Parse._.isUndefined(resParseUserSession)) { + cookie.expires = new Date(0); + res.setHeader('Set-Cookie', cookie.serialize(key, '')); + return; + } + + // Only send Parse session cookies via https connection. + /*if (!req.secure) { + console.warn('Skipped setting Parse session cookie because request is not https'); + return; + }*/ + + // Serialize. Prefix is Connect's convention for JSON in cookie. + resCookieJson = resParseUserSession; + // Skip Set-Cookie if cookie is same as request. + if (reqCookieJson !== resCookieJson) { + var val = JSON.stringify(resParseUserSession); + val = cookie.serialize(key, val); + res.setHeader('Set-Cookie', val); + // Encrypt and sign. Prefix is Connect's convention for signed cookie. + //var val = encrypt(resCookieJson, encryptionSecret); + //val = 's:' + signature.sign(val, signatureSecret); + + } + }); + + if (options.fetchUser && !Parse._isNullOrUndefined(Parse.User.current())) { + Parse.User.current().fetch().then(function(user) { + next(); + }, function() { + // If user from cookie is invalid, reset Parse.User.current() to null. + Parse.User.logOut(); + next(); + }); + } else { + next(); + } + }; +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..5a8ee14 --- /dev/null +++ b/package.json @@ -0,0 +1,47 @@ +{ + "name": "parse-express-cookie-session", + "version": "0.0.3", + "description": "Parse cookie session for local env", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "https://github.com/flovilmart/parse-express-cookie-session.git" + }, + "author": { + "name": "Florent Vilmart" + }, + "license": "BSD", + "bugs": { + "url": "https://github.com/flovilmart/parse-express-cookie-session/issues" + }, + "dependencies": { + "express": "~3.1.0" + }, + "homepage": "https://github.com/flovilmart/parse-express-cookie-session", + "devDependencies": {}, + "gitHead": "44959f3499edd73199fdc2e651fadc0f236817e5", + "_id": "parse-express-cookie-session@0.0.3", + "_shasum": "ee87597ddc6eb1551e4b93bdd3082b6aca3893f0", + "_from": "parse-express-cookie-session@0.0.3", + "_npmVersion": "1.4.16", + "_npmUser": { + "name": "flovilmart", + "email": "flovilmart@icloud.com" + }, + "maintainers": [ + { + "name": "flovilmart", + "email": "flovilmart@icloud.com" + } + ], + "dist": { + "shasum": "ee87597ddc6eb1551e4b93bdd3082b6aca3893f0", + "tarball": "http://registry.npmjs.org/parse-express-cookie-session/-/parse-express-cookie-session-0.0.3.tgz" + }, + "directories": {}, + "_resolved": "https://registry.npmjs.org/parse-express-cookie-session/-/parse-express-cookie-session-0.0.3.tgz", + "readme": "ERROR: No README data found!" +}