'use strict'; // too bound to the request object, but ok for now var _ = require('underscore'); var OAuthUtil = require('oauth-client'); var step = require('step'); var CdbRequest = require('../models/cartodb-request'); var cdbReq = new CdbRequest(); var oAuth = (function(){ var me = { oauth_database: 3, oauth_user_key: "rails:oauth_access_tokens:<%= oauth_access_key %>", is_oauth_request: true }; // oauth token cases: // * in GET request // * in header me.parseTokens = function(req){ var query_oauth = _.clone(req.method === "POST" ? req.body: req.query); var header_oauth = {}; var oauth_variables = ['oauth_body_hash', 'oauth_consumer_key', 'oauth_token', 'oauth_signature_method', 'oauth_signature', 'oauth_timestamp', 'oauth_nonce', 'oauth_version']; // pull only oauth tokens out of query var non_oauth = _.difference(_.keys(query_oauth), oauth_variables); _.each(non_oauth, function(key){ delete query_oauth[key]; }); // pull oauth tokens out of header var header_string = req.headers.authorization; if (!_.isUndefined(header_string)) { _.each(oauth_variables, function(oauth_key){ var matched_string = header_string.match(new RegExp(oauth_key + '=\"([^\"]+)\"')); if (!_.isNull(matched_string)) { header_oauth[oauth_key] = decodeURIComponent(matched_string[1]); } }); } //merge header and query oauth tokens. preference given to header oauth return _.defaults(header_oauth, query_oauth); }; // remove oauthy tokens from an object me.splitParams = function(obj) { var removed = null; for (var prop in obj) { if (/^oauth_\w+$/.test(prop)) { if(!removed) { removed = {}; } removed[prop] = obj[prop]; delete obj[prop]; } } return removed; }; me.getAllowedHosts= function() { var oauthConfig = global.settings.oauth || {}; return oauthConfig.allowedHosts || ['carto.com', 'cartodb.com']; }; // do new fancy get User ID me.verifyRequest = function(req, metadataBackend, callback) { var that = this; //TODO: review this var httpProto = req.protocol; if(!httpProto || (httpProto !== 'http' && httpProto !== 'https')) { var msg = "Unknown HTTP protocol " + httpProto + "."; var unknownProtocolErr = new Error(msg); unknownProtocolErr.http_status = 500; return callback(unknownProtocolErr); } var username = cdbReq.userByReq(req); var requestTokens; var signature; step( function getTokensFromURL(){ return oAuth.parseTokens(req); }, function getOAuthHash(err, _requestTokens) { if (err) { throw err; } // this is oauth request only if oauth headers are present this.is_oauth_request = !_.isEmpty(_requestTokens); if (this.is_oauth_request) { requestTokens = _requestTokens; that.getOAuthHash(metadataBackend, requestTokens.oauth_token, this); } else { return null; } }, function regenerateSignature(err, oAuthHash){ if (err) { throw err; } if (!this.is_oauth_request) { return null; } var consumer = OAuthUtil.createConsumer(oAuthHash.consumer_key, oAuthHash.consumer_secret); var access_token = OAuthUtil.createToken(oAuthHash.access_token_token, oAuthHash.access_token_secret); var signer = OAuthUtil.createHmac(consumer, access_token); var method = req.method; var hostsToValidate = {}; var requestHost = req.headers.host; hostsToValidate[requestHost] = true; that.getAllowedHosts().forEach(function(allowedHost) { hostsToValidate[username + '.' + allowedHost] = true; }); that.splitParams(req.query); // remove oauth_signature from body if(req.body) { delete req.body.oauth_signature; } signature = requestTokens.oauth_signature; // remove signature from requestTokens delete requestTokens.oauth_signature; var requestParams = _.extend({}, req.body, requestTokens, req.query); var hosts = Object.keys(hostsToValidate); var requestSignatures = hosts.map(function(host) { var url = httpProto + '://' + host + req.path; return signer.sign(method, url, requestParams); }); return requestSignatures.reduce(function(validSignature, requestSignature) { if (signature === requestSignature && !_.isUndefined(requestSignature)) { validSignature = true; } return validSignature; }, false); }, function finishValidation(err, hasValidSignature) { const authorizationLevel = hasValidSignature ? 'master' : null; return callback(err, authorizationLevel); } ); }; me.getOAuthHash = function(metadataBackend, oAuthAccessKey, callback){ metadataBackend.getOAuthHash(oAuthAccessKey, callback); }; return me; })(); function OAuthAuth(req, metadataBackend) { this.req = req; this.metadataBackend = metadataBackend; this.isOAuthRequest = null; } OAuthAuth.prototype.verifyCredentials = function(callback) { if (this.hasCredentials()) { oAuth.verifyRequest(this.req, this.metadataBackend, callback); } else { callback(null, false); } }; OAuthAuth.prototype.getCredentials = function() { return oAuth.parseTokens(this.req); }; OAuthAuth.prototype.hasCredentials = function() { if (this.isOAuthRequest === null) { var passed_tokens = oAuth.parseTokens(this.req); this.isOAuthRequest = !_.isEmpty(passed_tokens); } return this.isOAuthRequest; }; module.exports = OAuthAuth; module.exports.backend = oAuth;