WIP de-nest and restructure

This commit is contained in:
AJ ONeal 2019-05-14 02:16:45 -06:00
parent 2a28dca257
commit 2301966f9f
5 changed files with 671 additions and 535 deletions

File diff suppressed because it is too large Load Diff

View File

@ -525,6 +525,27 @@ controllers.newAccount = function (req, res) {
}); });
}); });
}; };
controllers.acmeAccounts = function (req, res) {
if (!req.jws || !req.jws.verified) {
res.statusCode = 400;
res.send({"error":{"message": "this type of requests must be encoded as a jws payload"
+ " and signed by a known account holder"}});
return;
}
var account;
var accountId = req.params[0];
DB.accounts.some(function (acc) {
// TODO calculate thumbprint from jwk
// find a key with matching jwk
if (acc._id === accountId) {
account = acc;
return true;
}
});
// TODO check that the JWS matches the accountI
console.warn("[warn] account ID still acts as secret, should use JWS kid for verification");
res.send(account);
};
function jsonEggspress(req, res, next) { function jsonEggspress(req, res, next) {
/* /*
@ -1064,6 +1085,10 @@ function handleApi() {
next(); next();
} }
// TODO convert /acme/accounts/:account_id into a regex
app.get(/^\/acme\/accounts\/([\w]+)/, controllers.acmeAccounts);
// POST-as-GET
app.post(/^\/acme\/accounts\/([\w]+)/, controllers.acmeAccounts);
app.use(/\b(relay)\b/, mustTrust, controllers.relay); app.use(/\b(relay)\b/, mustTrust, controllers.relay);
app.get(/\b(config)\b/, mustTrust, getConfigOnly); app.get(/\b(config)\b/, mustTrust, getConfigOnly);
app.use(/\b(init|config)\b/, mustTrust, initOrConfig); app.use(/\b(init|config)\b/, mustTrust, initOrConfig);

View File

@ -186,29 +186,31 @@ var telebitState = {};
var appMethods = { var appMethods = {
initialize: function () { initialize: function () {
console.log("call initialize"); console.log("call initialize");
if (!appData.init.relay) { return requestAccountHelper().then(function (/*key*/) {
appData.init.relay = DEFAULT_RELAY; if (!appData.init.relay) {
} appData.init.relay = DEFAULT_RELAY;
appData.init.relay = appData.init.relay.toLowerCase();
telebitState = { relay: appData.init.relay };
return Telebit.api.directory(telebitState).then(function (dir) {
if (!dir.api_host) {
window.alert("Error: '" + telebitState.relay + "' does not appear to be a valid telebit service");
return;
} }
appData.init.relay = appData.init.relay.toLowerCase();
telebitState = { relay: appData.init.relay };
telebitState.dir = dir; return Telebit.api.directory(telebitState).then(function (dir) {
if (!dir.api_host) {
window.alert("Error: '" + telebitState.relay + "' does not appear to be a valid telebit service");
return;
}
// If it's one of the well-known relays telebitState.dir = dir;
if (-1 !== TELEBIT_RELAYS.indexOf(appData.init.relay)) {
return doConfigure(); // If it's one of the well-known relays
} else { if (-1 !== TELEBIT_RELAYS.indexOf(appData.init.relay)) {
changeState('advanced'); return doConfigure();
} } else {
}).catch(function (err) { changeState('advanced');
console.error(err); }
window.alert("Error: [initialize] " + (err.message || JSON.stringify(err, null, 2))); }).catch(function (err) {
console.error(err);
window.alert("Error: [initialize] " + (err.message || JSON.stringify(err, null, 2)));
});
}); });
} }
, advance: function () { , advance: function () {
@ -473,54 +475,84 @@ new Vue({
, methods: appMethods , methods: appMethods
}); });
function run(key) { function requestAccountHelper() {
api._key = key; function reset() {
// 😁 1. Get ACME directory changeState('setup');
// 😁 2. Fetch ACME account setState();
// 3. Test if account has access }
// 4. Show command line auth instructions to auth return new Promise(function (resolve) {
// 5. Sign requests / use JWT appData.init.email = localStorage.getItem('email');
// 6. Enforce token required for config, status, etc if (!appData.init.email) {
// 7. Move admin interface to standard ports (admin.foo-bar-123.telebit.xyz) // don't resolve
api.config().then(function (config) { reset();
telebitState.config = config;
if (config.greenlock) {
appData.init.acmeServer = config.greenlock.server;
}
if (config.relay) {
appData.init.relay = config.relay;
}
if (config.email) {
appData.init.email = config.email;
}
if (config.agreeTos) {
appData.init.letos = config.agreeTos;
appData.init.teletos = config.agreeTos;
}
if (config._otp) {
appData.init.otp = config._otp;
}
telebitState.pollUrl = config._pollUrl || localStorage.getItem('poll_url');
if ((!config.token && !config._otp) || !config.relay || !config.email || !config.agreeTos) {
changeState('setup');
setState();
return; return;
} }
if (!config.token && config._otp) { return requestAccount(appData.init.email).then(function (key) {
changeState('otp'); if (!key) { throw new Error("[SANITY] Error: completed without key"); }
setState(); resolve(key);
// this will skip ahead as necessary }).catch(function (err) {
return Telebit.authorize(telebitState, showOtp).then(function () { appData.init.email = "";
return changeState('status'); localStorage.removeItem('email');
}); console.error(err);
} window.alert("something went wrong");
// don't resolve
reset();
});
});
}
// TODO handle default state function run() {
changeState('status'); return requestAccountHelper().then(function (key) {
}).catch(function (err) { api._key = key;
appData.views.flash.error = err.message || JSON.stringify(err, null, 2); // TODO create session instance of Telebit
Telebit._key = key;
// 😁 1. Get ACME directory
// 😁 2. Fetch ACME account
// 3. Test if account has access
// 4. Show command line auth instructions to auth
// 😁 5. Sign requests / use JWT
// 😁 6. Enforce token required for config, status, etc
// 7. Move admin interface to standard ports (admin.foo-bar-123.telebit.xyz)
api.config().then(function (config) {
telebitState.config = config;
if (config.greenlock) {
appData.init.acmeServer = config.greenlock.server;
}
if (config.relay) {
appData.init.relay = config.relay;
}
if (config.email) {
appData.init.email = config.email;
}
if (config.agreeTos) {
appData.init.letos = config.agreeTos;
appData.init.teletos = config.agreeTos;
}
if (config._otp) {
appData.init.otp = config._otp;
}
telebitState.pollUrl = config._pollUrl || localStorage.getItem('poll_url');
if ((!config.token && !config._otp) || !config.relay || !config.email || !config.agreeTos) {
changeState('setup');
setState();
return;
}
if (!config.token && config._otp) {
changeState('otp');
setState();
// this will skip ahead as necessary
return Telebit.authorize(telebitState, showOtp).then(function () {
return changeState('status');
});
}
// TODO handle default state
changeState('status');
}).catch(function (err) {
appData.views.flash.error = err.message || JSON.stringify(err, null, 2);
});
}); });
} }
@ -543,46 +575,34 @@ function getKey() {
}); });
} }
function getEmail() { function requestAccount(email) {
return Promise.resolve().then(function () {
var email = localStorage.getItem('email');
if (email) { return email; }
while (!email) {
email = window.prompt("Email address (device owner)?");
}
return email;
});
}
function requestAccount() {
return getKey().then(function (jwk) { return getKey().then(function (jwk) {
return getEmail().then(function(email) { // creates new or returns existing
// creates new or returns existing var acme = ACME.create({});
var acme = ACME.create({}); var url = window.location.protocol + '//' + window.location.host + '/acme/directory';
var url = window.location.protocol + '//' + window.location.host + '/acme/directory'; return acme.init(url).then(function () {
return acme.init(url).then(function () { return acme.accounts.create({
return acme.accounts.create({ agreeToTerms: function (tos) { return tos; }
agreeToTerms: function (tos) { return tos; } , accountKeypair: { privateKeyJwk: jwk }
, accountKeypair: { privateKeyJwk: jwk } , email: email
, email: email }).then(function (account) {
}).then(function (account) { console.log('account:');
console.log('account:'); console.log(account);
console.log(account); if (account.id) {
if (account.id) { localStorage.setItem('email', email);
localStorage.setItem('email', email); }
} return jwk;
return jwk;
});
}); });
}); });
}); });
} }
window.api = api; window.api = api;
requestAccount().then(function (jwk) { run();
run(jwk); setTimeout(function () {
setTimeout(function () { document.body.hidden = false;
document.body.hidden = false; }, 50);
}, 50);
});
// Debug
window.changeState = changeState;
}()); }());

