telebit-relay.js/lib/extensions/index.js

175 lines
6.4 KiB
JavaScript
Raw Normal View History

2018-06-15 08:45:47 +00:00
'use strict';
2018-06-06 09:18:13 +00:00
/*
curl -s --user 'api:YOUR_API_KEY' \
https://api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages \
-F from='Excited User <mailgun@YOUR_DOMAIN_NAME>' \
-F to=YOU@YOUR_DOMAIN_NAME \
-F to=bar@example.com \
-F subject='Hello' \
-F text='Testing some Mailgun awesomeness!'
*/
2018-06-08 08:23:05 +00:00
var fs = require('fs');
2018-06-15 08:45:47 +00:00
var escapeHtml = require('escape-html');
2018-06-06 10:58:01 +00:00
var _auths = module.exports._auths = {};
2018-06-06 09:18:13 +00:00
module.exports.authenticate = function (opts) {
2018-06-06 10:58:01 +00:00
console.log("It's auth'n time!");
2018-06-06 09:18:13 +00:00
var util = require('util');
var requestAsync = util.promisify(require('request'));
var state = opts.state;
var jwtoken = opts.auth;
2018-06-06 10:58:01 +00:00
var auth;
2018-06-06 09:18:13 +00:00
var crypto = require('crypto');
2018-06-06 10:58:01 +00:00
console.log('[DEBUG] ext auth', jwtoken);
auth = jwtoken;
if ('object' === typeof auth && /^.+@.+\..+$/.test(auth.subject)) {
console.log("[DEBUG] gonna send email");
2018-06-15 08:45:47 +00:00
auth.id = crypto.randomBytes(12).toString('hex');
//var id = crypto.randomBytes(16).toString('base64').replace(/\+/g,'-').replace(/\//g,'_').replace(/=/g,'');
var subj = 'Confirm New Device Connection';
var text = "You tried connecting with '{{hostname}}' for the first time. Confirm to continue connecting:\n"
2018-06-08 08:23:05 +00:00
+ '\n'
2018-06-15 08:45:47 +00:00
+ ' https://' + state.config.webminDomain + '/login/#/magic={{id}}\n'
2018-06-08 08:23:05 +00:00
+ '\n'
2018-06-15 08:45:47 +00:00
+ "({{os_arch}} {{os_platform}} {{os_release}})\n"
2018-06-08 08:23:05 +00:00
+ '\n'
2018-06-15 08:45:47 +00:00
;
var html = "You tried connecting with '{{hostname}}' for the first time. Confirm to continue connecting:<br>"
2018-06-08 08:23:05 +00:00
+ '<br>'
2018-06-15 08:45:47 +00:00
+ '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <a href="https://' + state.config.webminDomain + '/login/#/magic={{id}}">Confirm Device</a><br>'
2018-06-09 20:46:08 +00:00
+ '<br>'
2018-06-15 08:45:47 +00:00
+ '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <small>or copy and paste this link:</small><br>'
+ '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <small>https://' + state.config.webminDomain + '/login/#/magic={{id}}</small><br>'
2018-06-08 08:23:05 +00:00
+ '<br>'
2018-06-15 08:45:47 +00:00
+ "({{os_arch}} {{os_platform}} {{os_release}})<br>"
2018-06-08 08:23:05 +00:00
+ '<br>'
2018-06-15 08:45:47 +00:00
;
[ 'id', 'hostname', 'os_arch', 'os_platform', 'os_release' ].forEach(function (key) {
var val = escapeHtml(auth[key]);
subj = subj.replace(new RegExp('{{' + key + '}}', 'g'), val);
text = text.replace(new RegExp('{{' + key + '}}', 'g'), val);
html = html.replace(new RegExp('{{' + key + '}}', 'g'), val);
});
return requestAsync({
url: state.config.mailer.url
, method: 'POST'
, auth: { user: 'api', pass: state.config.mailer.apiKey }
, formData: {
from: state.config.mailer.from
, to: auth.subject
, subject: subj
, text: text
, html: html
2018-06-06 10:58:01 +00:00
}
}).then(function (resp) {
console.log("[DEBUG] email was sent, or so they say");
console.log(resp.body);
2018-06-08 08:23:05 +00:00
fs.writeFile(path.join(__dirname, 'emails', auth.subject), JSON.stringify(auth), function (err) {
if (err) {
console.error('[ERROR] in writing auth details');
console.error(err);
}
});
2018-06-06 10:58:01 +00:00
return new state.Promise(function (resolve, reject) {
// TODO use global interval whenever the number of active links is high
var t = setTimeout(function () {
console.log("[Magic Link] Timeout for '" + auth.subject + "'");
2018-06-15 09:00:16 +00:00
delete _auths[auth.id];
2018-06-06 10:58:01 +00:00
var err = new Error("Login Failure: Magic Link was not clicked within 5 minutes");
err.code = 'E_LOGIN_TIMEOUT';
reject();
2018-06-09 20:46:08 +00:00
}, 2 * 60 * 60 * 1000);
2018-06-06 09:18:13 +00:00
2018-06-06 10:58:01 +00:00
function authorize() {
console.log("mighty auth'n ranger!");
clearTimeout(t);
2018-06-15 09:00:16 +00:00
delete _auths[auth.id];
2018-06-06 10:58:01 +00:00
var hri = require('human-readable-ids').hri;
2018-06-15 08:45:47 +00:00
var hrname = hri.random() + '.' + state.config.sharedDomain;
2018-06-06 10:58:01 +00:00
var jwt = require('jsonwebtoken');
var tokenData = {
domains: [ hrname ]
, ports: [ 1024 + Math.round(Math.random() * 6300) ]
2018-06-15 08:45:47 +00:00
, aud: state.config.webminDomain
2018-06-06 10:58:01 +00:00
, iss: Math.round(Date.now() / 1000)
2018-06-15 09:00:16 +00:00
, id: auth.id
2018-06-06 10:58:01 +00:00
, hostname: auth.hostname
2018-06-06 09:18:13 +00:00
};
2018-06-06 10:58:01 +00:00
tokenData.jwt = jwt.sign(tokenData, state.secret);
resolve(tokenData);
2018-06-08 08:25:06 +00:00
fs.writeFile(path.join(__dirname, 'emails', auth.subject + '.data'), JSON.stringify(tokenData), function (err) {
if (err) {
console.error('[ERROR] in writing token details');
console.error(err);
}
});
2018-06-06 10:58:01 +00:00
return tokenData;
}
2018-06-15 09:00:16 +00:00
_auths[auth.id] = {
2018-06-06 10:58:01 +00:00
dt: Date.now()
, resolve: authorize
, reject: reject
};
2018-06-06 09:18:13 +00:00
});
2018-06-06 10:58:01 +00:00
});
2018-06-06 09:18:13 +00:00
}
2018-06-06 10:58:01 +00:00
console.log("just trying a normal token...");
2018-06-15 08:45:47 +00:00
var decoded;
2018-06-06 09:18:13 +00:00
try {
decoded = jwt.decode(jwtoken, { complete: true });
} catch(e) {
decoded = null;
}
return state.defaults.authenticate(opts.auth);
};
2018-06-07 05:26:15 +00:00
//var loaded = false;
var path = require('path');
2018-06-06 10:58:01 +00:00
var express = require('express');
var app = express();
2018-06-15 08:45:47 +00:00
var staticApp = express();
var nowww = require('nowww')();
var CORS = require('connect-cors');
staticApp.use('/', express.static(path.join(__dirname, 'admin')));
app.use('/api', CORS({}));
app.get('/api/telebit.cloud/magic/:magic', function (req, res) {
console.log("DEBUG telebit.cloud magic");
2018-06-06 10:58:01 +00:00
var tokenData;
2018-06-15 08:45:47 +00:00
var magic = req.params.magic || req.query.magic;
2018-06-15 09:00:16 +00:00
console.log("DEBUG telebit.cloud magic 1a", magic);
2018-06-06 10:58:01 +00:00
if (_auths[magic]) {
2018-06-15 08:45:47 +00:00
console.log("DEBUG telebit.cloud magic 1b");
2018-06-06 10:58:01 +00:00
tokenData = _auths[magic].resolve();
2018-06-15 08:45:47 +00:00
console.log("DEBUG telebit.cloud magic 1c");
res.send(tokenData);
2018-06-06 10:58:01 +00:00
} else {
2018-06-15 08:45:47 +00:00
console.log("DEBUG telebit.cloud magic 2");
2018-06-15 09:00:16 +00:00
res.send({ error: { code: "E_TOKEN", message: "Invalid or expired magic link. (" + magic + ")" } });
2018-06-15 08:45:47 +00:00
console.log("DEBUG telebit.cloud magic 2b");
2018-06-06 10:58:01 +00:00
}
});
module.exports.webadmin = function (state, req, res) {
2018-06-07 05:26:15 +00:00
//if (!loaded) { loaded = true; app.use('/', state.defaults.webadmin); }
2018-06-06 10:58:01 +00:00
console.log('[DEBUG] extensions webadmin');
2018-06-15 08:45:47 +00:00
var host = (req.headers.host || '').toLowerCase().split(':')[0];
if (state.config.webminDomain === host) {
console.log("DEBUG going to static");
staticApp(req, res);
return;
}
if ('api.' + state.config.webminDomain === host) {
console.log("DEBUG going to api");
app(req, res);
return;
}
if ('www.' + state.config.webminDomain === host) {
console.log("DEBUG going to www");
nowww(req, res);
return;
}
res.end("Didn't recognize '" + escapeHtml(host) + "'. Not sure what to do.");
2018-06-06 10:58:01 +00:00
};