From d64699977e883871246d3fc062a384e88a554e2a Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Wed, 15 Mar 2017 03:38:18 -0600 Subject: [PATCH 01/14] add dns listing --- oauth3.dns.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 oauth3.dns.js diff --git a/oauth3.dns.js b/oauth3.dns.js new file mode 100644 index 0000000..fae5e62 --- /dev/null +++ b/oauth3.dns.js @@ -0,0 +1,19 @@ +;(function (exports) { +'use strict'; + +var OAUTH3 = exports.OAUTH3 = exports.OAUTH3 || require('./oauth3.core.js').OAUTH3; + +OAUTH3.api['dns.list'] = function (providerUri, opts) { + var session = opts.session; + + return OAUTH3.request({ + method: 'GET' + , url: OAUTH3.url.normalize(providerUri) + + '/api/com.daplie.domains/accounts/' + session.token.sub + '/dns' + , session: session + }).then(function (res) { + return res.data.records || res.data; + }); +}; + +}('undefined' !== typeof exports ? exports : window)); From 64393a540d7cc60821aa1c541ef71101be418008 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Wed, 15 Mar 2017 23:48:39 -0600 Subject: [PATCH 02/14] add devices:list, devices:remove, and tunnel.token --- oauth3.dns.js | 28 ++++++++++++++++++++++++++++ oauth3.tunnel.js | 23 +++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 oauth3.tunnel.js diff --git a/oauth3.dns.js b/oauth3.dns.js index fae5e62..7d44810 100644 --- a/oauth3.dns.js +++ b/oauth3.dns.js @@ -16,4 +16,32 @@ OAUTH3.api['dns.list'] = function (providerUri, opts) { }); }; +OAUTH3.api['devices.list'] = function (providerUri, opts) { + var session = opts.session; + + return OAUTH3.request({ + url: OAUTH3.url.normalize(providerUri) + + '/api/com.daplie.domains/accounts/' + session.token.sub + '/devices' + , method: 'GET' + , session: session + }, {}).then(function (res) { + return res.data.devices || res.data; + }); +}; + +OAUTH3.api['devices.detach'] = function (providerUri, opts) { + var session = opts.session; + + return OAUTH3.request({ + url: OAUTH3.url.normalize(providerUri) + + '/api/com.daplie.domains/accounts/' + session.token.sub + + '/devices/' + opts.data.device + + '/' + opts.data.tld + '/' + opts.data.sld + '/' + (opts.data.sub || '') + , method: 'DELETE' + , session: session + }, {}).then(function (res) { + return res.data.device || res.data; + }); +}; + }('undefined' !== typeof exports ? exports : window)); diff --git a/oauth3.tunnel.js b/oauth3.tunnel.js new file mode 100644 index 0000000..3ef5cbc --- /dev/null +++ b/oauth3.tunnel.js @@ -0,0 +1,23 @@ +;(function (exports) { +'use strict'; + +var OAUTH3 = exports.OAUTH3 = exports.OAUTH3 || require('./oauth3.core.js').OAUTH3; + +OAUTH3.api['tunnel.token'] = function (providerUri, opts) { + var session = opts.session; + + return OAUTH3.request({ + method: 'POST' + , url: OAUTH3.url.normalize(providerUri) + + '/api/org.oauth3.tunnel/accounts/' + session.token.sub + '/token' + , session: session + , data: { + domains: opts.data.domains + , device: opts.data.device + } + }).then(function (res) { + return res.data.records || res.data; + }); +}; + +}('undefined' !== typeof exports ? exports : window)); From bbd75d63ebd6f4bf7b71fc319b069ef5760a05af Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Thu, 16 Mar 2017 15:41:33 -0600 Subject: [PATCH 03/14] resolve providerUri on refreshToken --- oauth3.core.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/oauth3.core.js b/oauth3.core.js index dd3fea9..e609400 100644 --- a/oauth3.core.js +++ b/oauth3.core.js @@ -587,6 +587,11 @@ return OAUTH3.hooks.session.check(preq, opts).then(fetch); } , _requestHelper: function (preq, opts) { + /* + if (opts && opts.directives) { + preq.url = OAUTH3.url.resolve(opts.directives.issuer, preq.url); + } + */ return OAUTH3._browser.request(preq, opts); } , implicitGrant: function(directives, opts) { @@ -666,7 +671,8 @@ return OAUTH3.discover(providerUri, opts).then(function (directive) { var prequest = OAUTH3.urls.refreshToken(directive, opts); - return OAUTH3.request(prequest).then(function (req) { + prequest.url = OAUTH3.url.resolve(providerUri/*directives.issuer*/, prequest.url); + return OAUTH3.request(prequest/*, { directives: directive }*/).then(function (req) { var data = req.data; data.provider_uri = providerUri; if (data.error) { @@ -1071,6 +1077,7 @@ } // TODO maybe use a baseUrl from the directives file? preq.url = OAUTH3.url.resolve(this._providerUri, preq.url); + return OAUTH3.request(preq, opts); } , logout: function (opts) { From b2a992d2d5d7ebef7ea0123127a2395ab89e01a2 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Fri, 17 Mar 2017 14:17:37 -0600 Subject: [PATCH 04/14] more dns stuff --- dns.examples.js | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ oauth3.dns.js | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 dns.examples.js diff --git a/dns.examples.js b/dns.examples.js new file mode 100644 index 0000000..a6be988 --- /dev/null +++ b/dns.examples.js @@ -0,0 +1,48 @@ +oauth3.api('devices.list').then(function (result) { + console.log(result); +}); + +oauth3.api( + 'devices.set' +, { data: { + name: 'tester.local' + , uid: 'test-01-uid' + , addresses: [ + { + type: 'A' + , address: '192.168.1.104' + } + ] + } } +).then(function (result) { + console.log('devices.set'); + console.log(result); +}); + +// TODO don't allow attaching if the device is not set +// TODO update API as well +oauth3.api( + 'devices.attach' +, { data: { + sub: 'test-01' + , sld: 'aj' + , tld: 'daplie.me' + , uid: 'test-01-uid' + } } +).then(function (result) { + console.log('devices.attach'); + console.log(result); +}); + +oauth3.api( + 'devices.detach' +, { data: { + sub: 'test-01' + , sld: 'aj' + , tld: 'daplie.me' + , uid: 'test-01-uid' + } } +).then(function (result) { + console.log('devices.detach'); + console.log(result); +}); diff --git a/oauth3.dns.js b/oauth3.dns.js index 7d44810..abc139e 100644 --- a/oauth3.dns.js +++ b/oauth3.dns.js @@ -29,6 +29,38 @@ OAUTH3.api['devices.list'] = function (providerUri, opts) { }); }; +OAUTH3.api['devices.attach'] = function (providerUri, opts) { + var session = opts.session; + + return OAUTH3.request({ + url: OAUTH3.url.normalize(providerUri) + + '/api/com.daplie.domains/accounts/' + session.token.sub + //+ '/devices/' + device + '/' + + '/devices/' + (opts.data.uid || '_') + '/' + opts.data.device + + '/' + opts.data.tld + '/' + opts.data.sld + '/' + (opts.data.sub || '') + , method: 'POST' + , session: session + }, {}).then(function (res) { + return res.data.devices || res.data; + }); +}; + +OAUTH3.api['devices.detach'] = function (providerUri, opts) { + var session = opts.session; + + return OAUTH3.request({ + url: OAUTH3.url.normalize(providerUri) + + '/api/com.daplie.domains/accounts/' + session.token.sub + //+ '/devices/' + device + '/' + + '/devices/' + (opts.data.uid || '_') + '/' + opts.data.device + + '/' + opts.data.tld + '/' + opts.data.sld + '/' + (opts.data.sub || '') + , method: 'DELETE' + , session: session + }, {}).then(function (res) { + return res.data.devices || res.data; + }); +}; + OAUTH3.api['devices.detach'] = function (providerUri, opts) { var session = opts.session; From 813410d6b52201562023bb680c784ff3b9ab9bfb Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Mon, 20 Mar 2017 13:40:29 -0600 Subject: [PATCH 05/14] don't break angular's promise :) --- oauth3.ng.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/oauth3.ng.js b/oauth3.ng.js index 97f0b96..c906151 100644 --- a/oauth3.ng.js +++ b/oauth3.ng.js @@ -15,9 +15,9 @@ angular function PromiseAngularQ(fn) { var d = $q.defer(); - $timeout(function () { + //$timeout(function () { fn(d.resolve, d.reject); - }, 0); + //}, 0); //this.then = d.promise.then; //this.catch = d.promise.catch; From fcb2bdcd457b9d4f160950ef7090d8e45f83f72e Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Mon, 20 Mar 2017 13:41:12 -0600 Subject: [PATCH 06/14] v1.0.0 --- bower.json | 37 +++++++++++++++++++++++++++++++++++++ package.json | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 bower.json create mode 100644 package.json diff --git a/bower.json b/bower.json new file mode 100644 index 0000000..073c9ff --- /dev/null +++ b/bower.json @@ -0,0 +1,37 @@ +{ + "name": "oauth3", + "authors": [ + "AJ ONeal " + ], + "description": "The world's smallest, fastest, and most secure OAuth3 (and OAuth2) JavaScript implementation.", + "main": "oauth3.core.js", + "keywords": [ + "oauth", + "oauth2", + "oauth3", + "oidc", + "openid", + "connect", + "openidconnect", + "authn", + "authz", + "authentication", + "authorization", + "user", + "password", + "passphrase", + "login", + "signin", + "log", + "sign" + ], + "license": "MIT", + "homepage": "https://git.daplie.com/OAuth3/oauth3.js", + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests" + ] +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..02d4599 --- /dev/null +++ b/package.json @@ -0,0 +1,35 @@ +{ + "name": "oauth3", + "version": "1.0.0", + "description": "The world's smallest, fastest, and most secure OAuth3 (and OAuth2) JavaScript implementation.", + "main": "oauth3.node.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git@git.daplie.com:OAuth3/oauth3.js.git" + }, + "keywords": [ + "oauth", + "oauth2", + "oauth3", + "oidc", + "openid", + "connect", + "openidconnect", + "authn", + "authz", + "authentication", + "authorization", + "user", + "password", + "passphrase", + "login", + "signin", + "log", + "sign" + ], + "author": "AJ ONeal (https://coolaj86.com/)", + "license": "(MIT OR Apache-2.0)" +} From d382464cafe12c003cb4cec9cc53bb4561c1677c Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Mon, 20 Mar 2017 13:44:04 -0600 Subject: [PATCH 07/14] oauth3.js as package name for now --- bower.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bower.json b/bower.json index 073c9ff..ab2e3cd 100644 --- a/bower.json +++ b/bower.json @@ -1,5 +1,5 @@ { - "name": "oauth3", + "name": "oauth3.js", "authors": [ "AJ ONeal " ], diff --git a/package.json b/package.json index 02d4599..6ef6685 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "oauth3", + "name": "oauth3.js", "version": "1.0.0", "description": "The world's smallest, fastest, and most secure OAuth3 (and OAuth2) JavaScript implementation.", "main": "oauth3.node.js", From a2c4e718fde930682666b8a5642a2f017d1183bc Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Mon, 20 Mar 2017 13:48:34 -0600 Subject: [PATCH 08/14] script to bump versions --- bump-versions.sh | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 bump-versions.sh diff --git a/bump-versions.sh b/bump-versions.sh new file mode 100644 index 0000000..e7875b5 --- /dev/null +++ b/bump-versions.sh @@ -0,0 +1,14 @@ +git push --tags + +git checkout v1.0 +git push + +git checkout v1 +git merge v1.0 +git push + +git checkout master +git merge v1 +git push + +git checkout v1.0 From f2e6ea5890de6240f3c3bdf290b0b9dd9ae1d8f4 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Mon, 20 Mar 2017 17:55:00 -0600 Subject: [PATCH 09/14] test for window --- oauth3.core.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/oauth3.core.js b/oauth3.core.js index e609400..2295f9e 100644 --- a/oauth3.core.js +++ b/oauth3.core.js @@ -148,7 +148,7 @@ // TODO put in different file for browser vs node try { return Array.prototype.slice.call( - window.crypto.getRandomValues(new Uint8Array(16)) + OAUTH3._browser.window.crypto.getRandomValues(new Uint8Array(16)) ).map(function (ch) { return (ch).toString(16); }).join(''); } catch(e) { return OAUTH3.utils._insecureRandomState(); @@ -723,7 +723,7 @@ // Let the Code Waste begin!! // , _browser: { - window: window + window: 'undefined' !== typeof window ? window : null // TODO we don't need to include this if we're using jQuery or angular , discover: function(providerUri, opts) { opts = opts || {}; From 847e8c2a6a6478c6486e5ed92ab8be91af7d0560 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Mon, 20 Mar 2017 17:55:27 -0600 Subject: [PATCH 10/14] begining of node support --- oauth3.node.js | 112 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 oauth3.node.js diff --git a/oauth3.node.js b/oauth3.node.js new file mode 100644 index 0000000..156cd5e --- /dev/null +++ b/oauth3.node.js @@ -0,0 +1,112 @@ +'use strict'; + +//var OAUTH3 = require('./oauth3.core.js').OAUTH3; +var OAUTH3 = require('./oauth3.issuer.js').OAUTH3; +// used for OAUTH3.urls.resourcePasswordOwner +// used for OAUTH3.authn.loginMeta +// used for OAUTH3.authn.otp +// used for OAUTH3.authn.resourcePasswordOwner +var PromiseA = require('bluebird'); +var requestAsync = PromiseA.promisify(require('request')); +var crypto = require('crypto'); + +OAUTH3._discoverHelper = function(providerUri, opts) { + return OAUTH3._browser.discover(providerUri, opts); +}; +OAUTH3._requestHelper = function (preq, opts) { + /* + if (opts && opts.directives) { + preq.url = OAUTH3.url.resolve(opts.directives.issuer, preq.url); + } + */ + return OAUTH3._node.request(preq, opts); +}; +OAUTH3._node = {}; +OAUTH3._node.discover = function(providerUri/*, opts*/) { + return OAUTH3.request({ + method: 'GET' + , url: OAUTH3.url.normalize(providerUri) + '/.well-known/oauth3/directives.json' + }).then(function (resp) { + return resp.data; + }); +}; +OAUTH3._node.request = function(preq/*, _sys*/) { + var data = { + method: preq.method + , url: preq.url || preq.uri + , headers: preq.headers + , json: preq.data || preq.body || preq.json || undefined // TODO which to use? + , formData: preq.formData || undefined + }; + + //console.log('DEBUG request'); + //console.log(preq.url || preq.uri); + //console.log(data.json); + + return requestAsync(data).then(OAUTH3._node._parseJson); +}; +OAUTH3._node._parseJson = function (resp) { + var err; + var json = resp.body; + + // TODO toCamelCase + if (!(resp.statusCode >= 200 && resp.statusCode < 400)) { + // console.log('[A3] DEBUG', resp.body); + err = new Error("bad response code: " + resp.statusCode); + err.result = resp.body; + return PromiseA.reject(err); + } + + //console.log('resp.body', typeof resp.body); + if ('string' === typeof json) { + try { + json = JSON.parse(json); + } catch(e) { + err = new Error('response not parsable:' + resp.body); + err.result = resp.body; + return PromiseA.reject(err); + } + } + + // handle both Oauth2- and node-style errors + if (json.error) { + err = new Error(json.error && json.error.message || json.error_description || json.error); + err.result = json; + return PromiseA.reject(err); + } + + return json; +}; +OAUTH3._logoutHelper = function(directives, opts) { + var logoutReq = OAUTH3.urls.logout( + directives + , { client_id: (opts.client_id || opts.client_uri || OAUTH3.clientUri(OAUTH3._browser.window.location)) + , windowType: 'popup' // we'll figure out background later + , broker: opts.broker + //, state: opts._state + , debug: opts.debug + } + ); + + return OAUTH3._browser.frameRequest( + OAUTH3.url.resolve(directives.issuer, logoutReq.url) + , logoutReq.state // state should recycle params + , { windowType: 'popup' + , reuseWindow: opts.broker && '-broker' + , debug: opts.debug + } + ).then(function (params) { + OAUTH3._browser.closeFrame(params.state || opts._state, opts); + + if (params.error) { + // TODO directives.audience + return OAUTH3.PromiseA.reject(OAUTH3.error.parse(directives.issuer /*providerUri*/, params)); + } + + return params; + }); +}; +OAUTH3._node.randomState = function () { + return crypto.randomBytes(16).toString('hex'); +}; +OAUTH3.randomState = OAUTH3._node.randomState; From dbd264794118fdcf2dfa29dad770c9bfa322cb14 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Mon, 20 Mar 2017 17:55:27 -0600 Subject: [PATCH 11/14] beginning of node support --- oauth3.node.js | 112 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 oauth3.node.js diff --git a/oauth3.node.js b/oauth3.node.js new file mode 100644 index 0000000..156cd5e --- /dev/null +++ b/oauth3.node.js @@ -0,0 +1,112 @@ +'use strict'; + +//var OAUTH3 = require('./oauth3.core.js').OAUTH3; +var OAUTH3 = require('./oauth3.issuer.js').OAUTH3; +// used for OAUTH3.urls.resourcePasswordOwner +// used for OAUTH3.authn.loginMeta +// used for OAUTH3.authn.otp +// used for OAUTH3.authn.resourcePasswordOwner +var PromiseA = require('bluebird'); +var requestAsync = PromiseA.promisify(require('request')); +var crypto = require('crypto'); + +OAUTH3._discoverHelper = function(providerUri, opts) { + return OAUTH3._browser.discover(providerUri, opts); +}; +OAUTH3._requestHelper = function (preq, opts) { + /* + if (opts && opts.directives) { + preq.url = OAUTH3.url.resolve(opts.directives.issuer, preq.url); + } + */ + return OAUTH3._node.request(preq, opts); +}; +OAUTH3._node = {}; +OAUTH3._node.discover = function(providerUri/*, opts*/) { + return OAUTH3.request({ + method: 'GET' + , url: OAUTH3.url.normalize(providerUri) + '/.well-known/oauth3/directives.json' + }).then(function (resp) { + return resp.data; + }); +}; +OAUTH3._node.request = function(preq/*, _sys*/) { + var data = { + method: preq.method + , url: preq.url || preq.uri + , headers: preq.headers + , json: preq.data || preq.body || preq.json || undefined // TODO which to use? + , formData: preq.formData || undefined + }; + + //console.log('DEBUG request'); + //console.log(preq.url || preq.uri); + //console.log(data.json); + + return requestAsync(data).then(OAUTH3._node._parseJson); +}; +OAUTH3._node._parseJson = function (resp) { + var err; + var json = resp.body; + + // TODO toCamelCase + if (!(resp.statusCode >= 200 && resp.statusCode < 400)) { + // console.log('[A3] DEBUG', resp.body); + err = new Error("bad response code: " + resp.statusCode); + err.result = resp.body; + return PromiseA.reject(err); + } + + //console.log('resp.body', typeof resp.body); + if ('string' === typeof json) { + try { + json = JSON.parse(json); + } catch(e) { + err = new Error('response not parsable:' + resp.body); + err.result = resp.body; + return PromiseA.reject(err); + } + } + + // handle both Oauth2- and node-style errors + if (json.error) { + err = new Error(json.error && json.error.message || json.error_description || json.error); + err.result = json; + return PromiseA.reject(err); + } + + return json; +}; +OAUTH3._logoutHelper = function(directives, opts) { + var logoutReq = OAUTH3.urls.logout( + directives + , { client_id: (opts.client_id || opts.client_uri || OAUTH3.clientUri(OAUTH3._browser.window.location)) + , windowType: 'popup' // we'll figure out background later + , broker: opts.broker + //, state: opts._state + , debug: opts.debug + } + ); + + return OAUTH3._browser.frameRequest( + OAUTH3.url.resolve(directives.issuer, logoutReq.url) + , logoutReq.state // state should recycle params + , { windowType: 'popup' + , reuseWindow: opts.broker && '-broker' + , debug: opts.debug + } + ).then(function (params) { + OAUTH3._browser.closeFrame(params.state || opts._state, opts); + + if (params.error) { + // TODO directives.audience + return OAUTH3.PromiseA.reject(OAUTH3.error.parse(directives.issuer /*providerUri*/, params)); + } + + return params; + }); +}; +OAUTH3._node.randomState = function () { + return crypto.randomBytes(16).toString('hex'); +}; +OAUTH3.randomState = OAUTH3._node.randomState; From afb021af9b45e68931ea325a3ffd1afa12de89fd Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Mon, 20 Mar 2017 23:29:03 -0600 Subject: [PATCH 12/14] playing around with node.js --- bin/oauth3.js | 43 ++++++++++++++++++++++++++++++++++++++++++ oauth3.core.js | 3 ++- oauth3.node.js | 37 ++++++++---------------------------- oauth3.node.storage.js | 21 +++++++++++++++++++++ 4 files changed, 74 insertions(+), 30 deletions(-) create mode 100644 bin/oauth3.js create mode 100644 oauth3.node.storage.js diff --git a/bin/oauth3.js b/bin/oauth3.js new file mode 100644 index 0000000..b7bbe8f --- /dev/null +++ b/bin/oauth3.js @@ -0,0 +1,43 @@ +'use strict'; + +// process.stdout.isTTY +var form = require('terminal-forms.js').create(process.stdin, process.stdout); +var OAUTH3 = require('../oauth3.node.js'); +OAUTH3.hooks.directives._get = require('../oauth3.node.storage.js').directives._get; +OAUTH3.hooks.directives._set = require('../oauth3.node.storage.js').directives._set; +var url = require('url'); +console.log('stdin tty', process.stdin.isTTY); +console.log('stdout tty', process.stdout.isTTY); + +form.ask({ label: "What's your OAuth3 Provider URL? ", type: 'url' }).then(function (urlResult) { + var urlObj = url.parse(urlResult.input); + // TODO get unique client id for bootstrapping app + var oauth3 = OAUTH3.create(urlObj); + var providerPromise = oauth3.setProvider(urlObj.host + urlObj.pathname); + + function getCurrentUserEmail() { + return form.ask({ label: "What's your email (or cloud mail) address? ", type: 'email' }).then(function (emailResult) { + // TODO lookup uuid locally before performing loginMeta + // TODO lookup token locally before performing loginMeta / otp + return providerPromise.then(function () { + return OAUTH3.authn.loginMeta(oauth3._providerDirectives, { email: emailResult.input }).then(function (result) { + // TODO require hashcash to create user account + console.log(result); + return form.PromiseA.reject(new Error("not implemented")); + }); + }); + }); + } + + getCurrentUserEmail().then(function (email) { + // TODO skip if token exists locally + return OAUTH3.authn.otp(oauth3._providerDirectives, { email: email }).then(function (otpResult) { + return form.ask({ + label: "What's your login code?" + , help: "(it was sent to '" + urlResult.input + "' and looks like 1234-5678-9012)" + }).then(function () { + console.log(otpResult); + }); + }); + }); +}); diff --git a/oauth3.core.js b/oauth3.core.js index 2295f9e..d9fbaf3 100644 --- a/oauth3.core.js +++ b/oauth3.core.js @@ -4,7 +4,7 @@ var OAUTH3 = exports.OAUTH3 = { clientUri: function (location) { - return OAUTH3.uri.normalize(location.host + location.pathname); + return OAUTH3.uri.normalize(location.host + (location.pathname || '')); } , error: { parse: function (providerUri, params) { @@ -1024,6 +1024,7 @@ } if (this._providerUri) { p = OAUTH3.discover(this._providerUri, { client_id: this._clientUri }).then(function (/*directives*/) { + console.error("BUG: there's some jquery in oauth3.core.js that needs to be removed and tested"); $('.js-signin').removeAttr('disabled'); }); } diff --git a/oauth3.node.js b/oauth3.node.js index 156cd5e..fff8733 100644 --- a/oauth3.node.js +++ b/oauth3.node.js @@ -10,8 +10,9 @@ var PromiseA = require('bluebird'); var requestAsync = PromiseA.promisify(require('request')); var crypto = require('crypto'); +OAUTH3.PromiseA = PromiseA; OAUTH3._discoverHelper = function(providerUri, opts) { - return OAUTH3._browser.discover(providerUri, opts); + return OAUTH3._node.discover(providerUri, opts); }; OAUTH3._requestHelper = function (preq, opts) { /* @@ -75,38 +76,16 @@ OAUTH3._node._parseJson = function (resp) { return PromiseA.reject(err); } - return json; + resp.data = json; + return resp; }; OAUTH3._logoutHelper = function(directives, opts) { - var logoutReq = OAUTH3.urls.logout( - directives - , { client_id: (opts.client_id || opts.client_uri || OAUTH3.clientUri(OAUTH3._browser.window.location)) - , windowType: 'popup' // we'll figure out background later - , broker: opts.broker - //, state: opts._state - , debug: opts.debug - } - ); - - return OAUTH3._browser.frameRequest( - OAUTH3.url.resolve(directives.issuer, logoutReq.url) - , logoutReq.state // state should recycle params - , { windowType: 'popup' - , reuseWindow: opts.broker && '-broker' - , debug: opts.debug - } - ).then(function (params) { - OAUTH3._browser.closeFrame(params.state || opts._state, opts); - - if (params.error) { - // TODO directives.audience - return OAUTH3.PromiseA.reject(OAUTH3.error.parse(directives.issuer /*providerUri*/, params)); - } - - return params; - }); + // TODO allow prompting of which account + return OAUTH3.PromiseA.reject(new Error("logout not yet implemented for node.js")); }; OAUTH3._node.randomState = function () { return crypto.randomBytes(16).toString('hex'); }; OAUTH3.randomState = OAUTH3._node.randomState; + +module.exports = OAUTH3; diff --git a/oauth3.node.storage.js b/oauth3.node.storage.js new file mode 100644 index 0000000..1e0b81a --- /dev/null +++ b/oauth3.node.storage.js @@ -0,0 +1,21 @@ +'use strict'; + +var fs = require('fs'); +var path = require('path'); + +module.exports = { + directives: { + _get: function (providerUri) { + // TODO make safe + try { + return require(path.join(process.cwd(), providerUri + '.directives.json')); + } catch(e) { + return null; + } + } + , _set: function (providerUri, directives) { + fs.writeFileSync(path.join(process.cwd(), providerUri + '.directives.json'), JSON.stringify(directives, null, 2)); + return directives; + } + } +}; From cc4af8f95ac606392271c4f9f351adf4cce6156a Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Tue, 21 Mar 2017 01:02:41 -0600 Subject: [PATCH 13/14] basic node.js authentication tested --- bin/oauth3.js | 81 ++++++++++++++++++++++++++++++++++++------ oauth3.core.js | 9 +++-- oauth3.issuer.js | 9 +++-- oauth3.node.js | 16 +++++++-- oauth3.node.storage.js | 15 ++++++++ 5 files changed, 109 insertions(+), 21 deletions(-) diff --git a/bin/oauth3.js b/bin/oauth3.js index b7bbe8f..2f74aaf 100644 --- a/bin/oauth3.js +++ b/bin/oauth3.js @@ -3,14 +3,17 @@ // process.stdout.isTTY var form = require('terminal-forms.js').create(process.stdin, process.stdout); var OAUTH3 = require('../oauth3.node.js'); +// TODO change to ._hooks OAUTH3.hooks.directives._get = require('../oauth3.node.storage.js').directives._get; OAUTH3.hooks.directives._set = require('../oauth3.node.storage.js').directives._set; +OAUTH3.hooks.session._get = require('../oauth3.node.storage.js').session._get; +OAUTH3.hooks.session._set = require('../oauth3.node.storage.js').session._set; var url = require('url'); -console.log('stdin tty', process.stdin.isTTY); -console.log('stdout tty', process.stdout.isTTY); +//console.log('stdin tty', process.stdin.isTTY); +//console.log('stdout tty', process.stdout.isTTY); form.ask({ label: "What's your OAuth3 Provider URL? ", type: 'url' }).then(function (urlResult) { - var urlObj = url.parse(urlResult.input); + var urlObj = url.parse(urlResult.result || urlResult.input); // TODO get unique client id for bootstrapping app var oauth3 = OAUTH3.create(urlObj); var providerPromise = oauth3.setProvider(urlObj.host + urlObj.pathname); @@ -20,23 +23,79 @@ form.ask({ label: "What's your OAuth3 Provider URL? ", type: 'url' }).then(funct // TODO lookup uuid locally before performing loginMeta // TODO lookup token locally before performing loginMeta / otp return providerPromise.then(function () { - return OAUTH3.authn.loginMeta(oauth3._providerDirectives, { email: emailResult.input }).then(function (result) { + return OAUTH3.authn.loginMeta(oauth3._providerDirectives, { email: emailResult.input }).then(function (/*result*/) { + return emailResult.input; + }, function (/*err*/) { // TODO require hashcash to create user account - console.log(result); - return form.PromiseA.reject(new Error("not implemented")); + function confirmCreateAccount() { + // TODO directives should specify private (invite-only) vs internal (request) vs public (allow) accounts + return form.ask({ + label: "We don't recognize that address. Do you want to create a new account? [Y/n] " + , type: 'text' // TODO boolean with default Y or N + }).then(function (result) { + if (!result.input) { + result.input = 'Y'; + } + + result.input = result.input.toLowerCase(); + + if ('y' !== result.input) { + return getCurrentUserEmail(); + } + + return emailResult.input; + }); + } + + return confirmCreateAccount(); }); }); }); } - getCurrentUserEmail().then(function (email) { + return getCurrentUserEmail().then(function (email) { // TODO skip if token exists locally + form.println("Sending login code to '" + email + "'..."); return OAUTH3.authn.otp(oauth3._providerDirectives, { email: email }).then(function (otpResult) { return form.ask({ - label: "What's your login code?" - , help: "(it was sent to '" + urlResult.input + "' and looks like 1234-5678-9012)" - }).then(function () { - console.log(otpResult); + label: "What's your login code? " + , help: "(it was sent to '" + email + "' and looks like 1234-5678-9012)" + // onkeyup + // ondebounce + // onchange + // regexp // html5 name? + , onReturnAsync: function (rs, ws, input/*, ch*/) { + var formatted = input.toLowerCase().replace(/[^\d]+/g, ''); + + if (12 !== formatted.length) { + return form.PromiseA.reject(new Error("invalid code please try again in the format xxxx-yyyy-zzzz")); + } + + formatted = formatted.match(/.{4,4}/g).join('-'); + + if (14 !== formatted.split('').length) { + return form.PromiseA.reject(new Error("invalid code '" + formatted + "', please try again xxxx-yyyy-zzzz")); + } + + var data = { + username: email + , username_type: 'email' + , client_id: OAUTH3.uri.normalize(oauth3._providerDirectives.issuer) + , client_uri: OAUTH3.uri.normalize(oauth3._providerDirectives.issuer) + , otp_code: formatted + , otp_uuid: otpResult.data.uuid + }; + + // returns session instead of input + var colors = require('colors'); + form.setStatus(colors.dim("authenticating with server...")); + return OAUTH3.authn.resourceOwnerPassword(oauth3._providerDirectives, data); + } + }).then(function (results) { + var session = results.result; + + form.println('session:'); + form.println(session); }); }); }); diff --git a/oauth3.core.js b/oauth3.core.js index d9fbaf3..b34fb4f 100644 --- a/oauth3.core.js +++ b/oauth3.core.js @@ -817,7 +817,8 @@ } resolve({ - request: xhr + _request: xhr + , headers: null // TODO , data: data , status: xhr.status }); @@ -1023,10 +1024,8 @@ this._clientUri = OAUTH3.clientUri(location); } if (this._providerUri) { - p = OAUTH3.discover(this._providerUri, { client_id: this._clientUri }).then(function (/*directives*/) { - console.error("BUG: there's some jquery in oauth3.core.js that needs to be removed and tested"); - $('.js-signin').removeAttr('disabled'); - }); + // returns directives + p = OAUTH3.discover(this._providerUri, { client_id: this._clientUri }); } return OAUTH3.discover(this._clientUri, { client_id: this._clientUri }).then(function (clientDirectives) { diff --git a/oauth3.issuer.js b/oauth3.issuer.js index dfacf07..c783992 100644 --- a/oauth3.issuer.js +++ b/oauth3.issuer.js @@ -130,6 +130,7 @@ OAUTH3.urls.resourceOwnerPassword = function (directive, opts) { var clientUri = opts.client_uri; var args = directive[type]; var otpCode = opts.otp || opts.otpCode || opts.otp_code || opts.otpToken || opts.otp_token || undefined; + // TODO require user agent var params = { client_id: opts.client_id || opts.client_uri , client_uri: opts.client_uri @@ -239,20 +240,23 @@ OAUTH3.authn.loginMeta = function (directive, opts) { return OAUTH3.request({ method: directive.credential_meta.method || 'GET' // TODO lint urls + // TODO client_uri , url: OAUTH3.url.resolve(directive.issuer, directive.credential_meta.url) .replace(':type', 'email') .replace(':id', opts.email) }); }; OAUTH3.authn.otp = function (directive, opts) { + // TODO client_uri var preq = { method: directive.credential_otp.method || 'POST' , url: OAUTH3.url.resolve(directive.issuer, directive.credential_otp.url) , data: { // TODO replace with signed hosted file client_agree_tos: 'oauth3.org/tos/draft' - , client_id: directive.issuer // In this case, the issuer is its own client - , client_uri: directive.issuer + // TODO unbreak the client_uri option (if broken) + , client_id: /*opts.client_id ||*/ OAUTH3.uri.normalize(directive.issuer) // In this case, the issuer is its own client + , client_uri: /*opts.client_uri ||*/ OAUTH3.uri.normalize(directive.issuer) , request_otp: true , username: opts.email } @@ -268,6 +272,7 @@ OAUTH3.authn.resourceOwnerPassword = function (directive, opts) { return OAUTH3.discover(providerUri, opts).then(function (directive) { var prequest = OAUTH3.urls.resourceOwnerPassword(directive, opts); + // TODO return not the raw request? return OAUTH3.request(prequest).then(function (req) { var data = req.data; data.provider_uri = providerUri; diff --git a/oauth3.node.js b/oauth3.node.js index fff8733..95b0bd3 100644 --- a/oauth3.node.js +++ b/oauth3.node.js @@ -22,6 +22,13 @@ OAUTH3._requestHelper = function (preq, opts) { */ return OAUTH3._node.request(preq, opts); }; +OAUTH3._base64.atob = function (base64) { + return new Buffer(base64, 'base64').toString('utf8'); +}; +OAUTH3._base64.btoa = function (text) { + return new Buffer(text, 'utf8').toString('base64'); +}; + OAUTH3._node = {}; OAUTH3._node.discover = function(providerUri/*, opts*/) { return OAUTH3.request({ @@ -76,10 +83,13 @@ OAUTH3._node._parseJson = function (resp) { return PromiseA.reject(err); } - resp.data = json; - return resp; + return { + headers: resp.headers + , status: resp.statusCode + , data: json + }; }; -OAUTH3._logoutHelper = function(directives, opts) { +OAUTH3._logoutHelper = function(/*directives, opts*/) { // TODO allow prompting of which account return OAUTH3.PromiseA.reject(new Error("logout not yet implemented for node.js")); }; diff --git a/oauth3.node.storage.js b/oauth3.node.storage.js index 1e0b81a..0285a30 100644 --- a/oauth3.node.storage.js +++ b/oauth3.node.storage.js @@ -18,4 +18,19 @@ module.exports = { return directives; } } + +, session: { + _get: function (providerUri) { + // TODO make safe + try { + return require(path.join(process.cwd(), providerUri + '.session.json')); + } catch(e) { + return null; + } + } + , _set: function (providerUri, session) { + fs.writeFileSync(path.join(process.cwd(), providerUri + '.session.json'), JSON.stringify(session, null, 2)); + return session; + } + } }; From cf6c1b9e5a703193b697568fc55d32d12cb525bb Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Tue, 21 Mar 2017 11:00:18 -0600 Subject: [PATCH 14/14] add submodule --- .gitmodules | 3 +++ node_modules/terminal-forms.js | 1 + 2 files changed, 4 insertions(+) create mode 100644 .gitmodules create mode 160000 node_modules/terminal-forms.js diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..7f951bf --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "node_modules/terminal-forms.js"] + path = node_modules/terminal-forms.js + url = git@git.daplie.com:/OAuth3/terminal-forms.js diff --git a/node_modules/terminal-forms.js b/node_modules/terminal-forms.js new file mode 160000 index 0000000..66d46ea --- /dev/null +++ b/node_modules/terminal-forms.js @@ -0,0 +1 @@ +Subproject commit 66d46eab32d8014f43307f9fbe97027b8c913f7a