View File

@ -34,11 +34,12 @@ module.exports = function eggspress() {
} }
var urlstr = (req.url.replace(/\/$/, '') + '/'); var urlstr = (req.url.replace(/\/$/, '') + '/');
if (!urlstr.match(todo[0])) { var match = urlstr.match(todo[0]);
if (!match) {
//console.log("[eggspress] pattern doesn't match", todo[0], req.url); //console.log("[eggspress] pattern doesn't match", todo[0], req.url);
next(); next();
return; return;
} else if ('string' === typeof todo[0] && 0 !== urlstr.match(todo[0]).index) { } else if ('string' === typeof todo[0] && 0 !== match.index) {
//console.log("[eggspress] string pattern is not the start", todo[0], req.url); //console.log("[eggspress] string pattern is not the start", todo[0], req.url);
next(); next();
return; return;
@ -58,6 +59,7 @@ module.exports = function eggspress() {
} }
var fns = todo[1].slice(0); var fns = todo[1].slice(0);
req.params = match.slice(1);
function nextTodo(err) { function nextTodo(err) {
if (err) { fail(err); return; } if (err) { fail(err); return; }

View File

@ -93,26 +93,45 @@ module.exports.create = function (state) {
} }
return reqOpts; return reqOpts;
}; };
RC.createErrorHandler = function (replay, opts, cb) { RC.createRelauncher = function (replay, opts, cb) {
return function (err) { return function (err) {
// ENOENT - never started, cleanly exited last start, or creating socket at a different path /*global Promise*/
// ECONNREFUSED - leftover socket just needs to be restarted var p = new Promise(function (resolve, reject) {
if ('ENOENT' === err.code || 'ECONNREFUSED' === err.code) { // ENOENT - never started, cleanly exited last start, or creating socket at a different path
if (opts._taketwo) { // ECONNREFUSED - leftover socket just needs to be restarted
cb(err); if ('ENOENT' !== err.code && 'ECONNREFUSED' !== err.code) {
reject(err);
return; return;
} }
// retried and failed again: quit
if (opts._taketwo) {
reject(err);
return;
}
require('../../usr/share/install-launcher.js').install({ env: process.env }, function (err) { require('../../usr/share/install-launcher.js').install({ env: process.env }, function (err) {
if (err) { cb(err); return; } if (err) { reject(err); return; }
opts._taketwo = true; opts._taketwo = true;
setTimeout(function () { setTimeout(function () {
replay(opts, cb); if (replay.length <= 1) {
replay(opts).then(resolve).catch(reject);
return;
} else {
replay(opts, function (err, res) {
if (err) { reject(err); }
else { resolve(res); }
});
return;
}
}, 2500); }, 2500);
}); });
return; return;
});
if (cb) {
p.then(function () { cb(null); }).catch(function (err) { cb(err); });
} }
return p;
cb(err);
}; };
}; };
RC.request = function request(opts, fn) { RC.request = function request(opts, fn) {
@ -141,7 +160,8 @@ module.exports.create = function (state) {
makeResponder(service, resp, fn); makeResponder(service, resp, fn);
}); });
req.on('error', RC.createErrorHandler(RC.request, opts, fn)); var errHandler = RC.createRelauncher(RC.request, opts, fn);
req.on('error', errHandler);
// Simple GET // Simple GET
if ('POST' !== method || !opts.data) { if ('POST' !== method || !opts.data) {