diff --git a/oauth3.core.js b/oauth3.core.js index e268388..c500461 100644 --- a/oauth3.core.js +++ b/oauth3.core.js @@ -1,4 +1,4 @@ -/* global Promise */ +/ * global Promise */ ;(function (exports) { 'use strict'; @@ -294,34 +294,41 @@ } } , urls: { - discover: function (providerUri, opts) { + , rpc: function (providerUri, opts) { if (!providerUri) { - throw new Error("cannot discover without providerUri"); + throw new Error("cannot run rpc without providerUri"); } if (!opts.client_id) { - throw new Error("cannot discover without options.client_id"); + throw new Error("cannot run rpc without options.client_id"); } var clientId = OAUTH3.url.normalize(opts.client_id || opts.client_uri); providerUri = OAUTH3.url.normalize(providerUri); var params = { - action: 'directives' - , state: opts.state || OAUTH3.utils.randomState() + state: opts.state || OAUTH3.utils.randomState() , redirect_uri: clientId + (opts.client_callback_path || '/.well-known/oauth3/callback.html#/') , response_type: 'rpc' , _method: 'GET' - , _pathname: '.well-known/oauth3/directives.json' + , _scheme: opts._scheme + , _pathname: opts._pathname , debug: opts.debug || undefined }; - var result = { + var toRequest = { url: providerUri + '/.well-known/oauth3/#/?' + OAUTH3.query.stringify(params) , state: params.state , method: 'GET' , query: params }; - return result; + return toRequest; + } + , discover: function (providerUri, opts) { + return OAUTH3.urls.directives(providerUri, opts); + } + , directives: function (providerUri, opts) { + opts._pathname = ".well-known/oauth3/scopes.json"; + return OAUTH3.urls.rpc(providerUri, opts); } , implicitGrant: function (directive, opts) { // @@ -530,6 +537,14 @@ return OAUTH3.PromiseA.resolve(OAUTH3._hooks.directives.clear()); } } + , scopes: { + get: function(providerUri) { + //TODO: retrieve cached scopes + } + , set: function(providerUri, scopes) { + //TODO: cache scopes + } + } , session: { refresh: function (oldSession, newSession) { var providerUri = oldSession.provider_uri; @@ -658,9 +673,29 @@ } } } - , discover: function (providerUri, opts) { + , discoverScopes: function (providerUri, opts) { + return OAUTH.scopes(providerUri, opts); + } + , scopes: function (providerUri, opts) { if (!providerUri) { - throw new Error('oauth3.discover(providerUri, opts) received providerUri as ' + providerUri); + throw new Error('oauth3.discoverScopes(providerUri, opts) received providerUri as :', providerUri); + } + + opts = opts || {}; + opts._pathname = ".well-known/oauth3/scopes.json"; + + //TODO: add caching + + return OAUTH3._rpcHelper(providerUri, opts).then(function(scopes) { + return scopes; + }); + } + , discover: function (providerUri, opts) { + return OAUTH3.directives(providerUri, opts); + } + , directives: function (providerUri, opts) { + if (!providerUri) { + throw new Error('oauth3.discover(providerUri, opts) received providerUri as :', providerUri); } return OAUTH3.hooks.directives.get(providerUri).then(function (directives) { @@ -668,7 +703,8 @@ return directives; } - return OAUTH3._discoverHelper(providerUri, opts).then(function (directives) { + opts._pathname = ".well-known/oauth3/directives.json"; + return OAUTH3._rpcHelper(providerUri, opts).then(function (directives) { directives.azp = directives.azp || OAUTH3.url.normalize(providerUri); directives.issuer = directives.issuer || OAUTH3.url.normalize(providerUri); directives.api = OAUTH3.url.normalize((directives.api||':hostname').replace(/:hostname/, OAUTH3.uri.normalize(directives.issuer) || OAUTH3.uri.normalize(providerUri))); @@ -677,8 +713,8 @@ }); }); } - , _discoverHelper: function(providerUri, opts) { - return OAUTH3._browser.discover(providerUri, opts); + , _rpcHelper: function(providerUri, opts) { + return OAUTH3._browser.rpc(providerUri, opts); } , request: function (preq, opts) { function fetch() { @@ -858,21 +894,28 @@ // , _browser: { 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) { + , rpc: function(providerUri, opts) { opts = opts || {}; providerUri = OAUTH3.url.normalize(providerUri); + // TODO SECURITY should we whitelist our own self? if (OAUTH3.uri.normalize(providerUri).replace(/\/.*/, '') === OAUTH3.uri.normalize(OAUTH3._browser.window.location.hostname)) { - console.warn("It looks like you're a provider checking for your own directive," + console.warn("It looks like you're a provider trying to run rpc on yourself," + " so we we're just gonna use" - + " OAUTH3.request({ method: 'GET', url: '.well-known/oauth3/directive.json' })"); - return OAUTH3.request({ - method: 'GET' - , url: OAUTH3.url.normalize(providerUri) + '/.well-known/oauth3/directives.json' - }).then(function (resp) { - return resp.data; - }); + + " OAUTH3.request({ method: 'GET', url: " + + "'" + opts._pathname + "' })"); + + if (/localstorage/i.test(opts._scheme)) { + return OAUTH3.PromiseA.resolve(localStorage.getItem(opts._pathname)); + } + else { + return OAUTH3.request({ + method: 'GET' + , url: OAUTH3.url.normalize(providerUri) + opts._pathname // '/.well-known/oauth3/' + discoverFile + }).then(function (resp) { + return resp.data; + }); + } } if (!(opts.client_id || opts.client_uri).match(OAUTH3._browser.window.location.hostname)) { @@ -881,17 +924,20 @@ console.warn(opts.client_id || opts.client_uri, OAUTH3._browser.window.location.hostname); } - var discReq = OAUTH3.urls.discover( + var discReq = OAUTH3.urls.rpc( providerUri , { client_id: (opts.client_id || opts.client_uri || OAUTH3.clientUri(OAUTH3._browser.window.location)) , windowType: opts.broker && opts.windowType || 'background' , broker: opts.broker , state: opts._state || undefined , debug: opts.debug + , _scheme: opts._scheme + , _pathname: opts._pathname + , _method: opts._method } ); opts._state = discReq.state; - //var discReq = OAUTH3.urls.discover(providerUri, opts); + //var discReq = OAUTH3.urls.rpc(providerUri, opts); // hmm... we're gonna need a broker for this since switching windows is distracting, // popups are obnoxious, iframes are sometimes blocked, and most servers don't implement CORS @@ -920,9 +966,9 @@ } // TODO params should have response_type indicating json, binary, etc - var directives = JSON.parse(OAUTH3._base64.decodeUrlSafe(params.result || params.directives)); + var result = JSON.parse(OAUTH3._base64.decodeUrlSafe(params.data || params.result || params.directives)); // caller will call OAUTH3.hooks.directives.set(providerUri, directives); - return directives; + return result; }); }); } diff --git a/well-known/oauth3/index.html b/well-known/oauth3/index.html index 9c415e5..a3d6c62 100644 --- a/well-known/oauth3/index.html +++ b/well-known/oauth3/index.html @@ -20,40 +20,20 @@ // TODO what about search within hash? var prefix = "(" + window.location.hostname + ") [.well-known/oauth3/]"; var params = OAUTH3.query.parse(window.location.hash || window.location.search); - if (params.debug) { - console.warn(prefix, "DEBUG MODE ENABLED. Automatic redirects disabled."); - } + var urlsafe64; + var redirect; + var err; + var oldRpc; + var sub = params.sub || params.subject; + var subData; - console.log(prefix, 'hash||search:'); - console.log(window.location.hash || window.location.search); + function doRedirect(redirect) { + if (params.debug) { + console.log(prefix, 'params.redirect_uri:', params.redirect_uri); + console.log(prefix, 'redirect'); + console.log(redirect); + } - console.log(prefix, 'params:'); - console.log(params); - - OAUTH3.request({ url: 'directives.json' }).then(function (resp) { - var urlsafe64 = OAUTH3._base64.encodeUrlSafe(JSON.stringify(resp.data, null, 0)); - var redirect; - - console.log(prefix, 'directives'); - console.log(resp); - - console.log(prefix, 'base64'); - console.log(urlsafe64); - - // TODO try postMessage back to redirect_uri domain right here - // window.postMessage(); - - // TODO make sure it's https NOT http - // NOTE: this can be only up to 2,083 characters - console.log(prefix, 'params.redirect_uri:', params.redirect_uri); - redirect = params.redirect_uri + '?' + OAUTH3.query.stringify({ - state: params.state - , directives: urlsafe64 - , debug: params.debug || undefined - }) - - console.log(prefix, 'redirect'); - console.log(redirect); if (!params.debug) { window.location = redirect; } else { @@ -63,6 +43,93 @@ + ' to let you look at logs or whatever it is that you intended to do.' + '

Continue with redirect: ' + redirect + ''; } + } + + function onError(err) { + var redirect = params.redirect_uri + '?' + OAUTH3.query.stringify({ + state: params.state + , error: err.code + , error_description: err.message + , error_uri: err.uri + , debug: params.debug || undefined + }); + + doRedirect(redirect); + } + + function onSuccess(urlsafe64, hasSub) { + if (params.debug) { + console.log(prefix, 'directives'); + console.log(resp); + + console.log(prefix, 'base64'); + console.log(urlsafe64); + } + + // TODO try postMessage back to redirect_uri domain right here + // window.postMessage(); + + // TODO SECURITY make sure it's https NOT http + // NOTE: this can be only up to 2,083 characters + redirect = params.redirect_uri + '?' + OAUTH3.query.stringify({ + state: params.state + , directives: oldRpc ? urlsafe64 : undefined + , data: !oldRpc ? urlsafe64 : undefined + , sub: hasSub && sub || undefined + , debug: params.debug || undefined + }); + + doRedirect(redirect); + } + + if (params.debug) { + console.warn(prefix, "DEBUG MODE ENABLED. Automatic redirects disabled."); + + console.log(prefix, 'hash||search:'); + console.log(window.location.hash || window.location.search); + + console.log(prefix, 'params:'); + console.log(params); + } + + if ('rpc' !== params.response_type) { + err = new Error("response_type '" + params.response_type + "' is not supported"); + err.code = "E_RESPONSE_TYPE"; + // TODO err.uri + onError(err); + return; + } + + if (params.action) { + oldRpc = true; + } + + if (/localstorage/i.test(params._scheme)) { + if (sub) { + subData = localStorage.getItem(sub + '@oauth3.org:issuer'); + onSuccess(subData || localStorage.getItem('oauth3.org:issuer'), subData && true); + return; + } + onSuccess(localStorage.getItem('oauth3.org:issuer')); + return; + } + + var fileWhiteList = [ + '.well-known/oauth3/directives.json' + , '.well-known/oauth3/scopes.json' + ]; + + if (-1 === fileWhiteList.indexOf(params._pathname)) { + err = new Error("No access to requested file: " + params._pathname); + err.code = "E_ACCESS_DENIED" + // TODO err.uri + onError(err); + } + + OAUTH3.request({ url: 'directives.json' }).then(function (resp) { + urlsafe64 = OAUTH3._base64.encodeUrlSafe(JSON.stringify(resp.data, null, 0)); + + onSuccess(urlsafe64); }); }()); diff --git a/well-known/oauth3/scopes.json b/well-known/oauth3/scopes.json new file mode 100644 index 0000000..268acf7 --- /dev/null +++ b/well-known/oauth3/scopes.json @@ -0,0 +1,26 @@ +{ + + "oauth3_authn": "Basic secure authentication" + , "auth@oauth3.org": "Basic secure authentication" + , "wallet": "Access to payments and subscriptions" + , "bucket": "Access to file storage" + , "db": "Access to app data" + , "domains": "Domain registration (and Glue and NS records)" + , "domains@oauth3.org": "Domain registration (and Glue and NS records)" + , "domains:glue": "Glue Record management (for vanity nameservers)" + , "domains:ns": "Name Server management" + , "dns": "DNS records (A/AAAA, TXT, SRV, MX, etc)" + + , "hello@example.com": "Hello World Example Access" + , "authn@oauth3.org": "Basic secure authentication" + , "wallet@oauth3.org": "Access to payments and subscriptions" + , "bucket@oauth3.org": "Access to file storage" + , "db@oauth3.org": "Access to app data" + , "domains@oauth3.org": "Domain registration (and Glue and NS records)" + , "domains:glue@oauth3.org": "Glue Record management (for vanity nameservers)" + , "domains:ns@oauth3.org": "Name Server management" + , "dns@oauth3.org": "DNS records (A/AAAA, TXT, SRV, MX, etc)" + , "www@daplie.com": "Websites and webapps" + + , "*": "FULL ACCOUNT ACCESS" + }