diff --git a/migrations/20220202011148-blobs-migrate-token-secret.js b/migrations/20220202011148-blobs-migrate-token-secret.js new file mode 100644 index 000000000..489eaaac1 --- /dev/null +++ b/migrations/20220202011148-blobs-migrate-token-secret.js @@ -0,0 +1,19 @@ +'use strict'; + +const safe = require('safetydance'); + +const PROXY_AUTH_TOKEN_SECRET_FILE = '/home/yellowtent/platformdata/proxy-auth-token-secret'; + +exports.up = function (db, callback) { + const token = safe.fs.readFileSync(PROXY_AUTH_TOKEN_SECRET_FILE); + if (!token) return callback(); + db.runSql('INSERT INTO blobs (id, value) VALUES (?, ?)', [ 'proxy_auth_token_secret', token ], function (error) { + if (error) return callback(error); + safe.fs.unlinkSync(PROXY_AUTH_TOKEN_SECRET_FILE); + callback(); + }); +}; + +exports.down = function(db, callback) { + callback(); +}; diff --git a/src/blobs.js b/src/blobs.js index f0e6fc77c..35cc13e39 100644 --- a/src/blobs.js +++ b/src/blobs.js @@ -4,13 +4,16 @@ exports = module.exports = { get, + getString, set, + setString, del, ACME_ACCOUNT_KEY: 'acme_account_key', ADDON_TURN_SECRET: 'addon_turn_secret', SFTP_PUBLIC_KEY: 'sftp_public_key', SFTP_PRIVATE_KEY: 'sftp_private_key', + PROXY_AUTH_TOKEN_SECRET: 'proxy_auth_token_secret', CERT_PREFIX: 'cert', @@ -30,6 +33,14 @@ async function get(id) { return result[0].value; } +async function getString(id) { + assert.strictEqual(typeof id, 'string'); + + const result = await database.query(`SELECT ${BLOBS_FIELDS} FROM blobs WHERE id = ?`, [ id ]); + if (result.length === 0) return null; + return result[0].value.toString('utf8'); +} + async function set(id, value) { assert.strictEqual(typeof id, 'string'); assert(value === null || Buffer.isBuffer(value)); @@ -37,6 +48,13 @@ async function set(id, value) { await database.query('INSERT INTO blobs (id, value) VALUES (?, ?) ON DUPLICATE KEY UPDATE value=VALUES(value)', [ id, value ]); } +async function setString(id, value) { + assert.strictEqual(typeof id, 'string'); + assert(value === null || typeof value === 'string'); + + await database.query('INSERT INTO blobs (id, value) VALUES (?, ?) ON DUPLICATE KEY UPDATE value=VALUES(value)', [ id, Buffer.from(value) ]); +} + async function del(id) { await database.query('DELETE FROM blobs WHERE id=?', [ id ]); } diff --git a/src/paths.js b/src/paths.js index 9748eaa42..ec57e2d5f 100644 --- a/src/paths.js +++ b/src/paths.js @@ -42,7 +42,6 @@ exports = module.exports = { DYNDNS_INFO_FILE: path.join(baseDir(), 'platformdata/dyndns-info.json'), DHPARAMS_FILE: path.join(baseDir(), 'platformdata/dhparams.pem'), FEATURES_INFO_FILE: path.join(baseDir(), 'platformdata/features-info.json'), - PROXY_AUTH_TOKEN_SECRET_FILE: path.join(baseDir(), 'platformdata/proxy-auth-token-secret'), VERSION_FILE: path.join(baseDir(), 'platformdata/VERSION'), SSHFS_KEYS_DIR: path.join(baseDir(), 'platformdata/sshfs'), SFTP_KEYS_DIR: path.join(baseDir(), 'platformdata/sftp/ssh'), diff --git a/src/proxyauth.js b/src/proxyauth.js index fdc0b76d2..cbe091852 100644 --- a/src/proxyauth.js +++ b/src/proxyauth.js @@ -1,5 +1,7 @@ 'use strict'; +const blobs = require('./blobs.js'); + // heavily inspired from https://gock.net/blog/2020/nginx-subrequest-authentication-server/ and https://github.com/andygock/auth-server exports = module.exports = { @@ -14,7 +16,6 @@ const apps = require('./apps.js'), debug = require('debug')('box:proxyAuth'), ejs = require('ejs'), express = require('express'), - fs = require('fs'), hat = require('./hat.js'), http = require('http'), HttpError = require('connect-lastmile').HttpError, @@ -31,16 +32,16 @@ const apps = require('./apps.js'), util = require('util'); let gHttpServer = null; -let TOKEN_SECRET = null; +let gTokenSecret = null; function jwtVerify(req, res, next) { const token = req.cookies.authToken; if (!token) return next(); - jwt.verify(token, TOKEN_SECRET, function (error, decoded) { + jwt.verify(token, gTokenSecret, function (error, decoded) { if (error) { - debug('clearing token', error); + debug('jwtVerify: clearing token', error); res.clearCookie('authToken'); return next(new HttpError(403, 'Malformed token or bad signature')); } @@ -95,7 +96,7 @@ async function loginPage(req, res, next) { try { finalContent = ejs.render(translatedContent, { title, icon, dashboardOrigin }); } catch (e) { - debug('Error rendering proxyauth-login.ejs', e); + debug('loginPage: Error rendering proxyauth-login.ejs', e); return next(new HttpError(500, 'Login template error')); } @@ -123,7 +124,7 @@ function auth(req, res, next) { } // user is already authenticated, refresh cookie - const token = jwt.sign({ user: req.user }, TOKEN_SECRET, { expiresIn: `${constants.DEFAULT_TOKEN_EXPIRATION_DAYS}d` }); + const token = jwt.sign({ user: req.user }, gTokenSecret, { expiresIn: `${constants.DEFAULT_TOKEN_EXPIRATION_DAYS}d` }); res.cookie('authToken', token, { httpOnly: true, @@ -171,7 +172,7 @@ async function authorize(req, res, next) { if (!apps.canAccess(app, req.user)) return next(new HttpError(403, 'Forbidden' )); - const token = jwt.sign({ user: users.removePrivateFields(req.user) }, TOKEN_SECRET, { expiresIn: `${constants.DEFAULT_TOKEN_EXPIRATION_DAYS}d` }); + const token = jwt.sign({ user: users.removePrivateFields(req.user) }, gTokenSecret, { expiresIn: `${constants.DEFAULT_TOKEN_EXPIRATION_DAYS}d` }); res.cookie('authToken', token, { httpOnly: true, @@ -251,11 +252,11 @@ function initializeAuthwallExpressSync() { async function start() { assert.strictEqual(gHttpServer, null, 'Authwall is already up and running.'); - if (!fs.existsSync(paths.PROXY_AUTH_TOKEN_SECRET_FILE)) { - TOKEN_SECRET = hat(64); - fs.writeFileSync(paths.PROXY_AUTH_TOKEN_SECRET_FILE, TOKEN_SECRET, 'utf8'); - } else { - TOKEN_SECRET = fs.readFileSync(paths.PROXY_AUTH_TOKEN_SECRET_FILE, 'utf8').trim(); + gTokenSecret = await blobs.getString(blobs.PROXY_AUTH_TOKEN_SECRET); + if (!gTokenSecret) { + debug('start: generating new token secret'); + gTokenSecret = hat(64); + await blobs.setString(blobs.PROXY_AUTH_TOKEN_SECRET, gTokenSecret); } gHttpServer = initializeAuthwallExpressSync();