diff --git a/lib/extensions/index.js b/lib/extensions/index.js index ee247d0..d155a8d 100644 --- a/lib/extensions/index.js +++ b/lib/extensions/index.js @@ -20,6 +20,16 @@ var mkdirpAsync = util.promisify(require('mkdirp')); var TRUSTED_ISSUERS = [ 'oauth3.org' ]; var DB = require('./db.js'); var Devices = require('../device-tracker'); +var Server = require('../server'); + +var OAUTH3 = require('oauth3.js').create({ pathname: process.cwd() }); +/* +// TODO all of the above should be replace with the official lib +return OAUTH3.jwk.verifyToken(req.auth.jwt).then(function (token) { +}).catch(function (err) { +}); +*/ + var Claims = {}; Claims.publicize = function publicizeClaim(claim) { if (!claim) { @@ -206,7 +216,7 @@ Accounts.getOrCreate = function (req) { function sendMail(state, auth) { - console.log('[DEBUG] ext auth', auth); + //console.log('[DEBUG] ext auth', auth); /* curl -s --user 'api:YOUR_API_KEY' \ https://api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages \ @@ -267,9 +277,7 @@ function sendMail(state, auth) { } }); // anything in the 200 range - if (2 === Math.floor(resp.statusCode / 100)) { - console.log("[DEBUG] email was sent, or so they say"); - } else { + if (2 !== Math.floor(resp.statusCode / 100)) { console.error("[Error] email failed to send, or so they say:"); console.error(resp.headers); console.error(resp.statusCode, resp.body); @@ -402,7 +410,7 @@ function oauth3Auth(req, res, next) { , json: true }).then(function (resp) { var jwk = resp.body; - console.log('Retrieved token\'s JWK: ', resp.body); + //console.log('Retrieved token\'s JWK: ', resp.body); if (200 !== resp.statusCode || 'object' !== typeof resp.body) { //headers.authorization res.send({ @@ -452,16 +460,9 @@ function oauth3Auth(req, res, next) { }); }); } -var OAUTH3 = require('oauth3.js').create({ pathname: process.cwd() }); -/* -// TODO all of the above should be replace with the official lib -return OAUTH3.jwk.verifyToken(req.auth.jwt).then(function (token) { -}).catch(function (err) { -}); -*/ module.exports.pairRequest = function (opts) { - console.log("It's auth'n time!"); + //console.log("It's auth'n time!"); var state = opts.state; var authReq = opts.auth; var jwt = require('jsonwebtoken'); @@ -635,23 +636,26 @@ module.exports.pairPin = function (opts) { // From a WS connection module.exports.authHelper = function (meta) { - console.log('[authHelper] 1'); + //console.log('[authHelper] 1'); var state = meta.state; - console.log('[authHelper] 2'); + //console.log('[authHelper] 2'); return state.Promise.resolve().then(function () { - console.log('[authHelper] 3'); + //console.log('[authHelper] 3'); var auth = meta.session; - console.log('[authHelper] 4', auth); + //console.log('[authHelper] 4', auth); if (!auth || 'string' !== typeof auth.authz || 'object' !== typeof auth.authzData) { - console.log('[authHelper] 5'); + //console.log('[authHelper] 5'); console.error("[SANITY FAIL] should not complete auth without authz data and access_token"); console.error(auth); return; } - console.log("[authHelper] passing authzData right along", auth.authzData); + //console.log("[authHelper] passing authzData right along", auth.authzData); + // validatedTokenData return auth.authzData; }); }; + +// Comes in from ../../index.js // opts = { state: state, auth: auth_request OR access_token } module.exports.authenticate = function (opts) { var jwt = require('jsonwebtoken'); @@ -764,8 +768,7 @@ app.use('/api', CORS({ })); app.use('/api', bodyParser.json()); -app.use('/api/telebit.cloud/account', oauth3Auth); -app.get('/api/telebit.cloud/account', function (req, res) { +function getAccountData(req) { return Accounts.getOrCreate(req).then(function (acc) { var hasEmail = acc.nodes.some(function (node) { return 'email' === node.type; @@ -811,9 +814,10 @@ app.get('/api/telebit.cloud/account', function (req, res) { // TODO assign devices by public key, not domain name result.domains.map(function (domain) { - console.log("[debug] Domain", domain); + console.log("[debug] Domain", domain.name); var devs = Devices.list(req._state.deviceLists, domain.name); - console.log("[debug] Devs", devs.map(function (d) { return d.socketId; })); + console.log("[debug] Devs", devs); + //console.log("[debug] Devs", devs.map(function (d) { return d.socketId; })); if (!devs.length) { return null; } devs.forEach(function (dev) { // eventually we should implement so that a new connection @@ -824,10 +828,23 @@ app.get('/api/telebit.cloud/account', function (req, res) { id: dev.id , socketId: dev.socketId , active: true - , names: [] + , names: [domain.name] + , ports: [] + , hostname: dev.hostname }; } - devsMap[dev.socketId].names.push(domain.name); + if (-1 === devsMap[dev.socketId].names.indexOf(domain.name)) { + devsMap[dev.socketId].names.push(domain.name); + } + // copy over ports too + Object.keys(dev.grants).forEach(function (k) { + var grant = dev.grants[k]; + grant.ports.forEach(function (p) { + if (-1 === devsMap[dev.socketId].ports.indexOf(p)) { + devsMap[dev.socketId].ports.push(p); + } + }); + }); return devsMap[dev.socketId]; }); }).filter(Boolean); @@ -855,7 +872,14 @@ app.get('/api/telebit.cloud/account', function (req, res) { } else { return getAllGrants(); } - }).then(function (result) { + }); +} +app.use('/api/telebit.cloud/account', oauth3Auth); +app.use('/api/telebit.cloud/devices', oauth3Auth); +app.use('/api/telebit.cloud/domains', oauth3Auth); +app.use('/api/telebit.cloud/ports', oauth3Auth); +app.get('/api/telebit.cloud/account', function (req, res) { + getAccountData(req).then(function (result) { res.send(result); }).catch(function (err) { return res.send({ @@ -867,6 +891,77 @@ app.get('/api/telebit.cloud/account', function (req, res) { }); }); }); +app.post('/api/telebit.cloud/devices/:devId/:name', function (req, res) { + var name = req.params.name; + var devId = req.params.devId; + return getAccountData(req).then(function (grants) { + var err; + var dev; + + // Test if we have permission to both the device and the domain + if (!(grants.domains.some(function (d) { + return d.name === name; + }) && grants.devices.some(function (d) { + if ((d.id && d.id === devId) || (d.socketId === devId)) { + dev = d; + return dev; + } + }))) { + // Missing permission to one or the other + err = new Error("You must have authorizations for both device '" + devId + "' and domain '" + name + "'... which you do not."); + err.code = "E_PERM"; + return PromiseA.reject(err); + } + + console.log("dev", dev); + // Test if the specified device already uses the specified domain + if (dev.names.some(function (n) { + if (n === name) { return true; } + })) { + // Yep, already there. No mods needed. + res.send({ success: true, modified: false }); + return; + } + + var jti = crypto.randomBytes(12).toString('hex'); + var domainsMap = {}; + var portsMap = {}; + var authzData = { + id: jti + , domains: [ name ] + , ports: [] + , aud: req._state.config.webminDomain + , iat: Math.round(Date.now() / 1000) + // of the client's computer + , hostname: dev.hostname + }; + dev.names.forEach(function (name) { + if (!domainsMap[name]) { + domainsMap[name] = true; + authzData.domains.push(name); + } + }); + dev.ports.forEach(function (num) { + if (!portsMap[num]) { + portsMap[num] = true; + authzData.ports.push(num); + } + }); + var authz = jwt.sign(authzData, req._state.secret); + authzData.jwt = authz; + // TODO we need to force a grant + Server.onAuth(req._state, Devices.bySocket(req._state.deviceLists, dev.socketId), authz, authzData, true); + res.send({ success: true, data: authzData }); + }).catch(function (err) { + return res.send({ + error: { + code: err.code || "E_GENERIC" + , message: err.toString() + , _stack: err.stack + } + }); + }); +}); app.post('/api/telebit.cloud/account', function (req, res) { return Accounts.create(req).then(function (acc) { res.send({ @@ -968,7 +1063,7 @@ app.post('/api/telebit.cloud/account/authorizations/new/:value/:challenge?', fun if (!records.some(function (txts) { return txts.some(function (txt) { - console.log('TXT', txt); + //console.log('TXT', txt); return claim.challenge === txt; }); })) { @@ -984,7 +1079,7 @@ app.post('/api/telebit.cloud/account/authorizations/new/:value/:challenge?', fun }); } - console.log('claim', claim); + //console.log('claim', claim); if ('dns' === claim.type) { checkDns(); @@ -1043,8 +1138,8 @@ app.get('/api/telebit.cloud/pair_request/:secret', function (req, res) { // From User (which has entered pin) function pairCode(req, res) { - console.log("DEBUG telebit.cloud magic"); - console.log(req.body || req.params); + //console.log("DEBUG telebit.cloud magic"); + //console.log(req.body || req.params); var magic; var pin;