balderdashy / sails

Realtime MVC Framework for Node.js
https://sailsjs.com
MIT License
22.85k stars 1.95k forks source link

SailsJS 0.12.14 Cookie SameSite Update Needed #6942

Open crh3675 opened 4 years ago

crh3675 commented 4 years ago

Node version: 10.16.3 Sails version (sails): 0.12.4


Need an update to the 0.12.4 branch for sameSite Cookie option. Currently get error:

TypeError: option sameSite = None is invalid ./node_modules/sails/node_modules/express-session/node_modules/cookie/index.js:174:15)

We cannot upgrade to Sails v1. Please assist.

sailsbot commented 4 years ago

@crh3675 Thanks for posting! We'll take a look as soon as possible.

In the mean time, there are a few ways you can help speed things along:

Please remember: never post in a public forum if you believe you've found a genuine security vulnerability. Instead, disclose it responsibly.

For help with questions about Sails, click here.

whichking commented 4 years ago

Hi, @crh3675—

Thanks for letting us know about the error you're getting! Would you mind sharing more about the configuration you're trying to use? We'd love to take a closer look.

crh3675 commented 4 years ago

I think I found a way around the issue using "Lax". Not really sure how to test (until/unless it breaks). https://blog.chromium.org/2019/10/developers-get-ready-for-new.html

whichking commented 4 years ago

Glad you found a workaround, @crh3675!

dcolley commented 4 years ago

This is still a problem in sails@1.2.3, which bundles "cookie": "0.3.1" I understand sameSite=None was implemented in cookie@0.4.0 (which is now bundled with express@4.17.1) I added the following to node_modules/sails/node_modules/cookie/index.js:174, and it worked for me:

      case 'none':
        str += '; SameSite=None';
        break;

Now, how am I going to remember that when I deploy to production... {scratching chin}

dcolley commented 4 years ago

Just an FYI, between last night and tonight I took an update to Chrome {Version 80.0.3987.122 (Official Build) (64-bit)} and then it started rejecting SameSite=Lax (this is the default if SameSite is not specified) for my API...

dcolley commented 4 years ago

I think this article about peerDependencies is a possible solution.

https://medium.com/angular-in-depth/npm-peer-dependencies-f843f3ac4e7f

Move

  "dependencies": {
        "cookie": "0.3.1",
  }

down to

  "peerDependencies": {
        "cookie": "^0.4.0",
  }

this way we don't get a duplicate module if a compatible cookie version is already installed?

NachtRitter commented 4 years ago

@dcolley peerDependency isn't a good solution, especially if package is used inside of the code. If compatible versions are installed they wouldn't be duplicated. npm flattens the dependency tree on the fly during the installation. You may read about it here: https://docs.npmjs.com/cli/dedupe

upd If you run command npm list in any of your projects you will see dependencies tree and which dependencies were moved to the up.

dcolley commented 4 years ago

@NachtRitter re. "if package is used inside of the code" - it does not make sense to have a package in package.json and then not use it..?

The bigger issue here is that sails specifies "0.3.1" exactly, and hence node won't use 0.4.0 even though it's available in the parent project. And the installation process, sails new my-app does not cater for pre-installation of modules into target directory - it fails if the my-app directory exists...

peter-gyulavari commented 4 years ago

Is there a good solution to this? We have a sails API in production, and we would like to ship new features with the next update but chrome already sets SameSite to Lax in the test environment and users can't login to the website. Should we just change "node_modules/sails/node_modules/cookie/index.js" ?

dcolley commented 4 years ago

