【问题标题】:publishing Sails / Node.js to Kongregate将 Sails / Node.js 发布到 Kongregate
【发布时间】:2020-06-17 20:32:49
【问题描述】:

所以,最终我基于 Sails 构建的 MMORTS 游戏将前往 Kongregate。 几乎没有障碍,比如连接 websocket,但现在解决了。 可能最后一个障碍是保持经过身份验证的会话。我到处都在使用框架,但我不知道身份验证会话是如何在后台工作的。
主要问题可能是 CSRF 或 CORS。
我使用的是 Sails v1.0。 所以,我从上传到 kongregate 的 HTML 开始。我正在举一个最简单的例子:

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta charset="utf-8">

    <script src="jquery.js"></script>
    <script src='https://cdn1.kongregate.com/javascripts/kongregate_api.js'></script>

    <script src="sails.io.js"
      autoConnect="false"
      environment="production"
      headers='{ "x-csrf-token": "" }'
    ></script>

    <script type="text/javascript">
      io.sails.url = 'https://my-secret-game.com'; // or where you want
    </script>

  </head>
  <script src="main.js"></script>
</html>

这是 main.js ,我也上传到 kongregate

kongregateAPI.loadAPI(function(){
  window.kongregate = kongregateAPI.getAPI();
  var username = kongregate.services.getUsername();
  var id = kongregate.services.getUserId();
  var token = kongregate.services.getGameAuthToken();

  $.get("https://my-secret-game.com/csrfToken", function (data, jwres) {

    var params = {
      username: username,
      id: id,
      token: token,
      _csrf: data._csrf
    };

    $.post("https://my-secret-game.com/kong", params, function(data, jwr, xhr){

      // cant set the cookie - because of chrome. this doesnt work
      document.cookie = document.cookie + ';authenticated=true;sails.sid=' + data.id;

      $.get("https://my-secret-game.com/csrfToken", function (data, jwres) {
        var msg = {
          testing_authentication: true,
          _csrf: data._csrf
        };
        $.post("https://my-secret-game.com/test", msg, function(data, status){
          // getting the 403 Forbidden, CSRF mismatch. trying to access 
          // the play/test route, which is protected by sessionAUTH
          console.log('data.response', data.response)
        });

      });
    });
  });
});

问题是,每当我尝试使用 sessionAUTH 发布我的 Sails 后端时,我都会收到 403 Forbidden。我也无法设置 cookie - 可能是因为 Chrome。我能做些什么?当我获得 CSRF 令牌时,在下一个请求中,我的 Sails 应用程序会响应 CSRF 不匹配。就错了。

这是我的 Sails 后端服务器上的控制器

