Browse Source

Pass connection token "ctoken" to SockJS

pull/126/head
Lal'C Mellk Mal 3 years ago
parent
commit
427a5c7816
6 changed files with 117 additions and 8 deletions
  1. +1
    -1
      archive/dump.js
  2. +5
    -1
      client/conn.js
  3. +1
    -1
      server/caps.js
  4. +4
    -2
      server/render.js
  5. +79
    -3
      server/server.js
  6. +27
    -0
      server/state.js

+ 1
- 1
archive/dump.js View File

@@ -139,7 +139,7 @@ function dump_thread(op, board, ident, outputs, cb) {
var authDumper = new AuthDumper(reader, outputs.auth);

var out = outputs.html;
render.write_thread_head(out, board, op, {
render.write_thread_head(out, '', board, op, {
subject: preThread.subject,
});



+ 5
- 1
client/conn.js View File

@@ -72,7 +72,11 @@ window.new_socket = function (attempt) {
var protocols = ['xdr-streaming', 'xhr-streaming', 'iframe-eventsource', 'iframe-htmlfile', 'xdr-polling', 'xhr-polling', 'iframe-xhr-polling', 'jsonp-polling'];
if (config.USE_WEBSOCKETS)
protocols.unshift('websocket');
return new SockJS(SOCKET_PATH, null, {
var url = SOCKET_PATH;
if (typeof ctoken != 'undefined') {
url += '?' + $.param({ctoken: ctoken});
}
return new SockJS(url, null, {
transports: protocols,
});
};


+ 1
- 1
server/caps.js View File

@@ -182,7 +182,7 @@ function parse_suspensions(suspensions) {
}

exports.lookup_ident = function (ip, country) {
var ident = {ip: ip, readOnly: config.READ_ONLY};
var ident = {ip, country, readOnly: config.READ_ONLY};
if (country
&& config.RESTRICTED_COUNTRIES
&& config.RESTRICTED_COUNTRIES.indexOf(country) >= 0) {


+ 4
- 2
server/render.js View File

@@ -104,7 +104,7 @@ function make_link_rels(board, bits) {
}).join('');
}

exports.write_board_head = function (out, board, nav) {
exports.write_board_head = function (out, initScript, board, nav) {
var indexTmpl = RES.indexTmpl;
var title = STATE.hot.TITLES[board] || escape(board);
var metaDesc = "Real-time imageboard";
@@ -116,6 +116,7 @@ exports.write_board_head = function (out, board, nav) {
out.write(escape(metaDesc));
out.write(indexTmpl[i++]);
out.write(make_board_meta(board, nav));
out.write(initScript);
out.write(indexTmpl[i++]);
if (RES.navigationHtml)
out.write(RES.navigationHtml);
@@ -124,7 +125,7 @@ exports.write_board_head = function (out, board, nav) {
out.write(indexTmpl[i++]);
};

exports.write_thread_head = function (out, board, op, opts) {
exports.write_thread_head = function (out, initScript, board, op, opts) {
var indexTmpl = RES.indexTmpl;
var title = '/'+escape(board)+'/ - ';
if (opts.subject)
@@ -140,6 +141,7 @@ exports.write_thread_head = function (out, board, op, opts) {
out.write(escape(metaDesc));
out.write(indexTmpl[i++]);
out.write(make_thread_meta(board, op, opts.abbrev));
out.write(initScript);
out.write(indexTmpl[i++]);
if (RES.navigationHtml)
out.write(RES.navigationHtml);


+ 79
- 3
server/server.js View File

@@ -10,6 +10,7 @@ var _ = require('../lib/underscore'),
check = require('./msgcheck').check,
common = require('../common'),
config = require('../config'),
crypto = require('crypto'),
db = require('../db'),
fs = require('fs'),
hooks = require('../hooks'),
@@ -347,7 +348,8 @@ function (req, resp) {
var paginationHtml;
yaku.once('begin', function (thread_count) {
var nav = page_nav(thread_count, -1, board == 'archive');
render.write_board_head(resp, board, nav);
var initScript = make_init_script(req.ident);
render.write_board_head(resp, initScript, board, nav);
paginationHtml = render.make_pagination_html(nav);
resp.write(paginationHtml);
resp.write('<hr>\n');
@@ -405,7 +407,8 @@ function (req, resp) {
var board = this.board;
var nav = page_nav(this.threadCount, this.page, board == 'archive');
resp = write_gzip_head(req, resp, web.noCacheHeaders);
render.write_board_head(resp, board, nav);
var initScript = make_init_script(req.ident);
render.write_board_head(resp, initScript, board, nav);
var paginationHtml = render.make_pagination_html(nav);
resp.write(paginationHtml);
resp.write('<hr>\n');
@@ -548,7 +551,8 @@ function (req, resp) {
var board = this.board, op = this.op;

resp = write_gzip_head(req, resp, this.headers);
render.write_thread_head(resp, board, op, {
var initScript = make_init_script(req.ident);
render.write_thread_head(resp, initScript, board, op, {
subject: this.subject,
abbrev: this.abbrev,
});
@@ -633,6 +637,57 @@ web.resource(/^\/outbound\/a\/(\d{0,10})$/, function (req, params, cb) {
cb(null, 303.1, url);
});

function make_init_script(ident) {
var secretKey = STATE.hot.connTokenSecretKey;
if (!ident || !ident.country || !secretKey)
return '';
var payload = JSON.stringify({
ip: ident.ip,
cc: ident.country,
ts: Date.now(),
});
// encrypt payload as 'ctoken'
var iv = crypto.randomBytes(12);
var cipher = crypto.createCipheriv('aes-256-gcm', secretKey, iv);
var crypted = cipher.update(payload, 'utf8', 'hex');
crypted += cipher.final('hex');
var authTag = cipher.getAuthTag()
if (authTag.length != 16) throw 'auth tag of unexpected length';
var combined = iv.toString('hex') + authTag.toString('hex') + crypted;
return '\t<script>var ctoken = ' + JSON.stringify(combined) + ';</script>\n';
}

function decrypt_ctoken(ctoken) {
var secretKey = STATE.hot.connTokenSecretKey;
if (!secretKey)
return null;
if (ctoken.length < 56) {
winston.warn('ctoken too short');
return null;
}
var iv = new Buffer(ctoken.slice(0, 24), 'hex');
if (iv.length != 12) {
winston.warn('iv not hex');
return null;
}
var authTag = new Buffer(ctoken.slice(24, 56), 'hex');
if (authTag.length != 16) {
winston.warn('authTag not hex');
return null;
}
try {
var decipher = crypto.createDecipheriv('aes-256-gcm', secretKey, iv);
decipher.setAuthTag(authTag);
var plain = decipher.update(ctoken.slice(56), 'hex', 'utf8');
plain += decipher.final('utf8');
return JSON.parse(plain);
}
catch (e) {
winston.warn(e);
}
return null;
}

var TWEET_CACHE = {};
var TWEET_CACHE_LEN = 0;

@@ -1116,6 +1171,27 @@ function start_server() {
socket.close();
return;
}

// parse ctoken
var url = urlParse(socket.url, true);
if (url.query && url.query.ctoken) {
var token = decrypt_ctoken(url.query.ctoken);
if (token) {
if (token.ts + 24*60*60*1000 < Date.now()) {
// token expired, ask for a new one?
winston.warn('ctoken: expired');
}
if (ip != token.ip)
winston.info('ctoken: ' + ip + ' != ' + token.ip);
country = token.cc;
}
else {
winston.log('ctoken: invalid from ' + ip);
}
} else {
winston.warn('ctoken: MISSING from ' + ip);
}

var client = new okyaku.Okyaku(socket, ip, country);
socket.on('data', client.on_message.bind(client));
socket.on('close', client.on_close.bind(client));


+ 27
- 0
server/state.js View File

@@ -22,6 +22,7 @@ exports.dbCache = {
funThread: 0,
addresses: {},
ranges: {},
connTokenSecretKey: null,
};

var HOT = exports.hot = {};
@@ -54,6 +55,32 @@ function reload_hot_config(cb) {
});
}

// load the encryption key for connToken
hooks.hook('reloadHot', function (hot, cb) {
var r = global.redis;
var key = 'ctoken-secret-key';
r.get(key, function (err, secretHex) {
if (err) return cb(err);
if (secretHex) {
var secretBytes = new Buffer(secretHex, 'hex');
if (secretBytes.length != 32)
return cb('ctoken secret key is invalid');
HOT.connTokenSecretKey = secretBytes;
return cb(null);
}
// generate a new one
var secretKey = crypto.randomBytes(32);
r.setnx(key, secretKey.toString('hex'), function (err, wasSet) {
if (err) return cb(err);
if (wasSet)
HOT.connTokenSecretKey = secretKey;
else
assert(!!HOT.connTokenSecretKey);
cb(null);
});
});
});

function reload_scripts(cb) {
var json = path.join('state', 'scripts.json');
fs.readFile(json, 'UTF-8', function (err, json) {


Loading…
Cancel
Save