pavelk2 / social-feed

JavaScript plugin that shows a user feed from the most popular social networks
http://pavelk2.github.io/social-feed-example/
MIT License
963 stars 304 forks source link

Security issue about sharing access tokens and app secret #44

Open grimgit opened 10 years ago

grimgit commented 10 years ago

Isn't it dangerous to publish access tokens?

pavelk2 commented 10 years ago

it is. you can generate an access token, which will last for some period of time. I decided to use pair key|secret - so the key stays for ever. Let me know if there are better ways to do it.

sigurdsn commented 10 years ago

How can one avoid publishing access tokens while using this script? Loving it btw.

pavelk2 commented 10 years ago

Dear Sigurd, I am glad that you like the plugin. The initial idea of this plugin was to make it front-end only, without using any back-end code. The problem of access tokens visibility is one implication of such an approach. One possible way to bypass it - is to create a script on your server, which does calls to social networks and retrieves back data, so access tokens will also be inside of this script (in a back-end so not visible to people). Then in social-feed - instead of calling instagram, facebook, etc - you call your script. It will also help to have twitter working.

sigurdsn commented 10 years ago

Hi, unfortunately I think a back-end script defeats the purpose of your script somewhat. However, I discovered that you can actually get a public feed in JSON from a Facebook page (ex. Apple Inc. page feed). As this is open to the public there is no need for an access token. I've tried adjusting your code, but struggle as it doesn't return a callback function. Console reads error: "Refused to execute script from 'xxx' because its MIME type ('application/json') is not executable, and strict MIME type checking is enabled". Do you have any tips to make this work with your original code? Thank you for your time.

pavelk2 commented 10 years ago

Hi, Thank you very much for sharing it here. I just tried and got the same problem. Because this kind of page seems to be not prepared for jsonp, we can not call it from different domain names. Checked here http://stackoverflow.com/questions/9373645/can-you-get-a-public-facebook-pages-feed-using-graph-api-without-asking-a-user - nothing new. BTW. Even if we will manage to make it work. It still will not work for searching posts via #tags, it will not work for other social networks, such as Google +.

I really see that it is not elegant and potentially dangerous to keep access-tokens public. At the same time if this facebook-app has 0 rights (may be even unclick user-login possibility), then i think it is safe.

Let me know if you see some other solution. Thank you.

sigurdsn commented 10 years ago

No worries. It won't solve every problem, but would be a nice addition to those who don't wan't to struggle with the Facebook Graph and its frequent changes. For me, it is exactly what I need so I'm trying to add it to your script. I found a way to bypass the cross-domain issue and get the json here. It uses YQL and should work. Unfortunately, I'm not so proficient so can't seem to make it! I'm not getting any errors anymore, it just doesn't seem to run the getPosts() function. Ideas?

Edit: I've added this function together with your request() and get_request() functions:

function get_json(url,callback){
  var yql="select *" + " from json" + " where url='" + url + "'";
  yql="http://query.yahooapis.com/v1/public/yql?q=" + encodeURIComponent(yql) + "&format=json" + "&callback=cbfunc";
  $.getJSON(yql,callback); //you need to provide getJSON or use a library
}

Then call it from the facebook section of your code:

