Merge branch 'master' into signing

This commit is contained in:
tigerbot 2017-03-21 14:09:45 -06:00
commit 9cfd517880
14 changed files with 501 additions and 19 deletions

3
.gitmodules vendored Normal file
View File

@ -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

102
bin/oauth3.js Normal file
View File

@ -0,0 +1,102 @@
'use strict';
// 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);
form.ask({ label: "What's your OAuth3 Provider URL? ", type: 'url' }).then(function (urlResult) {
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);
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*/) {
return emailResult.input;
}, function (/*err*/) {
// TODO require hashcash to create user account
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();
});
});
});
}
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 '" + 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);
});
});
});
});

37
bower.json Normal file
View File

@ -0,0 +1,37 @@
{
"name": "oauth3.js",
"authors": [
"AJ ONeal <aj@daplie.com>"
],
"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"
]
}

14
bump-versions.sh Normal file
View File

@ -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

48
dns.examples.js Normal file
View File

@ -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);
});

1
node_modules/terminal-forms.js generated vendored Submodule

@ -0,0 +1 @@
Subproject commit 66d46eab32d8014f43307f9fbe97027b8c913f7a

View File

@ -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) {
@ -175,7 +175,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();
@ -616,6 +616,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) {
@ -695,7 +700,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) {
@ -747,7 +753,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 || {};
@ -841,7 +847,8 @@
}
resolve({
request: xhr
_request: xhr
, headers: null // TODO
, data: data
, status: xhr.status
});
@ -1047,9 +1054,8 @@
this._clientUri = OAUTH3.clientUri(location);
}
if (this._providerUri) {
p = OAUTH3.discover(this._providerUri, { client_id: this._clientUri }).then(function (/*directives*/) {
$('.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) {
@ -1101,6 +1107,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) {

79
oauth3.dns.js Normal file
View File

@ -0,0 +1,79 @@
;(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;
});
};
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.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;
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));

View File

@ -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;

View File

@ -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;

101
oauth3.node.js Normal file
View File

@ -0,0 +1,101 @@
'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.PromiseA = PromiseA;
OAUTH3._discoverHelper = function(providerUri, opts) {
return OAUTH3._node.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._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({
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 {
headers: resp.headers
, status: resp.statusCode
, data: json
};
};
OAUTH3._logoutHelper = function(/*directives, opts*/) {
// 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;

36
oauth3.node.storage.js Normal file
View File

@ -0,0 +1,36 @@
'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;
}
}
, 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;
}
}
};

23
oauth3.tunnel.js Normal file
View File

@ -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));

View File

@ -1,12 +1,36 @@
{
"name": "oauth3",
"respository": {
"type": "git",
"url": "git+ssh://git@git.daplie.com:OAuth3/oauth3.js.git"
},
"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",
"scripts": {
"install": "./node_modules/.bin/gulp"
"install": "./node_modules/.bin/gulp",
"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"
],
"devDependencies": {
"browserify-aes": "^1.0.6",
"create-hash": "^1.1.2",
@ -20,5 +44,7 @@
"gulp-streamify": "^1.0.2",
"gulp-uglify": "^2.1.0",
"vinyl-source-stream": "^1.1.0"
}
},
"author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)",
"license": "(MIT OR Apache-2.0)"
}