module.exports = {
  kong: function (req, res, next) {
    var url = 'https://api.kongregate.com/api/authenticate.json';
    var kong_api_key = 'my-secred-api-key';
    var params = req.allParams();
    var request = require('request');

    var req_form = {
      "api_key": kong_api_key,
      "user_id": params.id,
      "game_auth_token": params.token
    };

    request({
        url: url,
        method: "POST",
        json: true,
        body: req_form,
        timeout: 5000
    }, function (err, response, body){
       if(err) { console.log(err, 'ERR43'); return res.ok(); }
       else {
        if(!response.body.success) {
          console.log('unsuccessful login from kongregate')
          return res.ok();
        }

        // trying to use a existing user and authenticate to it
        User.find({username: 'admin-user'}).exec(function(err, users) {
          var user = users[0];
          req.session.authenticated = true;
          req.session.user = { id: user.id };

          // trying to send session_id, so that i could hold it on kongregates cookies as `sid`
          return res.send({ user: user, id: req.session.id });

        });
       }
    });
  },

somoene 可以帮忙修复我的应用的身份验证和 CSRF 吗?

如果需要有关我的配置的更多信息,这是 config/session.js

var prefixes = 'dev';

module.exports.session = {
  secret: 'my-secret',

  cookie: {
     secure: false
  },

  adapter: 'redis',

  host: 'localhost',
  port: 6379,
  ttl: 3000000,
  db: 0,
  prefix: prefixes + 'sess:',
};

config/policies.js

module.exports.policies = {
  user: {
    'new':    'flash',
    'create': 'flash',
    'edit':   'rightUser',
    'update': 'rightUser',
    '*':      'sessionAuth'
  },
  play: {
    '*': 'sessionAuth'
  }
};

api/policies/sessionAuth.js

module.exports = function(req, res, next) {
  if (req.session.authenticated) {
    return next();
  } else {
    var requireLoginErr = [
      { name: 'requireLogin', message: 'You must be signed in' }
    ];
    req.session.flash = {
      err: requireLoginErr
    };
    res.redirect('/');
    return;
  }
};

config/security.js

module.exports.security = {
    csrf: true,
  cors: {
    allowRequestMethods: 'GET,PUT,POST,OPTIONS,HEAD',
    allowRequestHeaders: 'content-type,Access-Token',
    allowResponseHeaders: '*',
    allRoutes: true,
    allowOrigins: '*',
    allowCredentials: false,
  },
};

【问题讨论】:

    标签: javascript node.js sails.js csrf kongregate


    【解决方案1】:

    好吧,因为我没有答案(显然 - 问题很糟糕),所以用我自己解决的解决方案来回答 - 所以下次我可以阅读我的自我。不知道它有多好,但至少它有效。

    Sails CORS 采用 Express.js 格式,如果我在配置中允许,则允许将套接字连接到 kongregate。 但它不允许通过 cookie 发送sails.sid(身份验证令牌)以正常方式进行身份验证。

    出于安全考虑,Chrome 根本不允许使用 javascript(我在 Kongregate 上没有后端)将 cookie 设置为标头。因此,如果我无法发送带有标头的 cookie,Sails 就无法以正常方式对请求进行身份验证。即使我允许 CORS 接受“cookie”标头 - 浏览器也不允许使用 javascript 设置 cookie 标头。

    我可以制作一些独特的标头,例如“身份验证”并在那里设置sails.sid,扩展Sails 的一些核心功能以采用这个新标头而不是cookie 标头。但问题是 - 在支持 Sails 的情况下,我根本无法获得这些sails.sid 并将其发送到我的外部前端。它是在哪里创建的?如何在 Sails 后端获取sails.sid?不确定 - 无法通过 Google 搜索。

    所以,我只是以最简单的方式进行身份验证 - 在帐户登录/注册时,我只是自己创建一个会话密钥 - 使用 bcrypt 散列 user_id+secret_token(取自sails config secrets)。并发送到前端 { user_id: 'abcd', secret_token: 'a2dw412515...' }

    我在 Sails 中制定了对每个 POST/GET 请求进行身份验证的策略 - 获取请求的 session_id 和 user_id,并使用 bcrypt 进行比较,session_id 是否与加密的 user_id+secret_token 相同。我希望它足够安全。

    所以,它奏效了。我只需要禁用 CSRF。也许有一天我会再次实现它,我只需要以我的方式编写它,而不是保留 Sails 的默认值。

    工作代码:

    前端

    // you upload this HTML file to kongregate
    // also you upload additionally ZIP named "kongregate_uploaded_folder" with libraries like sails.io, jquery
    
    <!DOCTYPE html>
    <html>
      <head>
        <script src="kongregate_uploaded_folder/jquery.js"></script>
        <script src='https://cdn1.kongregate.com/javascripts/kongregate_api.js'></script>
        <script src="kongregate_uploaded_folder/sails.io.js"
          autoConnect="false"
          environment="production"
        ></script>
      </head>
    
      <body style="padding:0; margin:0; overflow:hidden;">
    
      <div style="position:absolute; margin: 0px; padding: 0px;">
        <canvas id="main_canvas" style="position:absolute;" width="640" height="480" >Best initial resolution to have in Kongregate</canvas>
      </div>
    
      <script>
        // the first thing happends - you try to connect your frontend with Kongregate
        kongregateAPI.loadAPI(function(){
          window.kongregate = kongregateAPI.getAPI();
    
          if(!kongregate.services.isGuest()) {
            var params = {
              username: kongregate.services.getUsername(),
              id: kongregate.services.getUserId(),
              token: kongregate.services.getGameAuthToken(),
            };
    
            // call your backend to create a new session and give you session_id
            $.post("https://your_game_server.com/kong", params, function(data, jwr, xhr){
              var kong_session_id = data.kong_session_id;
              var kong_user_id = data.kong_user_id;
              var user = data.user;
    
              // connect your sockets with the server in this way
              io.socket = io.sails.connect("https://your_game_server.com", { useCORSRouteToGetCookie: false, reconnection: true });
    
              // subscribe to the global sockets channel. You have to make this route and code, but here is a example
              io.socket.get('/subscribe', { kong_session_id: kong_session_id, kong_user_id: kong_user_id }, function(data, jwr){
                if (jwr.statusCode == 200){
                  io.socket.on(data.room, function(event){
                    // on any server-side event, you will get this "event" message. At this part you decide what to do with this data
                    incoming_socket_event(event); // i wont write this function
                  });
                  // your game continues here:
                  $.get("https://your_game_server.com/player_data?kong_session_id=" + kong_session_id + "&kong_user_id=" + kong_user_id, params, function(data, jwr, xhr){
                  // you will get authenticated "current_user"
                }
              });
    
            })
          }
        });
      </script>
    </html>
    

    后台

    // SAILS BACKEND:   home_controller.js
    module.exports = {
      kong: function (req, res, next) {
        // player has already opened your game in kongregate.com and frontend requests this endpoint POST /kong { id: 84165456, token: 'as54..' }
        // you need to create a new session for this player, or register this player. This is your own session creation, since default sails session wont work with external website frontend.
        var req_params = {
          url: "https://api.kongregate.com/api/authenticate.json", // default URL to validate kongregate user
          method: "POST",
          json: true,
          body: { 
            api_key: 'gg000g00-c000-4c00-0000-c00000f2de8', // kongregate will provide this api-key for server-side connection (this one has some letters replaced)
            user_id: 84165456, // when frontend requests POST /kong { id=84165456 } , this 84165456 is provided by kongregate in the frontend
            game_auth_token: "as54a45asd45fs4aa54sf" // when frontend requests POST /kong { token = 'as54..' }, this is provided by kongregate in the frontend
          },
          timeout: 20000
        }
    
        // request kongregate that this is the real player and you will get a username
        request(req_params, function (err, response, body){
          var response_params = response.body; // response from kongregate API
    
          // search for user with this kongregate_id inside your database, maybe he is already registered, and you need just to create a new session.
          User.find({ kongregate_id: response_params.user_id }).exec(function(err, usr) {
            var user = usr[0]
    
            // if player already has an account inside your online game
            if(user) {
              // create new session for this user.
              require('bcryptjs').hash("your_own_random_secret_key" + user.id, 10, function sessionCreated(err, kong_session_id) {
    
                // send this info to frontend, that this player has been connected to kongregate
                return res.send({ 
                  user: user, 
                  kong_session_id: kong_session_id, 
                  kong_user_id: user.id 
                });
              });            
    
            //if this is new user, you need to register him
            } else { 
              var allowedParams = {
                username: response_params.username, // kongregate will give you this player username
                email: 'you_have_no_email@kong.com', // kongregate does not provide email
                password: 'no_password_from_kongregate',
                kongregate_id: response_params.user_id // kongregate will give you this player id
              };
    
              User.create(allowedParams, function(err, new_user) {
                // create new session for this user.
                require('bcryptjs').hash("your_own_random_secret_key" + new_user.id, 10, function sessionCreated(err, kong_session_id) {
    
                  // send this info to frontend, that this player has been connected to kongregate
                  return res.send({ 
                    user: new_user, 
                    kong_session_id: kong_session_id, 
                    kong_user_id: new_user.id 
                  });
                }); 
              });
            }
          });
        });
      },
    };
    

    路线

        // config/routes.js
        module.exports.routes = {
          'GET /player_data': 'PlayController.player_data',
          'GET /subscribe': 'PlayController.subscribe',
          'POST /kong': {
             action: 'home/kong',
             csrf: false // kongregate is a external website and you will get CORS error without this
          },
        };
    

    安全

    // config/security.js
    module.exports.security = {
        csrf: false,
      cors: {
        allowOrigins: ['https://game292123.konggames.com'], // your game ID will be another, but in this style
        allowRequestMethods: 'GET,POST',
        allowRequestHeaders: 'Content-Type',
        allowResponseHeaders: '',
        allRoutes: true,
        allowCredentials: false,
      },
    };
    

    套接字

    // config/sockets.js
    module.exports.sockets = {
      grant3rdPartyCookie: true,
      transports: ["websocket"],
      beforeConnect: function(handshake, cb) { return cb(null, true); },
    };
    

    配置策略

    // /config/policies.js
    module.exports.policies = {
      play: {'*': 'sessionAuth'},
    };
    

    API 政策

    // /app/sessionAuth.js
    module.exports = function(req, res, next) {
      var params = req.allParams();
      // your own session handling way to get the user from session token
      require('bcryptjs').compare("your_own_random_secret_key" + params.kong_user_id, params.kong_session_id, function(err, valid) {
        req.session.authenticated = true;
        req.session.user_id = params.kong_user_id;
        return next();
      });
    };
    

    控制器

    // /api/controllers/PlayController.js
    module.exports = {
        player_data: async function (req, res, next) {
            var users = await User.find(req.session.user_id);
            return res.send({ current_user: users[0] });
        },
        subscribe: async function (req, res, next) {
          var users = await User.find(req.session.user_id);
          var roomName = String(users[0].id);
          sails.sockets.join(req.socket, roomName);
          res.json({ room: roomName });
        },
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-08-20
      • 1970-01-01
      • 1970-01-01
      • 2015-08-15
      • 2023-04-10
      相关资源
      最近更新 更多