switch (account[
  case '@':
  var username = account.substr(1);
  request_url = 'https://www.facebook.com/feeds/page.php?id=' + username + '&format=json';
  get_json(request_url,getPosts);
  break;
}

Edit2: Sorry, don't think I've been quite looking at the code I posted. I'm removing some parts from the original that shouldn't be there.

sigurdsn commented 10 years ago

OK! Got it to work now, for those interested... Turns out the YQL "callback" broke JSON-validation and stopped the script. Note that the JSON data received is differently formatted than from the Graph, so the storing of template variables (post array) needs to be adjusted.

Add this next to your request and get_request functions

function get_json(url,callback){
    var query="select * from json where url='" + url + "'";
    var yql="http://query.yahooapis.com/v1/public/yql?q=" + encodeURIComponent(query) + "&format=json";
    $.getJSON(yql,callback); //you need to provide getJSON or use a library
}

Then call this function instead of "request" for Facebook feed collection. (See "switch" code above.) Finally add/replace to the top of your getPosts(json) function:

var data=json.query.results.json;
$.each(data.entries,function(){
    // looping each entry - template variables must reflect new JSON data.

Personally think it would be a good option to include in your code.

attaway commented 9 years ago

Great stuff! Could you provide an example of how you implemented it?

sigurdsn commented 9 years ago

Hey. Sure! As long as you'll forgive my messy coding. :smile: Note that I've used a few regex functions to split some JSON strings into separate parts. It may not be the most efficient way, so please let me know if you have more 'sustainable' approaches. I have tried to explain most parts of the code with comments.

Here is the my modified Facebook section of the script (note this will break current fb-implementation with access token):

facebook: function(account) {
  var request_url, fb_graph = 'https://graph.facebook.com/';
  switch (account[0]) {
    case '@':
      var username = account.substr(1);
      request_url = 'https://www.facebook.com/feeds/page.php?id=' + username + '&format=json';
      get_json(request_url, getPosts);
      break;
    case '#':
      // we cannot search for posts via #tags with from this feed.
      break;
    default:
      request_url = 'https://www.facebook.com/feeds/page.php?id=' + account + '&format=json';
      get_json(request_url, getPosts);
  }

  function getPosts(data) {
    var json = data.query.results.json;
    var i = 0;
    $.each(json.entries, function() {
      var element = this,
        post = {};
      i++;

      if (element.content) {
        var text = '',
          url;
        // Regex function to replace unnecessary Facebook on-click javascript link with direct URL.
        if (element.content) text = element.content.replace(/<a[^>]*\?u=([^&]*)[^>]*>/gi, function(match, p1, offset, string) {
          url = unescape(JSON.parse('"' + p1 + '"'));
          return '<a href="' + url + '" target="_blank">';
        }).split('<br/><br/>'); // Split the string between 'message' (actual post) and 'description' (linked content).

        if (element.alternate) permalink = element.alternate; // Handy permalink to the single post on Facebook
        if (options.show_media) {
          // Regex function to get the actual attached image SRC from Facebook redirect.
          image_tag = text[1].replace(/.*src=\"([^\"]*).*/gi, function(match, src, offest, string) {
            var image_url = src;
            if (image_url.indexOf('?') == -1) {
              image_url = image_url.replace('_s.', '_b.').replace('s130x130', 's720x720') // Supposedly works to increase resolution of FB-images.
            } else {
              image_url = decodeURIComponent(image_url.replace(/.*(url=)(.+)$/gi, '$2')); // Strips away som extra code in some situations.
            }
            // CSS method to ensure specific width/height format of displayed image attachment.
            return '<div class="crop-height bg-image-attachment" style="background-image:url(' + image_url + ');"><img class="scale invisible" src="' + image_url + '" alt="FB-post image" /></div>';
          });
          // Wrapping image in a link, if there actually is an image.
          if (image_tag != text[1]) post.attachment = '<a href="' + url + '" target="_blank" alt="Post image attachment">' + image_tag + '</a>';
        }

        // Variots other post information from JSON-data
        post.id = element.id;
        post.dt_create = moment(element.published);
        post.author_link = 'http://facebook.com/' + json.self.replace(/\D/g, ''); // Regex to strip everything but the page ID
        post.author_picture = fb_graph + json.self.replace(/\D/g, '') + '/picture'; // Regex to strip everything but the page ID
        post.author_name = element.author.name;
        // First part of the post content (actual post)
        post.message = text[0];
        // Second part of the post content (linked content)
        post.description = text[1].replace(/<a href=\\?"[^http]+[^>]*>(<img[^>]+>)?([^<]*)?.*/gi, '$2').replace(/.*(<a[^<]*<\/a>)<br\/>([^>]*>)(.*)/gi, function(match, title, link, description, offset, string) {
          // Formatting should be moved to CSS-file.
          return '<div style="diplay:block;padding:5px;border:solid 1px #eee;">' + title + '<br />&quot;' + shorten(description, 150) + '&quot;</div>'
        });
        post.link = permalink;
        post.social_network = 'facebook';

        getTemplate(post, json.entries[json.entries.length - 1] == element);
      }
      // Limit loop to the number of posts set by init function.
      if (i >= options.facebook.limit) return false;
    });
  }
}

My additions to CSS-code (there is some documentation for this somewhere online):

.social-feed-element div.crop-height {
  max-height:231px;
  overflow:hidden;
}
.social-feed-element img.scale {
  display: block;
  width: 100%;
  max-width: 100%;
  height: auto !important;
  border: 0;
  -ms-interpolation-mode: bicubic;
}
.social-feed-element .bg-image-attachment {
  vertical-align: middle;
  -ms-background-position-x: center;
  -ms-background-position-y: center;
  background-position: center center;
  background-size: cover;
  -ms-behavior: url(backgroundsize.min.htc);
}
.social-feed-element .invisible {
  visibility: hidden;
}

Disclaimer: I've not done any extensive testing with this code, so I don't know if it will work for everyone - or if it may break by changes to the Facebook feed.

Best of luck!

attaway commented 9 years ago

I'm running into some problems with getPosts function. I keep getting the error "Cannot read property 'json' of null"

I created a Pen of it @ http://codepen.io/attaway/pen/bIqpF Could you check it out and let me know what you may have done differently?

sigurdsn commented 9 years ago

Hey, @Attaway. Good catch! It seems the JSON feed url only takes your page's ID number - not slug. So replace "pizzaofvenice" with "308622362552449". You could use http://findmyfacebookid.com/ to find any page-ID.

sigurdsn commented 9 years ago

Hey all. Unfortunately, as of yesterday (28/1), Facebook depreciated this work-around. From their changelog:

"The Pages JSON feed (e.g. https://www.facebook.com/feeds/page.php?id=%2019292868552&format=json) is now deprecated and will stop returning data from Jan 28, 2015 onwards. Developers should instead call the feed edge on the Graph API's Page object: /v2.2/{page_id}/feed."

So I don't think there are any other loopholes to get around the Facebook Graph and its tokens anymore.

sdbondi commented 9 years ago

I really see that it is not elegant and potentially dangerous to keep access-tokens public. At the same time if this facebook-app has 0 rights (may be even unclick user-login possibility), then i think it is safe.

@pavelk2 Could you give some steps on how to disable all permissions on facebook app key/secret? I don't see a way to disable all rights for the application.

Thanks :)

