diff --git a/bin/telebitd.js b/bin/telebitd.js index 6bbbdfd..fe5c70e 100755 --- a/bin/telebitd.js +++ b/bin/telebitd.js @@ -587,6 +587,10 @@ function jwtEggspress(req, res, next) { } catch(e) { // ignore } + if (!req.jwk.kid) { + res.send({ error: { message: "JWT must include a SHA thumbprint as the 'kid' (key id)" } }); + return; + } // TODO verify if possible console.warn("[warn] JWT is not verified yet"); diff --git a/lib/admin/js/app.js b/lib/admin/js/app.js index 1a06d3e..82705d4 100644 --- a/lib/admin/js/app.js +++ b/lib/admin/js/app.js @@ -25,8 +25,9 @@ function safeFetch(url, opts) { api.config = function apiConfig() { return Telebit.reqLocalAsync({ - url: "/api/config" - , method: "GET" + method: "GET" + , url: "/api/config" + , key: api._key }).then(function (resp) { var json = resp.body; appData.config = json; @@ -34,17 +35,22 @@ api.config = function apiConfig() { }); }; api.status = function apiStatus() { - return Telebit.reqLocalAsync({ url: "/api/status", method: "GET" }).then(function (resp) { + return Telebit.reqLocalAsync({ + method: "GET" + , url: "/api/status" + , key: api._key + }).then(function (resp) { var json = resp.body; return json; }); }; api.http = function apiHttp(o) { var opts = { - url: "/api/http" - , method: "POST" + method: "POST" + , url: "/api/http" , headers: { 'Content-Type': 'application/json' } , json: { name: o.name, handler: o.handler, indexes: o.indexes } + , key: api._key }; return Telebit.reqLocalAsync(opts).then(function (resp) { var json = resp.body; @@ -56,10 +62,11 @@ api.http = function apiHttp(o) { }; api.ssh = function apiSsh(port) { var opts = { - url: "/api/ssh" - , method: "POST" + method: "POST" + , url: "/api/ssh" , headers: { 'Content-Type': 'application/json' } , json: { port: port } + , key: api._key }; return Telebit.reqLocalAsync(opts).then(function (resp) { var json = resp.body; @@ -71,9 +78,10 @@ api.ssh = function apiSsh(port) { }; api.enable = function apiEnable() { var opts = { - url: "/api/enable" - , method: "POST" + method: "POST" + , url: "/api/enable" //, headers: { 'Content-Type': 'application/json' } + , key: api._key }; return Telebit.reqLocalAsync(opts).then(function (resp) { var json = resp.body; @@ -85,9 +93,10 @@ api.enable = function apiEnable() { }; api.disable = function apiDisable() { var opts = { - url: "/api/disable" - , method: "POST" + method: "POST" + , url: "/api/disable" //, headers: { 'Content-Type': 'application/json' } + , key: api._key }; return Telebit.reqLocalAsync(opts).then(function (resp) { var json = resp.body; @@ -465,8 +474,9 @@ new Vue({ }); function run(key) { - // 1. Get ACME directory - // 2. Fetch ACME account + api._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 diff --git a/lib/admin/js/telebit.js b/lib/admin/js/telebit.js index f4379df..4908f46 100644 --- a/lib/admin/js/telebit.js +++ b/lib/admin/js/telebit.js @@ -1,6 +1,7 @@ ;(function (exports) { 'use strict'; +var Keypairs = window.Keypairs; var common = exports.TELEBIT = {}; common.debug = true; @@ -14,7 +15,7 @@ if ('undefined' !== typeof Promise) { /*globals AbortController*/ if ('undefined' !== typeof fetch) { - common.requestAsync = function (opts) { + common._requestAsync = function (opts) { // funnel requests through the local server // (avoid CORS, for now) var relayOpts = { @@ -44,7 +45,7 @@ if ('undefined' !== typeof fetch) { }); }); }; - common.reqLocalAsync = function (opts) { + common._reqLocalAsync = function (opts) { if (!opts) { opts = {}; } if (opts.json && true !== opts.json) { opts.body = opts.json; @@ -78,9 +79,35 @@ if ('undefined' !== typeof fetch) { }); }; } else { - common.requestAsync = require('util').promisify(require('@root/request')); - common.reqLocalAsync = require('util').promisify(require('@root/request')); + common._requestAsync = require('util').promisify(require('@root/request')); + common._reqLocalAsync = require('util').promisify(require('@root/request')); } +common._sign = function (opts) { + var p; + if ('POST' === opts.method || opts.json) { + p = Keypairs.signJws({ jwk: opts.key, payload: opts.json || {} }).then(function (jws) { + opts.json = jws; + }); + } else { + p = Keypairs.signJwt({ jwk: opts.key , claims: { iss: false, exp: '60s' } }).then(function (jwt) { + if (!opts.headers) { opts.headers = {}; } + opts.headers.Authorization = 'Bearer ' + jwt; + }); + } + return p.then(function () { + return opts; + }); +}; +common.requestAsync = function (opts) { + return common._sign(opts).then(function (opts) { + return common._requestAsync(opts); + }); +}; +common.reqLocalAsync = function (opts) { + return common._sign(opts).then(function (opts) { + return common._reqLocalAsync(opts); + }); +}; common.parseUrl = function (hostname) { // add scheme, if missing