MAJOR: Updates for Authenticated Web UI and CLI #30
							
								
								
									
										107
									
								
								bin/telebitd.js
									
									
									
									
									
								
							
							
						
						
									
										107
									
								
								bin/telebitd.js
									
									
									
									
									
								
							| @ -31,6 +31,8 @@ var connectTimes = []; | |||||||
| var isConnected = false; | var isConnected = false; | ||||||
| var eggspress = require('../lib/eggspress.js'); | var eggspress = require('../lib/eggspress.js'); | ||||||
| var keypairs = require('keypairs'); | var keypairs = require('keypairs'); | ||||||
|  | var KEYEXT = '.key.jwk.json'; | ||||||
|  | var PUBEXT = '.pub.jwk.json'; | ||||||
| 
 | 
 | ||||||
| var TelebitRemote = require('../lib/daemon/index.js').TelebitRemote; | var TelebitRemote = require('../lib/daemon/index.js').TelebitRemote; | ||||||
| 
 | 
 | ||||||
| @ -140,8 +142,7 @@ controllers.http = function (req, res) { | |||||||
| 
 | 
 | ||||||
|   if (!req.body) { |   if (!req.body) { | ||||||
|     res.statusCode = 422; |     res.statusCode = 422; | ||||||
|     res.setHeader('Content-Type', 'application/json'); |     res.send({"error":{"message":"module \'http\' needs some arguments"}}); | ||||||
|     res.end(JSON.stringify({"error":{"message":"module \'http\' needs some arguments"}})); |  | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @ -153,8 +154,7 @@ controllers.http = function (req, res) { | |||||||
| 
 | 
 | ||||||
|   if (!portOrPath) { |   if (!portOrPath) { | ||||||
|     res.statusCode = 422; |     res.statusCode = 422; | ||||||
|     res.setHeader('Content-Type', 'application/json'); |     res.send({ error: { message: "module 'http' needs port or path" } }); | ||||||
|     res.end(JSON.stringify({ error: { message: "module 'http' needs port or path" } })); |  | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @ -249,22 +249,20 @@ controllers.http = function (req, res) { | |||||||
|   } |   } | ||||||
|   state.config.servernames = state.servernames; |   state.config.servernames = state.servernames; | ||||||
|   saveConfig(function (err) { |   saveConfig(function (err) { | ||||||
|     res.setHeader('Content-Type', 'application/json'); |     res.send({ | ||||||
|     res.end(JSON.stringify({ |  | ||||||
|       success: true |       success: true | ||||||
|     , active: active |     , active: active | ||||||
|     , remote: remoteHost |     , remote: remoteHost | ||||||
|     , local: portOrPath |     , local: portOrPath | ||||||
|     , saved: !err |     , saved: !err | ||||||
|     , module: 'http' |     , module: 'http' | ||||||
|     })); |     }); | ||||||
|   }); |   }); | ||||||
| }; | }; | ||||||
| controllers.tcp = function (req, res) { | controllers.tcp = function (req, res) { | ||||||
|   if (!req.body) { |   if (!req.body) { | ||||||
|     res.statusCode = 422; |     res.statusCode = 422; | ||||||
|     res.setHeader('Content-Type', 'application/json'); |     res.send({ error: { message: "module 'tcp' needs more arguments" } }); | ||||||
|     res.end(JSON.stringify({ error: { message: "module 'tcp' needs more arguments" } })); |  | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @ -298,22 +296,20 @@ controllers.tcp = function (req, res) { | |||||||
|   } |   } | ||||||
|   state.config.ports = state.ports; |   state.config.ports = state.ports; | ||||||
|   saveConfig(function (err) { |   saveConfig(function (err) { | ||||||
|     res.setHeader('Content-Type', 'application/json'); |     res.send({ | ||||||
|     res.end(JSON.stringify({ |  | ||||||
|       success: true |       success: true | ||||||
|     , active: active |     , active: active | ||||||
|     , remote: remotePort |     , remote: remotePort | ||||||
|     , local: portOrPath |     , local: portOrPath | ||||||
|     , saved: !err |     , saved: !err | ||||||
|     , module: 'tcp' |     , module: 'tcp' | ||||||
|     })); |     }); | ||||||
|   }); |   }); | ||||||
| }; | }; | ||||||
| controllers.ssh = function (req, res) { | controllers.ssh = function (req, res) { | ||||||
|   if (!req.body) { |   if (!req.body) { | ||||||
|     res.statusCode = 422; |     res.statusCode = 422; | ||||||
|     res.setHeader('Content-Type', 'application/json'); |     res.send({"error":{"message":"module 'ssh' needs more arguments"}}); | ||||||
|     res.end(JSON.stringify({"error":{"message":"module 'ssh' needs more arguments"}})); |  | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @ -324,15 +320,14 @@ controllers.ssh = function (req, res) { | |||||||
|       if (false !== local && !local) { |       if (false !== local && !local) { | ||||||
|         local = 22; |         local = 22; | ||||||
|       } |       } | ||||||
|       res.setHeader('Content-Type', 'application/json'); |       res.send({ | ||||||
|       res.end(JSON.stringify({ |  | ||||||
|         success: true |         success: true | ||||||
|       , active: true |       , active: true | ||||||
|       , remote: Object.keys(state.config.ports)[0] |       , remote: Object.keys(state.config.ports)[0] | ||||||
|       , local: local |       , local: local | ||||||
|       , saved: !err |       , saved: !err | ||||||
|       , module: 'ssh' |       , module: 'ssh' | ||||||
|       })); |       }); | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @ -351,8 +346,7 @@ controllers.ssh = function (req, res) { | |||||||
|   sshAuto = parseInt(sshAuto, 10); |   sshAuto = parseInt(sshAuto, 10); | ||||||
|   if (!sshAuto || sshAuto <= 0 || sshAuto > 65535) { |   if (!sshAuto || sshAuto <= 0 || sshAuto > 65535) { | ||||||
|     res.statusCode = 400; |     res.statusCode = 400; | ||||||
|     res.setHeader('Content-Type', 'application/json'); |     res.send({ error: { message: "bad ssh_auto option '" + rawSshAuto + "'" } }); | ||||||
|     res.end(JSON.stringify({ error: { message: "bad ssh_auto option '" + rawSshAuto + "'" } })); |  | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   state.config.sshAuto = sshAuto; |   state.config.sshAuto = sshAuto; | ||||||
| @ -361,15 +355,13 @@ controllers.ssh = function (req, res) { | |||||||
| controllers.relay = function (req, res) { | controllers.relay = function (req, res) { | ||||||
|   if (!req.body) { |   if (!req.body) { | ||||||
|     res.statusCode = 422; |     res.statusCode = 422; | ||||||
|     res.setHeader('Content-Type', 'application/json'); |     res.send({"error":{"message":"module \'relay\' needs more arguments"}}); | ||||||
|     res.end(JSON.stringify({"error":{"message":"module \'relay\' needs more arguments"}})); |  | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   return urequestAsync(req.body).then(function (resp) { |   return urequestAsync(req.body).then(function (resp) { | ||||||
|     res.setHeader('Content-Type', 'application/json'); |  | ||||||
|     resp = resp.toJSON(); |     resp = resp.toJSON(); | ||||||
|     res.end(JSON.stringify(resp)); |     res.send(resp); | ||||||
|   }); |   }); | ||||||
| }; | }; | ||||||
| controllers._nonces = {}; | controllers._nonces = {}; | ||||||
| @ -378,7 +370,7 @@ controllers._requireNonce = function (req, res, next) { | |||||||
|   var active = (Date.now() - controllers._nonces[nonce]) < (4 * 60 * 60 * 1000); |   var active = (Date.now() - controllers._nonces[nonce]) < (4 * 60 * 60 * 1000); | ||||||
|   if (!active) { |   if (!active) { | ||||||
|     // TODO proper headers and error message
 |     // TODO proper headers and error message
 | ||||||
|     res.end({ "error": "invalid or expired nonce", "error_code": "ENONCE" }); |     res.send({ "error": "invalid or expired nonce", "error_code": "ENONCE" }); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   delete controllers._nonces[nonce]; |   delete controllers._nonces[nonce]; | ||||||
| @ -451,7 +443,7 @@ function jsonEggspress(req, res, next) { | |||||||
|       req.body = JSON.parse(body); |       req.body = JSON.parse(body); | ||||||
|     } catch(e) { |     } catch(e) { | ||||||
|       res.statusCode = 400; |       res.statusCode = 400; | ||||||
|       res.end('{"error":{"message":"POST body is not valid json"}}'); |       res.send({"error":{"message":"POST body is not valid json"}}); | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|     next(); |     next(); | ||||||
| @ -508,12 +500,12 @@ function jwsEggspress(req, res, next) { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   var vjwk; |   var vjwk; | ||||||
|   jwks.some(function (jwk) { |   DB.pubs.some(function (jwk) { | ||||||
|     if (jwk.kid === req.jws.header.kid) { |     if (jwk.kid === req.jws.header.kid) { | ||||||
|       vjwk = jwk; |       vjwk = jwk; | ||||||
|     } |     } | ||||||
|   }); |   }); | ||||||
|   if ((0 === jwks.length && req.jws.header.jwk)) { |   if ((0 === DB.pubs.length && req.jws.header.jwk)) { | ||||||
|     vjwk = req.jws.header.jwk; |     vjwk = req.jws.header.jwk; | ||||||
|     if (!vjwk.kid) { throw Error("Impossible: no key id"); } |     if (!vjwk.kid) { throw Error("Impossible: no key id"); } | ||||||
|   } |   } | ||||||
| @ -524,7 +516,7 @@ function jwsEggspress(req, res, next) { | |||||||
|     } |     } | ||||||
|     req.jws.verified = verified; |     req.jws.verified = verified; | ||||||
| 
 | 
 | ||||||
|     if (0 !== jwks.length) { |     if (0 !== DB.pubs.length) { | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|     return keystore.set(vjwk.kid + '.pub.jwk.json', vjwk); |     return keystore.set(vjwk.kid + '.pub.jwk.json', vjwk); | ||||||
| @ -565,15 +557,14 @@ function handleApi() { | |||||||
|       dumpy.message = "Please run 'telebit init' to authenticate."; |       dumpy.message = "Please run 'telebit init' to authenticate."; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     res.end(JSON.stringify(dumpy)); |     res.send(dumpy); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   function getConfigOnly(req, res) { |   function getConfigOnly(req, res) { | ||||||
|     var resp = JSON.parse(JSON.stringify(state.config)); |     var resp = JSON.parse(JSON.stringify(state.config)); | ||||||
|     resp.version = pkg.version; |     resp.version = pkg.version; | ||||||
|     resp._otp = state.otp; |     resp._otp = state.otp; | ||||||
|     res.setHeader('Content-Type', 'application/json'); |     res.send(resp); | ||||||
|     res.end(JSON.stringify(resp)); |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   //
 |   //
 | ||||||
| @ -585,9 +576,8 @@ function handleApi() { | |||||||
|     fs.writeFile(confpath, YAML.safeDump(snakeCopy(state.config)), function (err) { |     fs.writeFile(confpath, YAML.safeDump(snakeCopy(state.config)), function (err) { | ||||||
|       if (err) { |       if (err) { | ||||||
|         res.statusCode = 500; |         res.statusCode = 500; | ||||||
|         res.setHeader('Content-Type', 'application/json'); |         res.send({"error":{"message":"Could not save config file after init: " + err.message.replace(/"/g, "'") | ||||||
|         res.end('{"error":{"message":"Could not save config file after init: ' + err.message.replace(/"/g, "'") |           + ".\nPerhaps check that the file exists and your user has permissions to write it?"}}); | ||||||
|           + '.\nPerhaps check that the file exists and your user has permissions to write it?"}}'); |  | ||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
| @ -599,7 +589,7 @@ function handleApi() { | |||||||
|     var conf = {}; |     var conf = {}; | ||||||
|     if (!req.body) { |     if (!req.body) { | ||||||
|       res.statusCode = 422; |       res.statusCode = 422; | ||||||
|       res.end('{"error":{"message":"module \'init\' needs more arguments"}}'); |       res.send({"error":{"message":"module 'init' needs more arguments"}}); | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -695,8 +685,7 @@ function handleApi() { | |||||||
|       console.warn('missing config'); |       console.warn('missing config'); | ||||||
|       res.statusCode = 400; |       res.statusCode = 400; | ||||||
| 
 | 
 | ||||||
|       res.setHeader('Content-Type', 'application/json'); |       res.send({ | ||||||
|       res.end(JSON.stringify({ |  | ||||||
|         error: { |         error: { | ||||||
|           code: "E_INIT" |           code: "E_INIT" | ||||||
|         , message: "Missing important config file params" |         , message: "Missing important config file params" | ||||||
| @ -704,7 +693,7 @@ function handleApi() { | |||||||
|         , _config: JSON.stringify(state.config) |         , _config: JSON.stringify(state.config) | ||||||
|         , _body: JSON.stringify(req.body) |         , _body: JSON.stringify(req.body) | ||||||
|         } |         } | ||||||
|       })); |       }); | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -729,8 +718,7 @@ function handleApi() { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     function respondAndClose() { |     function respondAndClose() { | ||||||
|       res.setHeader('Content-Type', 'application/json'); |       res.send({ success: true }); | ||||||
|       res.end(JSON.stringify({ success: true })); |  | ||||||
|       controlServer.close(function () { |       controlServer.close(function () { | ||||||
|         console.info("[telebitd.js] server closed"); |         console.info("[telebitd.js] server closed"); | ||||||
|         setTimeout(function () { |         setTimeout(function () { | ||||||
| @ -751,10 +739,9 @@ function handleApi() { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     res.statusCode = 400; |     res.statusCode = 400; | ||||||
|     res.setHeader('Content-Type', 'application/json'); |     res.send({ | ||||||
|     res.end(JSON.stringify({ |  | ||||||
|       error: { code: "E_CONFIG", message: "Invalid config file. Please run 'telebit init'" } |       error: { code: "E_CONFIG", message: "Invalid config file. Please run 'telebit init'" } | ||||||
|     })); |     }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   function saveAndCommit(req, res) { |   function saveAndCommit(req, res) { | ||||||
| @ -763,10 +750,9 @@ function handleApi() { | |||||||
|     fs.writeFile(confpath, YAML.safeDump(snakeCopy(state.config)), function (err) { |     fs.writeFile(confpath, YAML.safeDump(snakeCopy(state.config)), function (err) { | ||||||
|       if (err) { |       if (err) { | ||||||
|         res.statusCode = 500; |         res.statusCode = 500; | ||||||
|         res.setHeader('Content-Type', 'application/json'); |         res.send({ | ||||||
|         res.end(JSON.stringify({ |  | ||||||
|           "error":{"message":"Could not save config file. Perhaps you're not running as root?"} |           "error":{"message":"Could not save config file. Perhaps you're not running as root?"} | ||||||
|         })); |         }); | ||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
|       listSuccess(); |       listSuccess(); | ||||||
| @ -775,10 +761,9 @@ function handleApi() { | |||||||
| 
 | 
 | ||||||
|   function handleError(err, req, res) { |   function handleError(err, req, res) { | ||||||
|     res.statusCode = 500; |     res.statusCode = 500; | ||||||
|     res.setHeader('Content-Type', 'application/json'); |     res.send({ | ||||||
|     res.end(JSON.stringify({ |  | ||||||
|       error: { message: err.message, code: err.code } |       error: { message: err.message, code: err.code } | ||||||
|     })); |     }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   function enable(req, res) { |   function enable(req, res) { | ||||||
| @ -808,21 +793,19 @@ function handleApi() { | |||||||
| 
 | 
 | ||||||
|     if (myRemote) { myRemote.end(); myRemote = null; } |     if (myRemote) { myRemote.end(); myRemote = null; } | ||||||
|     fs.writeFile(confpath, YAML.safeDump(snakeCopy(state.config)), function (err) { |     fs.writeFile(confpath, YAML.safeDump(snakeCopy(state.config)), function (err) { | ||||||
|       res.setHeader('Content-Type', 'application/json'); |  | ||||||
|       if (err) { |       if (err) { | ||||||
|         err.message = "Could not save config file. Perhaps you're user doesn't have permission?"; |         err.message = "Could not save config file. Perhaps you're user doesn't have permission?"; | ||||||
|         handleError(err); |         handleError(err); | ||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
|       res.end('{"success":true}'); |       res.send({"success":true}); | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   function getStatus(req, res) { |   function getStatus(req, res) { | ||||||
|     var now = Date.now(); |     var now = Date.now(); | ||||||
|     res.setHeader('Content-Type', 'application/json'); |  | ||||||
|     require('../lib/ssh.js').checkSecurity().then(function (ssh) { |     require('../lib/ssh.js').checkSecurity().then(function (ssh) { | ||||||
|       res.end(JSON.stringify( |       res.send( | ||||||
|         { module: 'status' |         { module: 'status' | ||||||
|         , version: pkg.version |         , version: pkg.version | ||||||
|         , port: (state.config.ipc && state.config.ipc.port || state._ipc.port || undefined) |         , port: (state.config.ipc && state.config.ipc.port || state._ipc.port || undefined) | ||||||
| @ -840,7 +823,7 @@ function handleApi() { | |||||||
|         , ssh_password_authentication: ssh.password_authentication |         , ssh_password_authentication: ssh.password_authentication | ||||||
|         , ssh_requests_password: ssh.requests_password |         , ssh_requests_password: ssh.requests_password | ||||||
|         } |         } | ||||||
|       )); |       ); | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @ -876,8 +859,7 @@ function handleApi() { | |||||||
|   app.use(/\b(status)\b/, getStatus); |   app.use(/\b(status)\b/, getStatus); | ||||||
|   app.use(/\b(list)\b/, listSuccess); |   app.use(/\b(list)\b/, listSuccess); | ||||||
|   app.use('/', function (req, res) { |   app.use('/', function (req, res) { | ||||||
|     res.setHeader('Content-Type', 'application/json'); |     res.send({"error":{"message":"unrecognized rpc"}}); | ||||||
|     res.end(JSON.stringify({"error":{"message":"unrecognized rpc"}})); |  | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   return app; |   return app; | ||||||
| @ -1430,7 +1412,8 @@ state.net = state.net || { | |||||||
|   } |   } | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| var jwks = []; | var DB = {}; | ||||||
|  | DB.pubs = []; | ||||||
| var token; | var token; | ||||||
| var tokenname = "access_token.jwt"; | var tokenname = "access_token.jwt"; | ||||||
| try { | try { | ||||||
| @ -1444,12 +1427,10 @@ try { | |||||||
| } catch(e) { onKeystore(); } | } catch(e) { onKeystore(); } | ||||||
| function onKeystore() { | function onKeystore() { | ||||||
|   return keystore.all().then(function (list) { |   return keystore.all().then(function (list) { | ||||||
|     var keyext = '.key.jwk.json'; |  | ||||||
|     var pubext = '.pub.jwk.json'; |  | ||||||
|     var key; |     var key; | ||||||
|     list.forEach(function (el) { |     list.forEach(function (el) { | ||||||
|       // find key
 |       // find key
 | ||||||
|       if (keyext === el.account.slice(-keyext.length) |       if (KEYEXT === el.account.slice(-KEYEXT.length) | ||||||
|         && el.password.kty && el.password.kid) { |         && el.password.kty && el.password.kid) { | ||||||
|         key = el.password; |         key = el.password; | ||||||
|         return; |         return; | ||||||
| @ -1465,9 +1446,9 @@ function onKeystore() { | |||||||
|       // (if we sign these we could probably just store them to the fs,
 |       // (if we sign these we could probably just store them to the fs,
 | ||||||
|       // but we do want some way to know that they weren't just willy-nilly
 |       // but we do want some way to know that they weren't just willy-nilly
 | ||||||
|       // added to the fs my any old program)
 |       // added to the fs my any old program)
 | ||||||
|       if (pubext === el.account.slice(-pubext.length)) { |       if (PUBEXT === el.account.slice(-PUBEXT.length)) { | ||||||
|         // pre-parsed
 |         // pre-parsed
 | ||||||
|         jwks.push(el.password); |         DB.pubs.push(el.password); | ||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
| @ -1485,7 +1466,7 @@ function onKeystore() { | |||||||
|       var jwk = pair.private; |       var jwk = pair.private; | ||||||
|       return keypairs.thumbprint({ jwk: jwk }).then(function (kid) { |       return keypairs.thumbprint({ jwk: jwk }).then(function (kid) { | ||||||
|         jwk.kid = kid; |         jwk.kid = kid; | ||||||
|         return keystore.set(kid + keyext, jwk).then(function () { |         return keystore.set(kid + KEYEXT, jwk).then(function () { | ||||||
|           var size = (jwk.crv || Buffer.from(jwk.n, 'base64').byteLength * 8); |           var size = (jwk.crv || Buffer.from(jwk.n, 'base64').byteLength * 8); | ||||||
|           console.info("Generated new %s %s private key with thumbprint %s", jwk.kty, size, kid); |           console.info("Generated new %s %s private key with thumbprint %s", jwk.kty, size, kid); | ||||||
|           state.key = jwk; |           state.key = jwk; | ||||||
|  | |||||||
| @ -1,5 +1,14 @@ | |||||||
| 'use strict'; | 'use strict'; | ||||||
| 
 | 
 | ||||||
|  | function eggSend(obj) { | ||||||
|  |   /*jslint validthis: true*/ | ||||||
|  |   var me = this; | ||||||
|  |   if (!me.getHeader('content-type')) { | ||||||
|  |     me.setHeader('Content-Type', 'application/json'); | ||||||
|  |   } | ||||||
|  |   me.end(JSON.stringify(obj)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| module.exports = function eggspress() { | module.exports = function eggspress() { | ||||||
|   //var patternsMap = {};
 |   //var patternsMap = {};
 | ||||||
|   var allPatterns = []; |   var allPatterns = []; | ||||||
| @ -52,6 +61,9 @@ module.exports = function eggspress() { | |||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     res.send = eggSend; | ||||||
|  | 
 | ||||||
|     next(); |     next(); | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -154,7 +154,7 @@ module.exports.create = function (state) { | |||||||
|       } |       } | ||||||
|     , payload: JSON.stringify(opts.data) |     , payload: JSON.stringify(opts.data) | ||||||
|     }).then(function (jws) { |     }).then(function (jws) { | ||||||
|       req.setHeader("content-type", 'application/json'); |       req.setHeader("Content-Type", 'application/jose+json'); | ||||||
|       req.write(JSON.stringify(jws)); |       req.write(JSON.stringify(jws)); | ||||||
|       req.end(); |       req.end(); | ||||||
|     }); |     }); | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user