pavelk2 commented 9 years ago

Hi @sdbondi,

I just checked a facebook application settings. There is an IP white list - may be you can use it, so even if somebody steals your credentials - they can not send requests.

Anyway all these workarounds are just patches. The real solution is to use a server side. I initiated a nodejs project https://github.com/pavelk2/social-feed-server, which anybody will be able to launch in heroku for free to use with a regular social-feed plugin to solve the security and some other issues. Unfortunately so far I don't have much time to bring the server side to a working version 1.0. Any contributions are very welcome.

sdbondi commented 9 years ago

Hi @pavelk2

The problem is that the IP address that's using the keys could be anything as it's the users browser that's making the request. Facebook say using the client ID is the correct way should be used as a public key as it is already restricted - not sure if that key has permission to read posts.

But yes, as you say the solution is to put these keys on the server side.

Thanks for your help :)

pavelk2 commented 8 years ago

Dear @grimgit, @sigurdsn, @attaway, @sdbondi, @tiptronic The server side is the most requested component now. Is there anybody who could help implementing it? https://github.com/pavelk2/social-feed-server

packetlost commented 8 years ago

would webrtc datachannels not work for passing the data between users: each user queries their own social data using their token, stores it locally via the browsers data store and then passes the requested social data browser to browser as other users load the page containing the javascript?

Or is this not distributed? I checked the wiki; it is blank. :(

Taking the power out of the hands of the big players, not to mention ad revenue, and returning it to smaller intermediaries seems like a great idea.

ateufel commented 8 years ago

publishing the app access token (which includes the app secret) is a VERY bad idea, you should make it perfectly clear. actually, you should recommend NOT to do that in the readme. there is no need to implement it, it's just a simple api call with curl in php...i mean, writing a plugin (= a dependency) just for a few lines of code would be pointless.