WIP de-nest and restructure
This commit is contained in:
parent
2a28dca257
commit
2301966f9f
|
@ -1,12 +1,14 @@
|
|||
#!/usr/bin/env node
|
||||
(function () {
|
||||
'use strict';
|
||||
/*global Promise*/
|
||||
|
||||
var pkg = require('../package.json');
|
||||
var os = require('os');
|
||||
|
||||
//var url = require('url');
|
||||
var fs = require('fs');
|
||||
var util = require('util');
|
||||
var path = require('path');
|
||||
//var https = require('https');
|
||||
var YAML = require('js-yaml');
|
||||
|
@ -104,7 +106,9 @@ if (!confpath || /^--/.test(confpath)) {
|
|||
process.exit(1);
|
||||
}
|
||||
|
||||
function askForConfig(state, mainCb) {
|
||||
var Console = {};
|
||||
Console.setup = function (state) {
|
||||
if (Console.rl) { return; }
|
||||
var fs = require('fs');
|
||||
var ttyname = '/dev/tty';
|
||||
var stdin = useTty ? fs.createReadStream(ttyname, {
|
||||
|
@ -119,6 +123,34 @@ function askForConfig(state, mainCb) {
|
|||
, terminal: !/^win/i.test(os.platform()) && !useTty
|
||||
});
|
||||
state._useTty = useTty;
|
||||
Console.rl = rl;
|
||||
};
|
||||
Console.teardown = function () {
|
||||
// https://github.com/nodejs/node/issues/21319
|
||||
if (useTty) { try { Console.stdin.push(null); } catch(e) { /*ignore*/ } }
|
||||
Console.rl.close();
|
||||
if (useTty) { try { Console.stdin.close(); } catch(e) { /*ignore*/ } }
|
||||
Console.rl = null;
|
||||
};
|
||||
|
||||
function askEmail(cb) {
|
||||
Console.setup();
|
||||
if (state.config.email) { cb(); return; }
|
||||
console.info(TPLS.remote.setup.email);
|
||||
// TODO attempt to read email from npmrc or the like?
|
||||
Console.rl.question('email: ', function (email) {
|
||||
// TODO validate email domain
|
||||
email = /@/.test(email) && email.trim();
|
||||
if (!email) { askEmail(cb); return; }
|
||||
state.config.email = email.trim();
|
||||
state.config.agreeTos = true;
|
||||
console.info("");
|
||||
setTimeout(cb, 250);
|
||||
});
|
||||
}
|
||||
|
||||
function askForConfig(state, mainCb) {
|
||||
Console.setup(state);
|
||||
|
||||
// NOTE: Use of setTimeout
|
||||
// We're using setTimeout just to make the user experience a little
|
||||
|
@ -128,19 +160,7 @@ function askForConfig(state, mainCb) {
|
|||
// <= 100ms is shorter than normal human reaction time (ability to place events chronologically, which happened first)
|
||||
// ~ 150-250ms is the sweet spot for most humans (long enough to notice change and not be jarred, but stay on task)
|
||||
var firstSet = [
|
||||
function askEmail(cb) {
|
||||
if (state.config.email) { cb(); return; }
|
||||
console.info(TPLS.remote.setup.email);
|
||||
// TODO attempt to read email from npmrc or the like?
|
||||
rl.question('email: ', function (email) {
|
||||
email = /@/.test(email) && email.trim();
|
||||
if (!email) { askEmail(cb); return; }
|
||||
state.config.email = email.trim();
|
||||
state.config.agreeTos = true;
|
||||
console.info("");
|
||||
setTimeout(cb, 250);
|
||||
});
|
||||
}
|
||||
askEmail
|
||||
, function askRelay(cb) {
|
||||
function checkRelay(relay) {
|
||||
// TODO parse and check https://{{relay}}/.well-known/telebit.cloud/directives.json
|
||||
|
@ -173,7 +193,7 @@ function askForConfig(state, mainCb) {
|
|||
console.info("");
|
||||
console.info("What relay will you be using? (press enter for default)");
|
||||
console.info("");
|
||||
rl.question('relay [default: telebit.cloud]: ', checkRelay);
|
||||
Console.rl.question('relay [default: telebit.cloud]: ', checkRelay);
|
||||
}
|
||||
, function checkRelay(cb) {
|
||||
nextSet = [];
|
||||
|
@ -201,7 +221,7 @@ function askForConfig(state, mainCb) {
|
|||
console.info("");
|
||||
console.info("Type 'y' or 'yes' to accept these Terms of Service.");
|
||||
console.info("");
|
||||
rl.question('agree to all? [y/N]: ', function (resp) {
|
||||
Console.rl.question('agree to all? [y/N]: ', function (resp) {
|
||||
resp = resp.trim();
|
||||
if (!/^y(es)?$/i.test(resp) && 'true' !== resp) {
|
||||
throw new Error("You didn't accept the Terms of Service... not sure what to do...");
|
||||
|
@ -219,7 +239,7 @@ function askForConfig(state, mainCb) {
|
|||
console.info("");
|
||||
console.info("What updates would you like to receive? (" + options.join(',') + ")");
|
||||
console.info("");
|
||||
rl.question('messages (default: important): ', function (updates) {
|
||||
Console.rl.question('messages (default: important): ', function (updates) {
|
||||
state._updates = (updates || '').trim().toLowerCase();
|
||||
if (!state._updates) { state._updates = 'important'; }
|
||||
if (-1 === options.indexOf(state._updates)) { askUpdates(cb); return; }
|
||||
|
@ -240,7 +260,7 @@ function askForConfig(state, mainCb) {
|
|||
console.info("");
|
||||
console.info("Contribute project telemetry data? (press enter for default [yes])");
|
||||
console.info("");
|
||||
rl.question('telemetry [Y/n]: ', function (telemetry) {
|
||||
Console.rl.question('telemetry [Y/n]: ', function (telemetry) {
|
||||
if (!telemetry || /^y(es)?$/i.test(telemetry)) {
|
||||
state.config.telemetry = true;
|
||||
}
|
||||
|
@ -263,7 +283,7 @@ function askForConfig(state, mainCb) {
|
|||
console.info("\tShared Secret (HMAC hex)");
|
||||
//console.info("\tPrivate key (hex)");
|
||||
console.info("");
|
||||
rl.question('auth: ', function (resp) {
|
||||
Console.rl.question('auth: ', function (resp) {
|
||||
resp = (resp || '').trim();
|
||||
try {
|
||||
JWT.decode(resp);
|
||||
|
@ -291,7 +311,7 @@ function askForConfig(state, mainCb) {
|
|||
console.info("What servername(s) will you be relaying here?");
|
||||
console.info("(use a comma-separated list such as example.com,example.net)");
|
||||
console.info("");
|
||||
rl.question('domain(s): ', function (resp) {
|
||||
Console.rl.question('domain(s): ', function (resp) {
|
||||
resp = (resp || '').trim().split(/,/g);
|
||||
if (!resp.length) { askServernames(); return; }
|
||||
// TODO validate the domains
|
||||
|
@ -306,7 +326,7 @@ function askForConfig(state, mainCb) {
|
|||
console.info("What tcp port(s) will you be relaying here?");
|
||||
console.info("(use a comma-separated list such as 2222,5050)");
|
||||
console.info("");
|
||||
rl.question('port(s) [default:none]: ', function (resp) {
|
||||
Console.rl.question('port(s) [default:none]: ', function (resp) {
|
||||
resp = (resp || '').trim().split(/,/g);
|
||||
if (!resp.length) { askPorts(); return; }
|
||||
// TODO validate the domains
|
||||
|
@ -320,10 +340,7 @@ function askForConfig(state, mainCb) {
|
|||
function next() {
|
||||
var q = nextSet.shift();
|
||||
if (!q) {
|
||||
// https://github.com/nodejs/node/issues/21319
|
||||
if (useTty) { try { stdin.push(null); } catch(e) { /*ignore*/ } }
|
||||
rl.close();
|
||||
if (useTty) { try { stdin.close(); } catch(e) { /*ignore*/ } }
|
||||
Console.teardown();
|
||||
mainCb(null, state);
|
||||
return;
|
||||
}
|
||||
|
@ -335,8 +352,76 @@ function askForConfig(state, mainCb) {
|
|||
|
||||
var RC;
|
||||
|
||||
function parseConfig(err, text) {
|
||||
function handleConfig(config) {
|
||||
function bootstrap(opts) {
|
||||
state.key = opts.key;
|
||||
// Create / retrieve account (sign-in, more or less)
|
||||
// TODO hit directory resource /.well-known/openid-configuration -> acme_uri (?)
|
||||
// Occassionally rotate the key just for the sake of testing the key rotation
|
||||
return urequestAsync({
|
||||
method: 'HEAD'
|
||||
, url: RC.resolve('/acme/new-nonce')
|
||||
, headers: { "User-Agent": 'Telebit/' + pkg.version }
|
||||
}).then(function (resp) {
|
||||
var nonce = resp.headers['replay-nonce'];
|
||||
var newAccountUrl = RC.resolve('/acme/new-acct');
|
||||
var contact = [];
|
||||
if (opts.email) {
|
||||
contact.push("mailto:" + opts.email);
|
||||
}
|
||||
return keypairs.signJws({
|
||||
jwk: state.key
|
||||
, protected: {
|
||||
// alg will be filled out automatically
|
||||
jwk: state.pub
|
||||
, kid: false
|
||||
, nonce: nonce
|
||||
, url: newAccountUrl
|
||||
}
|
||||
, payload: JSON.stringify({
|
||||
// We can auto-agree here because the client is the user agent of the primary user
|
||||
termsOfServiceAgreed: true
|
||||
, contact: contact // I don't think we have email yet...
|
||||
, onlyReturnExisting: opts.onlyReturnExisting || !opts.email
|
||||
//, externalAccountBinding: null
|
||||
})
|
||||
}).then(function (jws) {
|
||||
return urequestAsync({
|
||||
url: newAccountUrl
|
||||
, method: 'POST'
|
||||
, json: jws // TODO default to post when body is present
|
||||
, headers: {
|
||||
"Content-Type": 'application/jose+json'
|
||||
, "User-Agent": 'Telebit/' + pkg.version
|
||||
}
|
||||
}).then(function (resp) {
|
||||
//nonce = resp.headers['replay-nonce'];
|
||||
if (!resp.body || 'valid' !== resp.body.status) {
|
||||
console.error('request jws:', jws);
|
||||
console.error('response:');
|
||||
console.error(resp.headers);
|
||||
console.error(resp.body);
|
||||
throw new Error("did not successfully create or restore account");
|
||||
}
|
||||
return resp;
|
||||
});
|
||||
});
|
||||
}).catch(RC.createRelauncher(bootstrap._replay(opts), bootstrap._bootstate)).catch(function (err) {
|
||||
console.error(err);
|
||||
process.exit(17);
|
||||
});
|
||||
}
|
||||
bootstrap._bootstate = {};
|
||||
bootstrap._replay = function (_opts) {
|
||||
return function (opts) {
|
||||
// supply opts to match reverse signature (.length checking)
|
||||
opts = _opts;
|
||||
return bootstrap(opts);
|
||||
};
|
||||
};
|
||||
|
||||
function handleConfig(config) {
|
||||
var _config = state.config || {};
|
||||
|
||||
state.config = config;
|
||||
var verstrd = [ pkg.name + ' daemon v' + state.config.version ];
|
||||
if (state.config.version && state.config.version !== pkg.version) {
|
||||
|
@ -345,6 +430,10 @@ function parseConfig(err, text) {
|
|||
console.info(verstr.join(' '));
|
||||
}
|
||||
|
||||
if (!state.config.email && _config) {
|
||||
state.config.email = _config.email;
|
||||
}
|
||||
|
||||
//
|
||||
// check for init first, before anything else
|
||||
// because it has arguments that may help in
|
||||
|
@ -408,9 +497,9 @@ function parseConfig(err, text) {
|
|||
|
||||
//console.log("no questioning:");
|
||||
parseCli(state);
|
||||
}
|
||||
}
|
||||
|
||||
function parseCli(/*state*/) {
|
||||
function parseCli(/*state*/) {
|
||||
var special = [
|
||||
'false', 'none', 'off', 'disable'
|
||||
, 'true', 'auto', 'on', 'enable'
|
||||
|
@ -462,42 +551,9 @@ function parseConfig(err, text) {
|
|||
|
||||
help();
|
||||
process.exit(11);
|
||||
}
|
||||
try {
|
||||
state._clientConfig = JSON.parse(text || '{}');
|
||||
} catch(e1) {
|
||||
try {
|
||||
state._clientConfig = YAML.safeLoad(text || '{}');
|
||||
} catch(e2) {
|
||||
try {
|
||||
state._clientConfig = TOML.parse(text || '');
|
||||
} catch(e3) {
|
||||
console.error(e1.message);
|
||||
console.error(e2.message);
|
||||
process.exit(1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state._clientConfig = camelCopy(state._clientConfig || {}) || {};
|
||||
RC = require('../lib/rc/index.js').create(state);
|
||||
RC.requestAsync = require('util').promisify(RC.request);
|
||||
|
||||
if (!Object.keys(state._clientConfig).length) {
|
||||
console.info('(' + state._ipc.comment + ": " + state._ipc.path + ')');
|
||||
console.info("");
|
||||
}
|
||||
|
||||
if ((err && 'ENOENT' === err.code) || !Object.keys(state._clientConfig).length) {
|
||||
if (!err || 'ENOENT' === err.code) {
|
||||
//console.warn("Empty config file. Run 'telebit init' to configure.\n");
|
||||
} else {
|
||||
console.warn("Couldn't load config:\n\n\t" + err.message + "\n");
|
||||
}
|
||||
}
|
||||
|
||||
function handleRemoteRequest(service, fn) {
|
||||
function handleRemoteRequest(service, fn) {
|
||||
return function (err, body) {
|
||||
if ('function' === typeof fn) {
|
||||
fn(err, body); // XXX was resp
|
||||
|
@ -567,9 +623,9 @@ function parseConfig(err, text) {
|
|||
console.info();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function getToken(fn) {
|
||||
function getToken(fn) {
|
||||
state.relay = state.config.relay;
|
||||
|
||||
// { _otp, config: {} }
|
||||
|
@ -671,77 +727,28 @@ function parseConfig(err, text) {
|
|||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var bootState = {};
|
||||
function bootstrap() {
|
||||
// Create / retrieve account (sign-in, more or less)
|
||||
// TODO hit directory resource /.well-known/openid-configuration -> acme_uri (?)
|
||||
// Occassionally rotate the key just for the sake of testing the key rotation
|
||||
return urequestAsync({
|
||||
method: 'HEAD'
|
||||
, url: RC.resolve('/acme/new-nonce')
|
||||
, headers: { "User-Agent": 'Telebit/' + pkg.version }
|
||||
}).then(function (resp) {
|
||||
var nonce = resp.headers['replay-nonce'];
|
||||
var newAccountUrl = RC.resolve('/acme/new-acct');
|
||||
return keypairs.signJws({
|
||||
jwk: state.key
|
||||
, protected: {
|
||||
// alg will be filled out automatically
|
||||
jwk: state.pub
|
||||
, kid: false
|
||||
, nonce: nonce
|
||||
, url: newAccountUrl
|
||||
}
|
||||
, payload: JSON.stringify({
|
||||
// We can auto-agree here because the client is the user agent of the primary user
|
||||
termsOfServiceAgreed: true
|
||||
, contact: [] // I don't think we have email yet...
|
||||
//, externalAccountBinding: null
|
||||
})
|
||||
}).then(function (jws) {
|
||||
return urequestAsync({
|
||||
url: newAccountUrl
|
||||
, method: 'POST'
|
||||
, json: jws // TODO default to post when body is present
|
||||
, headers: {
|
||||
"Content-Type": 'application/jose+json'
|
||||
, "User-Agent": 'Telebit/' + pkg.version
|
||||
}
|
||||
}).then(function (resp) {
|
||||
//nonce = resp.headers['replay-nonce'];
|
||||
if (!resp.body || 'valid' !== resp.body.status) {
|
||||
console.error('request jws:', jws);
|
||||
console.error('response:');
|
||||
console.error(resp.headers);
|
||||
console.error(resp.body);
|
||||
throw new Error("did not successfully create or restore account");
|
||||
}
|
||||
return RC.requestAsync({ service: 'config', method: 'GET' }).catch(function (err) {
|
||||
if (err) {
|
||||
if ('ENOENT' === err.code || 'ECONNREFUSED' === err.code) {
|
||||
console.error("Either the telebit service was not already (and could not be started) or its socket could not be written to.");
|
||||
console.error(err);
|
||||
} else if ('ENOTSOCK' === err.code) {
|
||||
console.error(err);
|
||||
return;
|
||||
} else {
|
||||
console.error(err);
|
||||
}
|
||||
process.exit(101);
|
||||
function parseConfig(text) {
|
||||
var _clientConfig;
|
||||
try {
|
||||
_clientConfig = JSON.parse(text || '{}');
|
||||
} catch(e1) {
|
||||
try {
|
||||
_clientConfig = YAML.safeLoad(text || '{}');
|
||||
} catch(e2) {
|
||||
try {
|
||||
_clientConfig = TOML.parse(text || '');
|
||||
} catch(e3) {
|
||||
console.error(e1.message);
|
||||
console.error(e2.message);
|
||||
process.exit(1);
|
||||
return;
|
||||
}
|
||||
}).then(handleConfig);
|
||||
});
|
||||
});
|
||||
}).catch(RC.createErrorHandler(bootstrap, bootState, function (err) {
|
||||
console.error(err);
|
||||
process.exit(17);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
bootstrap();
|
||||
return camelCopy(_clientConfig || {}) || {};
|
||||
}
|
||||
|
||||
var parsers = {
|
||||
|
@ -821,12 +828,30 @@ var parsers = {
|
|||
}
|
||||
};
|
||||
|
||||
var keystore = require('../lib/keystore.js').create(state);
|
||||
state.keystore = keystore;
|
||||
state.keystoreSecure = !keystore.insecure;
|
||||
keystore.all().then(function (list) {
|
||||
//
|
||||
// Start by reading the config file, before all else
|
||||
//
|
||||
util.promisify(fs.readFile)(confpath, 'utf8').catch(function (err) {
|
||||
if (err && 'ENOENT' !== err.code) {
|
||||
console.warn("Couldn't load config:\n\n\t" + err.message + "\n");
|
||||
}
|
||||
}).then(function (text) {
|
||||
state._clientConfig = parseConfig(text);
|
||||
RC = require('../lib/rc/index.js').create(state); // adds state._ipc
|
||||
if (!Object.keys(state._clientConfig).length) {
|
||||
console.info('(' + state._ipc.comment + ": " + state._ipc.path + ')');
|
||||
console.info("");
|
||||
}
|
||||
RC.requestAsync = require('util').promisify(RC.request);
|
||||
}).then(function () {
|
||||
var keystore = require('../lib/keystore.js').create(state);
|
||||
state.keystore = keystore;
|
||||
state.keystoreSecure = !keystore.insecure;
|
||||
keystore.all().then(function (list) {
|
||||
var keyext = '.key.jwk.json';
|
||||
var key;
|
||||
var p;
|
||||
|
||||
// TODO create map by account and index into that map to get the master key
|
||||
// and sort keys in the process
|
||||
list.some(function (el) {
|
||||
|
@ -838,21 +863,65 @@ keystore.all().then(function (list) {
|
|||
});
|
||||
|
||||
if (key) {
|
||||
state.key = key;
|
||||
state.pub = keypairs.neuter({ jwk: key });
|
||||
fs.readFile(confpath, 'utf8', parseConfig);
|
||||
return;
|
||||
}
|
||||
|
||||
return keypairs.generate().then(function (pair) {
|
||||
p = Promise.resolve(key);
|
||||
} else {
|
||||
p = keypairs.generate().then(function (pair) {
|
||||
var jwk = pair.private;
|
||||
return keypairs.thumbprint({ jwk: jwk }).then(function (kid) {
|
||||
jwk.kid = kid;
|
||||
return keystore.set(kid + keyext, jwk).then(function () {
|
||||
var size = (jwk.crv || Buffer.from(jwk.n, 'base64').byteLength * 8);
|
||||
jwk.kid = kid;
|
||||
console.info("Generated new %s %s private key with thumbprint %s", jwk.kty, size, kid);
|
||||
state.key = jwk;
|
||||
fs.readFile(confpath, 'utf8', parseConfig);
|
||||
return keystore.set(kid + keyext, jwk).then(function () {
|
||||
return jwk;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return p.then(function (key) {
|
||||
state.key = key;
|
||||
state.pub = keypairs.neuter({ jwk: key });
|
||||
// we don't have config yet
|
||||
state.config = {};
|
||||
return bootstrap({ key: state.key, onlyReturnExisting: true }).catch(function (err) {
|
||||
console.error("[DEBUG] local account not created?");
|
||||
console.error(err);
|
||||
// Ask for email address. The prior email may have been bad
|
||||
return require('util').promisify(askEmail).then(function (email) {
|
||||
return bootstrap({ key: state.key, email: email });
|
||||
});
|
||||
}).catch(function (err) {
|
||||
console.error(err);
|
||||
console.error("You may need to go into the web interface and allow Telebit Client by ID '" + key.kid + "'");
|
||||
process.exit(10);
|
||||
}).then(function (result) {
|
||||
//#console.log("Telebit Account Bootstrap result:");
|
||||
//#console.log(result.body);
|
||||
state.config.email = (result.body.contact[0]||'').replace(/mailto:/, '');
|
||||
var p2;
|
||||
if (state.key.sub === state.config.email) {
|
||||
p2 = Promise.resolve(state.key);
|
||||
} else {
|
||||
state.key.sub = state.config.email;
|
||||
p2 = keystore.set(state.key.kid + keyext, state.key);
|
||||
}
|
||||
return p2.then(function () {
|
||||
return RC.requestAsync({ service: 'config', method: 'GET' }).catch(function (err) {
|
||||
if (err) {
|
||||
if ('ENOENT' === err.code || 'ECONNREFUSED' === err.code) {
|
||||
console.error("Either the telebit service was not already (and could not be started) or its socket could not be written to.");
|
||||
console.error(err);
|
||||
} else if ('ENOTSOCK' === err.code) {
|
||||
console.error(err);
|
||||
return;
|
||||
} else {
|
||||
console.error(err);
|
||||
}
|
||||
process.exit(101);
|
||||
return;
|
||||
}
|
||||
}).then(handleConfig);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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) {
|
||||
/*
|
||||
|
@ -1064,6 +1085,10 @@ function handleApi() {
|
|||
|
||||
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.get(/\b(config)\b/, mustTrust, getConfigOnly);
|
||||
app.use(/\b(init|config)\b/, mustTrust, initOrConfig);
|
||||
|
|
|
@ -186,6 +186,7 @@ var telebitState = {};
|
|||
var appMethods = {
|
||||
initialize: function () {
|
||||
console.log("call initialize");
|
||||
return requestAccountHelper().then(function (/*key*/) {
|
||||
if (!appData.init.relay) {
|
||||
appData.init.relay = DEFAULT_RELAY;
|
||||
}
|
||||
|
@ -210,6 +211,7 @@ var appMethods = {
|
|||
console.error(err);
|
||||
window.alert("Error: [initialize] " + (err.message || JSON.stringify(err, null, 2)));
|
||||
});
|
||||
});
|
||||
}
|
||||
, advance: function () {
|
||||
return doConfigure();
|
||||
|
@ -473,14 +475,43 @@ new Vue({
|
|||
, methods: appMethods
|
||||
});
|
||||
|
||||
function run(key) {
|
||||
function requestAccountHelper() {
|
||||
function reset() {
|
||||
changeState('setup');
|
||||
setState();
|
||||
}
|
||||
return new Promise(function (resolve) {
|
||||
appData.init.email = localStorage.getItem('email');
|
||||
if (!appData.init.email) {
|
||||
// don't resolve
|
||||
reset();
|
||||
return;
|
||||
}
|
||||
return requestAccount(appData.init.email).then(function (key) {
|
||||
if (!key) { throw new Error("[SANITY] Error: completed without key"); }
|
||||
resolve(key);
|
||||
}).catch(function (err) {
|
||||
appData.init.email = "";
|
||||
localStorage.removeItem('email');
|
||||
console.error(err);
|
||||
window.alert("something went wrong");
|
||||
// don't resolve
|
||||
reset();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function run() {
|
||||
return requestAccountHelper().then(function (key) {
|
||||
api._key = key;
|
||||
// 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
|
||||
// 😁 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;
|
||||
|
@ -522,6 +553,7 @@ function run(key) {
|
|||
}).catch(function (err) {
|
||||
appData.views.flash.error = err.message || JSON.stringify(err, null, 2);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
@ -543,19 +575,8 @@ function getKey() {
|
|||
});
|
||||
}
|
||||
|
||||
function getEmail() {
|
||||
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() {
|
||||
function requestAccount(email) {
|
||||
return getKey().then(function (jwk) {
|
||||
return getEmail().then(function(email) {
|
||||
// creates new or returns existing
|
||||
var acme = ACME.create({});
|
||||
var url = window.location.protocol + '//' + window.location.host + '/acme/directory';
|
||||
|
@ -574,15 +595,14 @@ function requestAccount() {
|
|||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
window.api = api;
|
||||
requestAccount().then(function (jwk) {
|
||||
run(jwk);
|
||||
setTimeout(function () {
|
||||
run();
|
||||
setTimeout(function () {
|
||||
document.body.hidden = false;
|
||||
}, 50);
|
||||
});
|
||||
}, 50);
|
||||
|
||||
// Debug
|
||||
window.changeState = changeState;
|
||||
}());
|
||||
|
|
|
@ -34,11 +34,12 @@ module.exports = function eggspress() {
|
|||
}
|
||||
|
||||
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);
|
||||
next();
|
||||
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);
|
||||
next();
|
||||
return;
|
||||
|
@ -58,6 +59,7 @@ module.exports = function eggspress() {
|
|||
}
|
||||
|
||||
var fns = todo[1].slice(0);
|
||||
req.params = match.slice(1);
|
||||
|
||||
function nextTodo(err) {
|
||||
if (err) { fail(err); return; }
|
||||
|
|
|
@ -93,26 +93,45 @@ module.exports.create = function (state) {
|
|||
}
|
||||
return reqOpts;
|
||||
};
|
||||
RC.createErrorHandler = function (replay, opts, cb) {
|
||||
RC.createRelauncher = function (replay, opts, cb) {
|
||||
return function (err) {
|
||||
/*global Promise*/
|
||||
var p = new Promise(function (resolve, reject) {
|
||||
// ENOENT - never started, cleanly exited last start, or creating socket at a different path
|
||||
// ECONNREFUSED - leftover socket just needs to be restarted
|
||||
if ('ENOENT' === err.code || 'ECONNREFUSED' === err.code) {
|
||||
if (opts._taketwo) {
|
||||
cb(err);
|
||||
return;
|
||||
}
|
||||
require('../../usr/share/install-launcher.js').install({ env: process.env }, function (err) {
|
||||
if (err) { cb(err); return; }
|
||||
opts._taketwo = true;
|
||||
setTimeout(function () {
|
||||
replay(opts, cb);
|
||||
}, 2500);
|
||||
});
|
||||
if ('ENOENT' !== err.code && 'ECONNREFUSED' !== err.code) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
cb(err);
|
||||
// retried and failed again: quit
|
||||
if (opts._taketwo) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
require('../../usr/share/install-launcher.js').install({ env: process.env }, function (err) {
|
||||
if (err) { reject(err); return; }
|
||||
opts._taketwo = true;
|
||||
setTimeout(function () {
|
||||
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);
|
||||
});
|
||||
return;
|
||||
});
|
||||
if (cb) {
|
||||
p.then(function () { cb(null); }).catch(function (err) { cb(err); });
|
||||
}
|
||||
return p;
|
||||
};
|
||||
};
|
||||
RC.request = function request(opts, fn) {
|
||||
|
@ -141,7 +160,8 @@ module.exports.create = function (state) {
|
|||
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
|
||||
if ('POST' !== method || !opts.data) {
|
||||
|
|
Loading…
Reference in New Issue