diff --git a/holepunch/helpers/update-ip.js b/holepunch/helpers/update-ip.js deleted file mode 100644 index 0432987..0000000 --- a/holepunch/helpers/update-ip.js +++ /dev/null @@ -1,90 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -var PromiseA = require('bluebird').Promise; -var https = require('https'); -var fs = PromiseA.promisifyAll(require('fs')); - -module.exports.update = function (opts) { - return new PromiseA(function (resolve, reject) { - var options; - var hostname = opts.hostname || opts.updater; - var port = opts.port; - var pathname = opts.pathname; - var req; - - if (!hostname) { - throw new Error('Please specify a DDNS host as opts.hostname'); - } - if (!pathname) { - throw new Error('Please specify the api route as opts.pathname'); - } - - options = { - host: hostname - , port: port - , method: 'POST' - , headers: { - 'Content-Type': 'application/json' - } - , path: pathname - //, auth: opts.auth || 'admin:secret' - }; - - if (opts.cacert) { - if (!Array.isArray(opts.cacert)) { - opts.cacert = [opts.cacert]; - } - options.ca = opts.cacert; - } - - if (opts.token || opts.jwt) { - options.headers.Authorization = 'Bearer ' + (opts.token || opts.jwt); - } - - if (false === opts.cacert) { - options.rejectUnauthorized = false; - } - - options.ca = (options.ca||[]).map(function (str) { - if ('string' === typeof str && str.length < 1000) { - str = fs.readFileAsync(str); - } - return str; - }); - - return PromiseA.all(options.ca).then(function (cas) { - options.ca = cas; - options.agent = new https.Agent(options); - - req = https.request(options, function(res) { - var textData = ''; - - res.on('error', function (err) { - reject(err); - }); - res.on('data', function (chunk) { - textData += chunk.toString(); - // console.log(chunk.toString()); - }); - res.on('end', function () { - var err; - try { - resolve(JSON.parse(textData)); - } catch(e) { - err = new Error("Unparsable Server Response"); - err.code = 'E_INVALID_SERVER_RESPONSE'; - err.data = textData; - reject(err); - } - }); - }); - - req.on('error', function (err) { - reject(err); - }); - - req.end(JSON.stringify(opts.ddns, null, ' ')); - }, reject); - }); -}; diff --git a/lib/ddns-updater.js b/lib/ddns-updater.js index 2996841..fd94146 100644 --- a/lib/ddns-updater.js +++ b/lib/ddns-updater.js @@ -1,6 +1,6 @@ 'use strict'; -var updateIp = require('../holepunch/helpers/update-ip.js').update; +var updateIp = require('ddns-cli').update; /* * @param {string[]} hostnames - A list of hostnames diff --git a/lib/master.js b/lib/master.js index 7ee2afb..414146c 100644 --- a/lib/master.js +++ b/lib/master.js @@ -129,55 +129,5 @@ function touch(conf, state) { */ } - //var config = require('./device.json'); - - // require('ssl-root-cas').inject(); - // TODO try SNI loopback.example.com as result of api.ipify.com with loopback token - - /* - function phoneHome() { - var holepunch = require('./holepunch/beacon'); - var ports; - - ports = [ - { private: 65022 - , public: 65022 - , protocol: 'tcp' - , ttl: 0 - , test: { service: 'ssh' } - , testable: false - } - , { private: 650443 - , public: 650443 - , protocol: 'tcp' - , ttl: 0 - , test: { service: 'https' } - } - , { private: 65080 - , public: 65080 - , protocol: 'tcp' - , ttl: 0 - , test: { service: 'http' } - } - ]; - - // TODO return a middleware - holepunch.run(require('./redirects.json').reduce(function (all, redirect) { - if (!all[redirect.from.hostname]) { - all[redirect.from.hostname] = true; - all.push(redirect.from.hostname); - } - if (!all[redirect.to.hostname]) { - all[redirect.to.hostname] = true; - all.push(redirect.to.hostname); - } - - return all; - }, []), ports).catch(function () { - console.error("Couldn't phone home. Oh well"); - }); - } - */ - module.exports.init = init; module.exports.touch = touch; diff --git a/lib/oauth3-auth.js b/lib/oauth3-auth.js deleted file mode 100644 index 024b28f..0000000 --- a/lib/oauth3-auth.js +++ /dev/null @@ -1,407 +0,0 @@ -'use strict'; - -var PromiseA = require('bluebird'); - -module.exports.inject = function (conf, app, pkgConf, pkgDeps) { - var scoper = require('app-scoped-ids'); - var inProcessCache = {}; - var createClientFactory = require('sqlite3-cluster/client').createClientFactory; - var dir = [ - { tablename: 'codes' - , idname: 'uuid' - , indices: ['createdAt'] - } - , { tablename: 'logins' // coolaj86, coolaj86@gmail.com, +1-317-426-6525 - , idname: 'hashId' - //, relations: [{ tablename: 'secrets', id: 'hashid', fk: 'loginId' }] - , indices: ['createdAt', 'type', 'node'] - //, immutable: false - } - , { tablename: 'verifications' - , idname: 'hashId' // hash(date + node) - //, relations: [{ tablename: 'secrets', id: 'hashid', fk: 'loginId' }] - , indices: ['createdAt', 'nodeId'] - //, immutable: true - } - , { tablename: 'secrets' - , idname: 'hashId' // hash(node + secret) - , indices: ['createdAt'] - //, immutable: true - } - , { tablename: 'recoveryNodes' // just for 1st-party logins - , idname: 'hashId' // - // TODO how transmit that something should be deleted / disabled? - , indices: ['createdAt', 'updatedAt', 'loginHash', 'recoveryNode', 'deleted'] - } - - // - // Accounts - // - , { tablename: 'accounts_logins' - , idname: 'id' // hash(accountId + loginId) - , indices: ['createdAt', 'revokedAt', 'loginId', 'accountId'] - } - , { tablename: 'accounts' - , idname: 'id' // crypto random id? or hash(name) ? - , unique: ['name'] - , indices: ['createdAt', 'updatedAt', 'deletedAt', 'name', 'displayName'] - } - - // - // OAuth3 - // - , { tablename: 'private_key' - , idname: 'id' - , indices: ['createdAt'] - } - , { tablename: 'oauth_clients' - , idname: 'id' - , indices: ['createdAt', 'updatedAt', 'accountId'] - , hasMany: ['apiKeys'] // TODO - , belongsTo: ['account'] - , schema: function () { - return { - test: true - , insecure: true - }; - } - } - , { tablename: 'api_keys' - , idname: 'id' - , indices: ['createdAt', 'updatedAt', 'oauthClientId'] - , belongsTo: ['oauthClient'] // TODO pluralization - , schema: function () { - return { - test: true - , insecure: true - }; - } - } - , { tablename: 'tokens' // note that a token functions as a session - , idname: 'id' - , indices: ['createdAt', 'updatedAt', 'expiresAt', 'revokedAt', 'oauthClientId', 'loginId', 'accountId'] - } - , { tablename: 'grants' - , idname: 'id' // sha256(scope + oauthClientId + (accountId || loginId)) - , indices: ['createdAt', 'updatedAt', 'oauthClientId', 'loginId', 'accountId'] - } - ]; - - function getAppScopedControllers(experienceId) { - if (inProcessCache[experienceId]) { - return PromiseA.resolve(inProcessCache[experienceId]); - } - - var mq = require('masterquest'); - var path = require('path'); - // TODO how can we encrypt this? - var systemFactory = createClientFactory({ - // TODO only complain if the values are different - algorithm: 'aes' - , bits: 128 - , mode: 'cbc' - , dirname: path.join(__dirname, '..', '..', 'var') // TODO info.conf - //, prefix: appname.replace(/\//g, ':') // 'com.example.' - //, dbname: 'cluster' - , suffix: '' - , ext: '.sqlcipher' - , sock: conf.sqlite3Sock - , ipcKey: conf.ipcKey - }); - var clientFactory = createClientFactory({ - // TODO only complain if the values are different - dirname: path.join(__dirname, '..', '..', 'var') // TODO info.conf - , prefix: 'com.oauth3' // 'com.example.' - //, dbname: 'config' - , suffix: '' - , ext: '.sqlite3' - , sock: conf.sqlite3Sock - , ipcKey: conf.ipcKey - }); - - inProcessCache[experienceId] = systemFactory.create({ - init: true - //, key: '00000000000000000000000000000000' - , dbname: experienceId // 'com.example.' - }).then(function (sqlStore) { - //var db = factory. - return mq.wrap(sqlStore, dir).then(function (models) { - return require('./oauthclient-microservice/lib/sign-token').create(models.PrivateKey).init().then(function (signer) { - var CodesCtrl = require('authcodes').create(models.Codes); - /* models = { Logins, Verifications } */ - var LoginsCtrl = require('./authentication-microservice/lib/logins').create({}, CodesCtrl, models); - /* models = { ApiKeys, OauthClients } */ - var ClientsCtrl = require('./oauthclient-microservice/lib/oauthclients').createController({}, models, signer); - - return { - Codes: CodesCtrl - , Logins: LoginsCtrl - , Clients: ClientsCtrl - , SqlFactory: clientFactory - , models: models - }; - }); - }); - }).then(function (ctrls) { - inProcessCache[experienceId] = ctrls; - return ctrls; - }); - - return inProcessCache[experienceId]; - } - - //var jwsUtils = require('./lib/jws-utils').create(signer); - var CORS = require('connect-cors'); - var cors = CORS({ credentials: true, headers: [ - 'X-Requested-With' - , 'X-HTTP-Method-Override' - , 'Content-Type' - , 'Accept' - , 'Authorization' - ], methods: [ "GET", "POST", "PATCH", "PUT", "DELETE" ] }); - - // Allows CORS access to API with ?access_token= - // TODO Access-Control-Max-Age: 600 - // TODO How can we help apps handle this? token? - // TODO allow apps to configure trustedDomains, auth, etc - - //function weakDecipher(secret, val) { return require('./weak-crypt').weakDecipher(val, secret); } - - // - // Generic Session / Login / Account Routes - // - function parseAccessToken(req, opts) { - var token; - var parts; - var scheme; - var credentials; - - if (req.headers && req.headers.authorization) { - parts = req.headers.authorization.split(' '); - - if (parts.length !== 2) { - return PromiseA.reject(new Error("malformed Authorization header")); - } - - scheme = parts[0]; - credentials = parts[1]; - - if (-1 !== (opts && opts.schemes || ['token', 'bearer']).indexOf(scheme.toLowerCase())) { - token = credentials; - } - } - - if (req.body && req.body.access_token) { - if (token) { PromiseA.reject(new Error("token exists in header and body")); } - token = req.body.access_token; - } - - // TODO disallow query with req.method === 'GET' - // (cookies should be used for protected static assets) - if (req.query && req.query.access_token) { - if (token) { PromiseA.reject(new Error("token already exists in either header or body and also in query")); } - token = req.query.access_token; - } - - /* - err = new Error(challenge()); - err.code = 'E_BEARER_REALM'; - - if (!token) { return PromiseA.reject(err); } - */ - - return PromiseA.resolve(token); - } - - function getClient(req, token, priv, Controllers) { - if (!token) { - token = req.oauth3.token; - } - - var cacheId = '_' + token.k + 'Client'; - - if (priv[cacheId]) { - return PromiseA.resolve(priv[cacheId]); - } - - // TODO could get client directly by token.app (id of client) - priv[cacheId] = Controllers.Clients.login(null, token.k).then(function (apiKey) { - if (!apiKey) { - return PromiseA.reject(new Error("Client no longer valid")); - } - - priv[cacheId + 'Key'] = apiKey; - priv[cacheId] = apiKey.oauthClient; - - return apiKey.oauthClient; - }); - - return priv[cacheId]; - } - - function getAccountsByLogin(req, token, priv, Controllers, loginId, decrypt) { - return getClient(req, req.oauth.token, priv).then(function (oauthClient) { - if (decrypt) { - loginId = scoper.unscope(loginId, oauthClient.secret); - } - - return Controllers.models.AccountsLogins.find({ loginId: loginId }).then(function (accounts) { - return PromiseA.all(accounts.map(function (obj) { - return Controllers.models.Accounts.get(obj.accountId)/*.then(function (account) { - account.appScopedId = weakCipher(oauthClient.secret, account.id); - return account; - })*/; - })); - }); - }); - } - - function getAccountsByArray(req, Controllers, arr) { - return PromiseA.all(arr.map(function (accountId) { - return Controllers.models.Accounts.get(accountId.id || accountId); - })); - } - - function getAccounts(req, token, priv, Controllers) { - if (!token) { - token = req.oauth3.token; - } - - var err; - - if (priv._accounts) { - return PromiseA.resolve(priv._accounts); - } - - if ((req.oauth3.token.idx || req.oauth3.token.usr) && ('password' === req.oauth3.token.grt || 'login' === req.oauth3.token.as)) { - priv._accounts = getAccountsByLogin(req, req.oauth3.token, priv, Controllers, (req.oauth3.token.idx || req.oauth3.token.usr), !!req.oauth3.token.idx); - } else if (req.oauth3.token.axs && req.oauth3.token.axs.length || req.oauth3.token.acx) { - req.oauth3._accounts = getAccountsByArray(req, Controllers, req.oauth3.token.axs && req.oauth3.token.axs.length && req.oauth3.token.axs || [req.oauth3.token.acx]); - } else { - err = new Error("neither login nor accounts were specified"); - err.code = "E_NO_AUTHZ"; - req.oauth3._accounts = PromiseA.reject(err); - } - - req.oauth3._accounts.then(function (accounts) { - req.oauth3._accounts = accounts; - - return accounts; - }); - - return req.oauth3._accounts; - } - - function getLoginId(req, token, priv/*, Controllers*/) { - if (!token) { - token = req.oauth3.token; - } - - var cacheId = '_' + token.idx + 'LoginId'; - - if (priv[cacheId]) { - return PromiseA.resolve(priv[cacheId]); - } - - // TODO - // this ends up defeating part of the purpose of JWT (few database calls) - // perhaps the oauthClient secret should be sent, encrypted with a master key, - // with the request? Or just mash the oauthClient secret with the loginId - // and encrypt with the master key? - priv._loginId = getClient(req, token, priv).then(function (oauthClient) { - var loginId; - - if (token.idx) { - loginId = scoper.unscope(token.idx, oauthClient.secret); - } else { - loginId = token.usr; - } - - priv[cacheId] = loginId; - - return loginId; - }); - - return priv[cacheId]; - } - - function getLogin(req, token, priv, Controllers) { - if (!token) { - token = req.oauth3.token; - } - - var cacheId = '_' + token.idx + 'Login'; - - if (priv[cacheId]) { - return PromiseA.resolve(priv[cacheId]); - } - - priv[cacheId] = getLoginId(req, token, priv).then(function (loginId) { - // DB.Logins.get(hashId) - return Controllers.Logins.rawGet(loginId).then(function (login) { - priv[cacheId] = login; - - return login; - }); - }); - - return priv[cacheId]; - } - - function attachOauth3(req, res, next) { - var privs = {}; - req.oauth3 = {}; - - getAppScopedControllers(req.experienceId).then(function (Controllers) { - - return parseAccessToken(req).then(function (token) { - if (!token) { - next(); - return; - } - - var jwt = require('jsonwebtoken'); - var data = jwt.decode(token); - var err; - - if (!data) { - err = new Error('not a json web token'); - err.code = 'E_NOT_JWT'; - res.send({ - error: err.code - , error_description: err.message - , error_url: 'https://oauth3.org/docs/errors#' + (err.code || 'E_UNKNOWN_EXCEPTION') - }); - // PromiseA.reject(err); - return; - } - - req.oauth3.token = token; - - req.oauth3.getLoginId = function (token) { - getLoginId(req, token || req.oauth3.token, privs, Controllers); - }; - - req.oauth3.getLogin = function (token) { - getLogin(req, token || req.oauth3.token, privs, Controllers); - }; - - // TODO modify prototypes? - req.oauth3.getClient = function (token) { - getClient(req, token || req.oauth3.token, privs, Controllers); - }; - - // TODO req.oauth3.getAccountIds - req.oauth3.getAccounts = function (token) { - getAccounts(req, token || req.oauth3.token, privs, Controllers); - }; - - next(); - }); - }); - } - - app.use('/', cors); - - app.use('/', attachOauth3); -}; diff --git a/lib/package-server.js b/lib/package-server.js index 60c534d..7721728 100644 --- a/lib/package-server.js +++ b/lib/package-server.js @@ -104,7 +104,7 @@ function loadPages(pkgConf, packagedPage, req, res, next) { } function getApi(conf, pkgConf, pkgDeps, packagedApi) { - var PromiseA = require('bluebird'); + var PromiseA = pkgDeps.Promise; var path = require('path'); var pkgpath = path.join(pkgConf.apipath, packagedApi.id/*, (packagedApi.api.version || '')*/); @@ -155,7 +155,9 @@ function getApi(conf, pkgConf, pkgDeps, packagedApi) { packagedApi._api = require('express-lazy')(); packagedApi._api_app = myApp; - require('./oauth3-auth').inject(conf, packagedApi._api, pkgConf, pkgDeps); + pkgDeps.getOauth3Controllers = + packagedApi._getOauth3Controllers = require('oauthcommon/example-oauthmodels').create(conf).getControllers; + require('oauthcommon').inject(packagedApi._getOauth3Controllers, packagedApi._api, pkgConf, pkgDeps); // DEBUG // @@ -245,16 +247,18 @@ function layerItUp(pkgConf, router, req, res, next) { } function runApi(opts, router, req, res, next) { + var path = require('path'); var pkgConf = opts.config; var pkgDeps = opts.deps; //var Services = opts.Services; var packagedApi; + var pathname; // TODO compile packagesMap // TODO people may want to use the framework in a non-framework way (i.e. to conceal the module name) router.packagedApis.some(function (_packagedApi) { // console.log('[DEBUG _packagedApi.id]', _packagedApi.id); - var pathname = router.pathname; + pathname = router.pathname; if ('/' === pathname) { pathname = ''; } @@ -284,7 +288,7 @@ function runApi(opts, router, req, res, next) { // TODO this identifier may need to be non-deterministic as to transfer if a domain name changes but is still the "same" app // (i.e. a company name change. maybe auto vs manual register - just like oauth3?) // NOTE: probably best to alias the name logically - , value: (req.hostname + req.pathname).replace(/\/$/, '') + , value: (path.join(req.hostname, pathname || '')).replace(/\/$/, '') }); Object.defineProperty(req, 'escapedExperienceId', { enumerable: true diff --git a/lib/worker.js b/lib/worker.js index ac9f303..e165372 100644 --- a/lib/worker.js +++ b/lib/worker.js @@ -193,6 +193,7 @@ module.exports.create = function (webserver, info, state) { , Promise: PromiseA , express: express , app: app + //, oauthmodels: require('oauthcommon/example-oauthmodels').create(info.conf) }; var Services = require('./services-loader').create(pkgConf, { memstore: memstore diff --git a/package.json b/package.json index de7bad8..4a7abed 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "cookie-session": "1.x", "cookie-signature": "^1.0.6", "crc": "^3.2.1", - "masterquest-sqlite3": "git://github.com/coolaj86/masterquest-sqlite3.git", + "ddns-cli": "^1.2.1", "debug": "^2.1.3", "depd": "^1.0.0", "destroy": "^1.0.3", @@ -81,6 +81,7 @@ "json-storage": "2.x", "jsonwebtoken": "^5.4.0", "lodash": "2.x", + "masterquest-sqlite3": "git://github.com/coolaj86/masterquest-sqlite3.git", "media-typer": "^0.3.0", "methods": "^1.1.1", "mime": "^1.3.4", @@ -104,7 +105,7 @@ "request": "2.44.0", "request-ip": "^1.1.1", "scmp": "1.x", - "secret-utils": "1.x", + "secret-utils": "^2.0.0", "semver": "^4.3.1", "send": "^0.12.2", "serve-favicon": "2.x",