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; + } + } };