One way would be to amend the code in node_modules (you need to remember to do this each time you deploy...

Another way to do this is to

After amending the you should be able to set a cookie in your controller

    fn: async function (inputs, exits, env) {

        let {req, res} = env
        let myvalue = 1
        // ... blah ...
        res.cookie('mycookie', myvalue, {
            // domain: config.domain,
            path: '/admin',
            httpOnly: true,
            // sameSite: true,
            sameSite: "none",
            secure: true,
        }); 
        res.redirect(installUrl);
    }
peter-gyulavari commented 4 years ago

I see, thanks for the help. Other possible solution we thought of is to create a DNS A record pointing to our "real API", so browsers might not think it's a cross site request.

according to web.dev: "If the user is on www.web.dev and requests an image from static.web.dev then that is a same-site request."

so if we do: frontend: www.mysite.com backend: api.mysite.com (which points to our real API's ip address) it might work.

I'm not really sure we can "cheat" browsers like this though.

kriss1897 commented 4 years ago

@dcolley @ewol123 You can directly use cookie@0.4.x in your project to serialize cookies, and set them using res.set('set-cookie', [serializedCookies]). If you want to sign the cookies, then you can use cookie-signature as the project dependency as well.

LouAdrien commented 4 years ago

Is there anyway to just override the default behaviour through a hook without redoing the cookie logic? Declared http middleware in config/http still seem to be called before any Set cookie headers are set on the requests. What the right way to create a callback that'll modify the headers (to add the proper same site attribute?

I think this should be a highly critical issue as this will break existing project in production, as many apps will probably count on cookies being accepted from 3rd parties.

kriss1897 commented 4 years ago

@LouAdrien The only way is to use the latest cookie module. The session middleware in HTTP headers is the one that sets session cookies. Probably you can override the handling of res.cookie in an HTTP middleware which is called before the session middleware

LouAdrien commented 4 years ago

I did not want to rely on have to mess with sails internal dependencies, so I ended implementing a hook that uses the onHeaders callback to do what I need. I hardcoded a few thing for my use case but you can modify it to fit your own. Make sure to register it before the session hook.

order: [
      'customCookieSet',
      'cookieParser',
      'session',
      'bodyParser',
      'compress',
      'poweredBy',
      'router',
      'www',
      'favicon',
    ],

customCookieSet : function (req,res,next) {
        // Using express onHeader callback to make sure we modify the cookie just before they are sent. Carefull this requires that this hook be registered
        // BEFORE the session hook (or any other hook working with cookies) as cookies are also set with onHeaders and onHeaders callback are called in reverse order of subscription
        // https://www.npmjs.com/package/on-headers
        onHeaders(res, function(){
          // There can be multiple set-cookie instructions
          var currentCookieSets = res.get("set-cookie");
          // get always returns an array, contrary to what the documentation says.
          if(currentCookieSets && currentCookieSets.length){
            for (var i = 0; i < currentCookieSets.length; i++) {
              var currentCookieSet = currentCookieSets[i];
              if( currentCookieSet.indexOf("SameSite") === -1){
                  currentCookieSet += '; SameSite=None';
              }else {
                currentCookieSet = currentCookieSet.replace("SameSite=Strict","SameSite=None");
                currentCookieSet = currentCookieSet.replace("SameSite=Lax","SameSite=None");
              }

              if( currentCookieSet.indexOf("Secure") === -1 ) {
                currentCookieSet += '; Secure';
              }
              currentCookieSets[i] = currentCookieSet;
            }
            res.setHeader('set-cookie',currentCookieSets);
          }
          // console.log("onHeader() --> ", res.get("set-cookie"));
        })
        return next();
    },
crh3675 commented 4 years ago

@LouAdrien Is that proposed solution for 1.x or 0.12?

LouAdrien commented 4 years ago

1.x

crh3675 commented 4 years ago

For our needs, I am testing with 0.12 using a slight modification and it seems to work as well:


    cookieSameSiteHandler: function(req, res, next) {
      onHeaders(res, function() {
        const currentCookieSets = res.get('set-cookie');

        if (currentCookieSets && currentCookieSets.length) {
          for (let i = 0; i < currentCookieSets.length; i++) {
            let currentCookieSet = currentCookieSets[i];
            if (sails.config.session.cookie.secure) {
              currentCookieSet += '; Secure; SameSite=None';
            } else {
              currentCookieSet += '; SameSite=Strict';
            }
            currentCookieSets[i] = currentCookieSet;
          }
          res.setHeader('set-cookie', currentCookieSets);
        }
      });
      return next();
    }
crh3675 commented 4 years ago

I added this code to our config/http.js using the same pattern that @LouAdrien recommended. You need to add a require at the top of that file: const onHeaders = require('on-headers')

Muhammadinaam commented 4 years ago

I am following @LouAdrien or @crh3675 solution. But for me, res.get('set-cookie') returns undefined. How can I fix it?

Muhammadinaam commented 4 years ago

This issue is open for 7 months... 😲 😲

eashaw commented 4 years ago

Hi @Muhammadinaam, whose solution are you trying to use and what version of Sails is your app running?

crh3675 commented 4 years ago

@Muhammadinaam, if set-cookie is undefined, are you sure you have sessions enabled and you are passing a cookie to the browser?

Muhammadinaam commented 4 years ago

I am following @LouAdrien or @crh3675 solution. But for me, res.get('set-cookie') returns undefined.

sails version is 1.0.2. I am working on an already developed project. @LouAdrien's solution is actually adding a middleware. But in my project, previous developers have commented out middleware code in http.js file.

// order: [ // 'cookieParser', // 'session', // 'bodyParser', // 'compress', // 'poweredBy', // 'router', // 'www', // 'favicon', // ],

So I tried to implement @LouAdrien solution using hooks. But for me, res.get('set-cookie') returns undefined

dflupu commented 4 years ago

The following code works for sails < 0.12. Note that it needs to be added AFTER the session middleware.

    cookieAddSameSite: function(req, res, next) {
      res.on('header', function() {
        var currentCookieSets = res.get('set-cookie');

        if (typeof(currentCookieSets) === 'undefined') {
          return;
        }

        if (typeof(currentCookieSets) === 'string') {
          currentCookieSets = [currentCookieSets];
        }

        if (currentCookieSets && currentCookieSets.length) {
          for (var i = 0; i < currentCookieSets.length; i++) {
            var currentCookieSet = currentCookieSets[i];

            if (sails.config.session.cookie.secure) {
              currentCookieSets[i] = currentCookieSet + '; SameSite=None; Secure';
            } else {
              currentCookieSets[i] = currentCookieSet + '; SameSite=Strict';
            }
          }

          res.removeHeader('set-cookie');
          res.setHeader('set-cookie', currentCookieSets);
        }
      });

      return next();
    }
eashaw commented 4 years ago

@crh3675 @dcolley @ewol123 @LouAdrien @dflupu @Muhammadinaam This issue is fixed as of Sails v1.4.0