Compare commits

..

No commits in common. "master" and "scope-discovery" have entirely different histories.

14 changed files with 265 additions and 507 deletions

View File

@ -1 +1 @@
_apis well-known

View File

@ -5,7 +5,7 @@ oauth3.js
| [issuer.html](https://git.oauth3.org/OAuth3/issuer.html) | [issuer.html](https://git.oauth3.org/OAuth3/issuer.html)
| [issuer.rest.walnut.js](https://git.oauth3.org/OAuth3/issuer.rest.walnut.js) | [issuer.rest.walnut.js](https://git.oauth3.org/OAuth3/issuer.rest.walnut.js)
| [issuer.srv](https://git.oauth3.org/OAuth3/issuer.srv) | [issuer.srv](https://git.oauth3.org/OAuth3/issuer.srv)
| Sponsored by [ppl](https://ppl.family) | Sponsored by [Daplie](https://daplie.com)
The world's smallest, fastest, and most secure OAuth3 (and OAuth2) JavaScript implementation The world's smallest, fastest, and most secure OAuth3 (and OAuth2) JavaScript implementation
(Yes! works in browsers and node.js with no extra dependencies or bloat and no hacks!) (Yes! works in browsers and node.js with no extra dependencies or bloat and no hacks!)
@ -29,7 +29,8 @@ If you have no idea what you're doing
4. Download [oauth3.js-v1.zip](https://git.oauth3.org/OAuth3/oauth3.js/repository/archive.zip?ref=v1) 4. Download [oauth3.js-v1.zip](https://git.oauth3.org/OAuth3/oauth3.js/repository/archive.zip?ref=v1)
5. Double-click to unzip the folder. 5. Double-click to unzip the folder.
6. Copy the file `oauth3.core.js` into the folder `example.com/assets/oauth3.org/` 6. Copy the file `oauth3.core.js` into the folder `example.com/assets/oauth3.org/`
7. Copy the folder `_apis` into the folder `example.com/` 7. Copy the folder `well-known` into the folder `example.com/`
8. Rename the folder `well-known` to `.well-known` (when you do this, it become invisible, that's okay)
9. Add `<script src="assets/oauth3.org/oauth3.core.js"></script>` to your `index.html` 9. Add `<script src="assets/oauth3.org/oauth3.core.js"></script>` to your `index.html`
9. Add `<script src="app.js"></script>` to your `index.html` 9. Add `<script src="app.js"></script>` to your `index.html`
10. Create files in `example.com` called `app.js` and `index.html` and put this in it: 10. Create files in `example.com` called `app.js` and `index.html` and put this in it:
@ -58,13 +59,13 @@ If you have no idea what you're doing
`app.js`: `app.js`:
```js ```js
var OAUTH3 = window.OAUTH3; var OAUTH3 = window.OAUTH3;
var oauth3 = OAUTH3.create(window.location); // use window.location to set Client URI (your app's id) var auth = OAUTH3.create(window.location); // use window.location to set Client URI (your app's id)
// this is any OAuth3-compatible provider, such as oauth3.org // this is any OAuth3-compatible provider, such as oauth3.org
// in v1.1.0 we'll add backwards compatibility for facebook.com, google.com, etc // in v1.1.0 we'll add backwards compatibility for facebook.com, google.com, etc
// //
function onChangeProvider(providerUri) { function onChangeProvider(_providerUri) {
// example https://oauth3.org // example https://oauth3.org
return oauth3.setIdentityProvider(providerUri); return oauth3.setIdentityProvider(providerUri);
} }
@ -86,13 +87,11 @@ function onClickLogin() {
console.info('Secure PPID (aka subject):', session.token.sub); console.info('Secure PPID (aka subject):', session.token.sub);
return oauth3.request({ return oauth3.request({
url: 'https://api.oauth3.org/api/issuer@oauth3.org/jwks/:sub/:kid' url: 'https://oauth3.org/api/issuer@oauth3.org/inspect'
.replace(/:sub/g, session.token.sub)
.replace(/:kid/g, session.token.kid || session.token.iss)
, session: session , session: session
}).then(function (resp) { }).then(function (resp) {
console.info("Signing Public Key JWK:"); console.info("Inspect Token:");
console.log(resp.data); console.log(resp.data);
}); });
@ -145,13 +144,13 @@ it might look like this:
example.com example.com
├── _apis ├── .well-known (hidden)
│   └── oauth3.org │   └── oauth3
│   ├── callback.html │   ├── callback.html
│   ├── directives.json │   ├── directives.json
│   └── index.html │   └── index.html
├── assets ├── assets
│   └── oauth3.org │   └── org.oauth3
│   └── oauth3.core.js │   └── oauth3.core.js
@ -172,17 +171,17 @@ Installation (if you know what you're doing)
pushd /path/to/your/web/app pushd /path/to/your/web/app
# clone the project as assets/oauth3.org # clone the project as assets/org.oauth3
mkdir -p assets mkdir -p assets
git clone git@git.oauth3.org:OAuth3/oauth3.js.git assets/oauth3.org git clone git@git.daplie.com:OAuth3/oauth3.js.git assets/org.oauth3
pushd assets/oauth3.org pushd assets/org.oauth3
git checkout v1 git checkout v1
popd popd
# symlink `_apis/oauth3.org` to `assets/oauth3.org/_apis/oauth3.org` # symlink `.well-known/oauth3` to `assets/org.oauth3/.well-known/oauth3`
mkdir -p _apis mkdir -p .well-known
ln -sf ../assets/oauth3.org/_apis/oauth3 _apis/oauth3.org ln -sf ../assets/org.oauth3/.well-known/oauth3 .well-known/oauth3
``` ```
**Advanced Installation with `bower`** **Advanced Installation with `bower`**
@ -192,17 +191,17 @@ ln -sf ../assets/oauth3.org/_apis/oauth3 _apis/oauth3.org
bower install oauth3 bower install oauth3
# create a `_apis` folder and an `assets` folder # create a `.well-known` folder and an `assets` folder
mkdir -p _apis assets mkdir -p .well-known assets
# symlink `_apis/oauth3.org` to `bower_components/oauth3.org/_apis/oauth3.org` # symlink `.well-known/oauth3` to `bower_components/oauth3/.well-known/oauth3`
ln -sf ../bower_components/oauth3.org/_apis/oauth3.org _apis/oauth3.org ln -sf ../bower_components/oauth3/.well-known/oauth3 .well-known/oauth3
# symlink `assets/oauth3.org` to `bower_components/oauth3.org` # symlink `assets/org.oauth3` to `bower_components/oauth3`
ln -sf ../bower_components/oauth3.org/_apis/oauth3.org _apis/oauth3.org ln -sf ../bower_components/oauth3/.well-known/oauth3 .well-known/oauth3
ln -sf ../bower_components/oauth3.org assets/oauth3.org ln -sf ../bower_components/oauth3 assets/org.oauth3
``` ```
Usage Usage
@ -211,7 +210,7 @@ Usage
Update your HTML to include the the following script tag: Update your HTML to include the the following script tag:
```html ```html
<script src="assets/oauth3.org/oauth3.core.js"></script> <script src="assets/org.oauth3/oauth3.core.js"></script>
``` ```
You can create a very simple demo application like this: You can create a very simple demo application like this:
@ -290,7 +289,7 @@ You're all set. Nothing else is needed.
We've created an `Oauth3` service just for you: We've created an `Oauth3` service just for you:
```html ```html
<script src="assets/oauth3.org/oauth3.ng.js"></script> <script src="assets/org.oauth3/oauth3.ng.js"></script>
``` ```
```js ```js
@ -323,7 +322,7 @@ promise = oauth3.init(opts); // set and fetch your own si
// promises your site's config // opts = { location, session, issuer, audience } // promises your site's config // opts = { location, session, issuer, audience }
promise = oauth3.setIdentityProvider(url); // changes the Identity Provider URI (the site you're logging into), promise = oauth3.setIdentityProvider(url); // changes the Identity Provider URI (the site you're logging into),
// promises the provider's config // gets the config for that site (from their _apis/oauth3.org), // promises the provider's config // gets the config for that site (from their .well-known/oauth3),
// and caches it in internal state as the default // and caches it in internal state as the default
promise = oauth3.setResourceProvider(url); // changes the Resource Provider URI (the site you're getting stuff from) promise = oauth3.setResourceProvider(url); // changes the Resource Provider URI (the site you're getting stuff from)
@ -340,11 +339,12 @@ promise = oauth3.request({ url, method, data }); // make an (authorized) arbi
// (contacts, photos, whatever) // (contacts, photos, whatever)
promise = oauth3.api(apiname, opts); // make an (authorized) well-known api call to an audience promise = oauth3.api(apiname, opts); // make an (authorized) well-known api call to an audience
// Ex: oauth3.api('dns.list', { sld: 'example', tld: 'com' }); // See https://labs.daplie.com/docs/ for API schemas
// Ex: oauth3.api('dns.list', { sld: 'daplie', tld: 'com' });
// TODO // TODO
api = await oauth3.package(audience, schemaname); // make an (authorized) well-known api call to an audience api = await oauth3.package(audience, schemaname); // make an (authorized) well-known api call to an audience
// Ex: api = await oauth3.package('domains.example.com', 'dns@oauth3.org'); // Ex: api = await oauth3.package('domains.daplie.com', 'dns@oauth3.org');
// api.list({ sld: 'mydomain', tld: 'com' }); // api.list({ sld: 'mydomain', tld: 'com' });
@ -353,10 +353,6 @@ promise = oauth3.logout(); // opens logout window for t
oauth3.session(); // returns the current session, if any oauth3.session(); // returns the current session, if any
``` ```
<!-- TODO
Track down the old https://labs.daplie.com/docs/ for API schemas
--
Real API Real API
---------- ----------
@ -498,5 +494,5 @@ can be very ugly and confusing and we definitely need to allow relative paths.
A potential work-around would be to assume all paths are relative (eliminate #4 instead) A potential work-around would be to assume all paths are relative (eliminate #4 instead)
and have the path always key off of the base URL - if oauth3 directives are to be found at and have the path always key off of the base URL - if oauth3 directives are to be found at
https://example.com/username/_apis/oauth3.org/index.json then /api/whatever would refer https://example.com/username/.well-known/oauth3/directives.json then /api/whatever would refer
to https://example.com/username/api/whatever. to https://example.com/username/api/whatever.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 B

View File

@ -1,140 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<style>
body {
background-color: #ffcccc;
}
</style>
</head>
<body>
OAuth3 RPC
<script src="../../assets/oauth3.org/oauth3.core.js"></script>
<script>
;(function () {
'use strict';
// Taken from oauth3.core.js
// 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);
var urlsafe64;
var redirect;
var err;
var oldRpc;
var sub = params.sub || params.subject;
var subData;
function doRedirect(redirect) {
if (params.debug) {
console.log(prefix, 'params.redirect_uri:', params.redirect_uri);
console.log(prefix, 'redirect');
console.log(redirect);
}
if (!params.debug) {
window.location = redirect;
} else {
// yes, we're violating the security lint with purpose
document.body.innerHTML += window.location.host + window.location.pathname
+ '<br/><br/>You\'ve passed the \'debug\' parameter so we\'re pausing'
+ ' to let you look at logs or whatever it is that you intended to do.'
+ '<br/><br/>Continue with redirect: <a href="' + redirect + '">' + redirect + '</' + 'a>';
}
}
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;
}
var loco = window.location.href.replace(/\/\.well-known.*/, '');
//var loco = 'sso.hellabit.com';
var resp;
if (/localstorage/i.test(params._scheme)) {
if (sub) {
subData = localStorage.getItem(sub + '@oauth3.org:issuer');
}
resp = subData || localStorage.getItem('oauth3.org:issuer') || loco;
onSuccess(resp, subData && true);
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: params._pathname.replace(/^\.well-known\/oauth3\//, '') }).then(function (resp) {
urlsafe64 = OAUTH3._base64.encodeUrlSafe(JSON.stringify(resp.data, null, 0));
onSuccess(urlsafe64);
});
}());
</script>
</body>
</html>

View File

@ -1,96 +0,0 @@
(function () {
'use strict';
function create(myOpts) {
return {
requestScope: function (opts) {
// TODO pre-generate URL
// deliver existing session if it exists
var scope = opts && opts.scope || [];
if (myOpts.session) {
if (!scope.length || scope.every(function (scp) {
return -1 !== opts.myOpts.session.scope.indexOf(scp);
})) {
return OAUTH3.PromiseA.resolve(myOpts.session);
}
}
// request a new session otherwise
return OAUTH3.implicitGrant(myOpts.directives, {
client_id: myOpts.conf.client_uri
, client_uri: myOpts.conf.client_uri
// maybe use inline instead?
, windowType: 'popup'
, scope: scope
}).then(function (session) {
return session;
});
}
, session: function () {
return myOpts.session;
}
, refresh: function (session) {
return OAUTH3.implicitGrant(myOpts.directives, {
client_id: myOpts.conf.client_uri
, client_uri: myOpts.conf.client_uri
, windowType: 'background'
}).then(function (_session) {
session = _session;
return session;
});
}
, logout: function () {
return OAUTH3.logout(myOpts.directives, {
client_id: myOpts.conf.client_uri
, client_uri: myOpts.conf.client_uri
});
}
, switchUser: function () {
// should open dialog with user selection dialog
}
}
}
window.navigator.auth = {
getUserAuthenticator: function (opts) {
var conf = {};
var directives;
var session;
opts = opts || {};
conf.client_uri = opts.client_uri || OAUTH3.clientUri(opts.location || window.location);
return OAUTH3.issuer({ broker: opts.issuer_uri || 'https://new.oauth3.org' }).then(function (issuer) {
conf.issuer_uri = issuer;
conf.provider_uri = issuer;
return OAUTH3.directives(conf.provider_uri, {
client_id: conf.client_uri
, client_uri: conf.client_uri
}).then(function (_directives) {
directives = _directives;
var myOpts = {
directives: directives
, conf: conf
};
return OAUTH3.implicitGrant(directives, {
client_id: conf.client_uri
, client_uri: conf.client_uri
, windowType: 'background'
}).then(function (_session) {
session = _session;
myOpts.session = session;
return create(myOpts);
}, function (err) {
console.error('[DEBUG] implicitGrant err:');
console.error(err);
return create(myOpts);
});
});
});
}
};
}());

View File

@ -169,7 +169,7 @@
} }
, scope: { , scope: {
parse: function (scope) { parse: function (scope) {
return (scope||'').toString().split(/[+, ]+/g); return (scope||'').split(/[+, ]+/g);
} }
, stringify: function (scope) { , stringify: function (scope) {
if (Array.isArray(scope)) { if (Array.isArray(scope)) {
@ -294,23 +294,25 @@
} }
} }
, urls: { , urls: {
rpc: function (providerUri, opts) { , discover: function (providerUri, opts) {
if (!providerUri) { if (!providerUri) {
throw new Error("cannot run rpc without providerUri"); throw new Error("cannot discover without providerUri");
} }
if (!opts.client_id) { if (!opts.client_id) {
throw new Error("cannot run rpc without options.client_id"); throw new Error("cannot discover without options.client_id");
} }
var clientId = OAUTH3.url.normalize(opts.client_id || opts.client_uri); var clientId = OAUTH3.url.normalize(opts.client_id || opts.client_uri);
providerUri = OAUTH3.url.normalize(providerUri); providerUri = OAUTH3.url.normalize(providerUri);
var discoverFile = opts.discoverFile || "directives.json";
var params = { var params = {
state: opts.state || OAUTH3.utils.randomState() action: 'directives' //TODO: change this to not be directive specific. Is it even used?
, state: opts.state || OAUTH3.utils.randomState()
, redirect_uri: clientId + (opts.client_callback_path || '/.well-known/oauth3/callback.html#/') , redirect_uri: clientId + (opts.client_callback_path || '/.well-known/oauth3/callback.html#/')
, response_type: 'rpc' , response_type: 'rpc'
, discoverFile: opts.discoveFile || "directives.json"
, _method: 'GET' , _method: 'GET'
, _scheme: opts._scheme , _pathname: '.well-known/oauth3/directives.json'
, _pathname: opts._pathname
, debug: opts.debug || undefined , debug: opts.debug || undefined
}; };
@ -323,18 +325,6 @@
return toRequest; return toRequest;
} }
, broker: function (providerUri, opts) {
opts._scheme = "localstorage:";
opts._pathname = "issuer";
return OAUTH3.urls.rpc(providerUri, opts);
}
, 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) { , implicitGrant: function (directive, opts) {
// //
// Example Implicit Grant Request // Example Implicit Grant Request
@ -679,26 +669,21 @@
} }
} }
, discoverScopes: function (providerUri, opts) { , discoverScopes: function (providerUri, opts) {
return OAUTH.scopes(providerUri, opts);
}
, scopes: function (providerUri, opts) {
if (!providerUri) { if (!providerUri) {
throw new Error('oauth3.discoverScopes(providerUri, opts) received providerUri as :', providerUri); throw new Error('oauth3.discoverScopes(providerUri, opts) received providerUri as :', providerUri);
} }
opts = opts || {}; var opts = opts || {};
opts._pathname = ".well-known/oauth3/scopes.json"; opts.discoverFile = "scopes.json";
//TODO: add caching //TODO: add caching
return OAUTH3._rpcHelper(providerUri, opts).then(function(scopes) { return OAUTH3._discoverHelper(providerUri, opts).then(function(scopes) {
return scopes; return scopes;
}); });
} }
, discover: function (providerUri, opts) { , discover: function (providerUri, opts) {
return OAUTH3.directives(providerUri, opts);
}
, directives: function (providerUri, opts) {
if (!providerUri) { if (!providerUri) {
throw new Error('oauth3.discover(providerUri, opts) received providerUri as :', providerUri); throw new Error('oauth3.discover(providerUri, opts) received providerUri as :', providerUri);
} }
@ -708,8 +693,7 @@
return directives; return directives;
} }
opts._pathname = ".well-known/oauth3/directives.json"; return OAUTH3._discoverHelper(providerUri, opts).then(function (directives) {
return OAUTH3._rpcHelper(providerUri, opts).then(function (directives) {
directives.azp = directives.azp || OAUTH3.url.normalize(providerUri); directives.azp = directives.azp || OAUTH3.url.normalize(providerUri);
directives.issuer = directives.issuer || 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))); directives.api = OAUTH3.url.normalize((directives.api||':hostname').replace(/:hostname/, OAUTH3.uri.normalize(directives.issuer) || OAUTH3.uri.normalize(providerUri)));
@ -718,8 +702,9 @@
}); });
}); });
} }
, _rpcHelper: function(providerUri, opts) { , _discoverHelper: function(providerUri, opts) {
return OAUTH3._browser.rpc(providerUri, opts); opts.discoverFile = "directives.json";
return OAUTH3._browser.discover(providerUri, opts);
} }
, request: function (preq, opts) { , request: function (preq, opts) {
function fetch() { function fetch() {
@ -747,21 +732,6 @@
*/ */
return OAUTH3._browser.request(preq, opts); return OAUTH3._browser.request(preq, opts);
} }
, issuer: function (opts) {
if (!opts) { opts = {}; }
// TODO this will default to browserlogin.org
var broker = opts.broker || 'https://new.oauth3.org';
//var broker = opts.broker || 'https://broker.oauth3.org';
opts._rpc = "broker";
opts._scheme = "localstorage:";
opts._pathname = "issuer";
return OAUTH3._rpcHelper(broker, opts).then(function(issuer) {
return issuer;
});
}
, implicitGrant: function (directives, opts) { , implicitGrant: function (directives, opts) {
var promise; var promise;
var providerUri = directives.azp || directives.issuer || directives; var providerUri = directives.azp || directives.issuer || directives;
@ -872,19 +842,12 @@
}); });
}); });
} }
, logout: function(issuerUri, opts) { , logout: function(providerUri, opts) {
var directives; return OAUTH3.hooks.directives.get(providerUri).then(function (directives) {
if ('string' !== typeof issuerUri) {
directives = issuerUri;
return OAUTH3._logoutHelper(directives, opts);
}
return OAUTH3.hooks.directives.get(issuerUri).then(function (directives) {
return OAUTH3._logoutHelper(directives, opts); return OAUTH3._logoutHelper(directives, opts);
}); });
} }
, _logoutHelper: function(directives, opts) { , _logoutHelper: function(providerUri, directives, opts) {
var issuerUri = directives.issuer_uri || directives.provider_uri;
var logoutReq = OAUTH3.urls.logout( var logoutReq = OAUTH3.urls.logout(
directives directives
, { client_id: (opts.client_id || opts.client_uri || OAUTH3.clientUri(OAUTH3._browser.window.location)) , { client_id: (opts.client_id || opts.client_uri || OAUTH3.clientUri(OAUTH3._browser.window.location))
@ -907,10 +870,10 @@
if (params.error) { if (params.error) {
// TODO directives.audience // TODO directives.audience
return OAUTH3.PromiseA.reject(OAUTH3.error.parse(directives.issuer /*issuerUri*/, params)); return OAUTH3.PromiseA.reject(OAUTH3.error.parse(directives.issuer /*providerUri*/, params));
} }
OAUTH3.hooks.session.clear(issuerUri); OAUTH3.hooks.session.clear(providerUri);
return params; return params;
}); });
} }
@ -921,50 +884,48 @@
// //
, _browser: { , _browser: {
window: 'undefined' !== typeof window ? window : null window: 'undefined' !== typeof window ? window : null
, rpc: function(providerUri, opts) { // TODO we don't need to include this if we're using jQuery or angular
, discover: function(providerUri, opts) {
opts = opts || {}; opts = opts || {};
providerUri = OAUTH3.url.normalize(providerUri); providerUri = OAUTH3.url.normalize(providerUri);
// TODO SECURITY should we whitelist our own self? // If no discoverFile was specified, who knows what they want, but
// this function used to only support directives.json, so it's worth
// a shot.
var discoverFile = opts.discoverFile || "directives.json";
if (OAUTH3.uri.normalize(providerUri).replace(/\/.*/, '') === OAUTH3.uri.normalize(OAUTH3._browser.window.location.hostname)) { if (OAUTH3.uri.normalize(providerUri).replace(/\/.*/, '') === OAUTH3.uri.normalize(OAUTH3._browser.window.location.hostname)) {
console.warn("It looks like you're a provider trying to run rpc on yourself," console.warn("It looks like you're a provider trying to discover on yourself,"
+ " so we we're just gonna use" + " so we we're just gonna use"
+ " OAUTH3.request({ method: 'GET', url: " + " OAUTH3.request({ method: 'GET', url: "
+ "'" + opts._pathname + "' })"); + "'/.well-known/oauth3/" + discoverFile + "' })");
if (/localstorage/i.test(opts._scheme)) {
return OAUTH3.PromiseA.resolve(localStorage.getItem(opts._pathname));
}
else {
return OAUTH3.request({ return OAUTH3.request({
method: 'GET' method: 'GET'
, url: OAUTH3.url.normalize(providerUri) + '/' + opts._pathname // '/.well-known/oauth3/' + discoverFile , url: OAUTH3.url.normalize(providerUri) + '/.well-known/oauth3/' + discoverFile
}).then(function (resp) { }).then(function (resp) {
return resp.data; return resp.data;
}); });
} }
}
if (!(opts.client_id || opts.client_uri || '').match(OAUTH3._browser.window.location.hostname)) { if (!(opts.client_id || opts.client_uri).match(OAUTH3._browser.window.location.hostname)) {
console.warn("It looks like your client_id doesn't match your current window..." console.warn("It looks like your client_id doesn't match your current window..."
+ " this probably won't end well"); + " this probably won't end well");
console.warn(opts.client_id || opts.client_uri, OAUTH3._browser.window.location.hostname); console.warn(opts.client_id || opts.client_uri, OAUTH3._browser.window.location.hostname);
} }
var discReq = OAUTH3.urls[opts._rpc || 'rpc']( var discReq = OAUTH3.urls.discover(
providerUri providerUri
, { client_id: (opts.client_id || opts.client_uri || OAUTH3.clientUri(OAUTH3._browser.window.location)) , { client_id: (opts.client_id || opts.client_uri || OAUTH3.clientUri(OAUTH3._browser.window.location))
, windowType: opts.broker && opts.windowType || 'background' , windowType: opts.broker && opts.windowType || 'background'
, broker: opts.broker , broker: opts.broker
, state: opts._state || undefined , state: opts._state || undefined
, debug: opts.debug , debug: opts.debug
, _scheme: opts._scheme , discoverFile: opts.discoverFile
, _pathname: opts._pathname
, _method: opts._method
} }
); );
opts._state = discReq.state; opts._state = discReq.state;
//var discReq = OAUTH3.urls.rpc(providerUri, opts); //var discReq = OAUTH3.urls.discover(providerUri, opts);
// hmm... we're gonna need a broker for this since switching windows is distracting, // 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 // popups are obnoxious, iframes are sometimes blocked, and most servers don't implement CORS
@ -974,7 +935,6 @@
// TODO allow node to open a desktop browser window // TODO allow node to open a desktop browser window
opts._windowType = opts.windowType; opts._windowType = opts.windowType;
opts.windowType = opts.windowType || 'background'; opts.windowType = opts.windowType || 'background';
return OAUTH3._browser.testPixel(providerUri).then(function () {
return OAUTH3._browser.frameRequest( return OAUTH3._browser.frameRequest(
OAUTH3.url.resolve(providerUri, discReq.url) OAUTH3.url.resolve(providerUri, discReq.url)
, discReq.state , discReq.state
@ -993,18 +953,10 @@
} }
// TODO params should have response_type indicating json, binary, etc // TODO params should have response_type indicating json, binary, etc
var result; var result = JSON.parse(OAUTH3._base64.decodeUrlSafe(params.result || params.directives));
try {
result = JSON.parse(OAUTH3._base64.decodeUrlSafe(params.data || params.result || params.directives));
} catch(e) {
result = params.data || params.result;
}
console.log('result:', result);
// caller will call OAUTH3.hooks.directives.set(providerUri, directives); // caller will call OAUTH3.hooks.directives.set(providerUri, directives);
return result; return result;
}); });
});
} }
, request: function (preq, _sys) { , request: function (preq, _sys) {
return new OAUTH3.PromiseA(function (resolve, reject) { return new OAUTH3.PromiseA(function (resolve, reject) {
@ -1102,28 +1054,6 @@
} }
}); });
} }
, testPixel: function (targetUri) {
var url = OAUTH3.url.resolve(OAUTH3.url.normalize(targetUri), '.well-known/oauth3/clear.gif');
return new OAUTH3.PromiseA(function (resolve, reject) {
var img = document.createElement('img');
img.addEventListener('load', function () {
resolve();
});
img.addEventListener('error', function () {
var err = new Error("OAuth3 support not detected: '" + url + "' not found");
err.code = 'E_NOT_SUPPORTED';
reject(err);
});
// works with CSP
img.style.position = 'absolute';
img.style.left = '-2px';
img.style.bottom = '-2px';
img.className = 'js-oauth3-discover';
img.src = url;
document.body.appendChild(img);
console.log('img', img);
});
}
, frameRequest: function (url, state, opts) { , frameRequest: function (url, state, opts) {
opts = opts || {}; opts = opts || {};
var previousFrame = OAUTH3._browser._frames[state]; var previousFrame = OAUTH3._browser._frames[state];
@ -1134,10 +1064,11 @@
} }
var timeout = opts.timeout; var timeout = opts.timeout;
if ('background' === windowType) { if (opts.debug) {
if (!timeout) { timeout = timeout || 3 * 60 * 1000;
timeout = 7 * 1000;
} }
else {
timeout = timeout || ('background' === windowType ? 15 * 1000 : 3 * 60 * 1000);
} }
return new OAUTH3.PromiseA(function (resolve, reject) { return new OAUTH3.PromiseA(function (resolve, reject) {
@ -1159,7 +1090,6 @@
cleanup(); cleanup();
}; };
if (timeout) {
tok = setTimeout(function () { tok = setTimeout(function () {
var err = new Error( var err = new Error(
"the '" + windowType + "' request did not complete within " + Math.round(timeout / 1000) + "s" "the '" + windowType + "' request did not complete within " + Math.round(timeout / 1000) + "s"
@ -1168,7 +1098,6 @@
reject(err); reject(err);
cleanup(); cleanup();
}, timeout); }, timeout);
}
setTimeout(function () { setTimeout(function () {
if (!OAUTH3._browser._frames[state]) { if (!OAUTH3._browser._frames[state]) {
@ -1371,23 +1300,6 @@
OAUTH3.utils = { OAUTH3.utils = {
clientUri: OAUTH3.clientUri clientUri: OAUTH3.clientUri
, query: OAUTH3.query , query: OAUTH3.query
, parseSubject: function (sub) {
var parts = sub.split('@');
var issuer;
var subject;
if (/@/.test(sub)) {
// The username may have a single @, the provider may not
// user@thing.com@whatever.com -> user@thing.com, whatever.com
issuer = parts.pop();
subject = parts.join('@');
} else {
//subject = '';
issuer = parts.join('@');
}
return { subject: subject, issuer: issuer };
}
, scope: OAUTH3.scope , scope: OAUTH3.scope
, uri: OAUTH3.uri , uri: OAUTH3.uri
, url: OAUTH3.url , url: OAUTH3.url

View File

@ -218,7 +218,6 @@ OAUTH3.urls.grants = function (directive, opts) {
, session: opts.session , session: opts.session
}; };
}; };
//OAUTH3.urls.accessToken = function (directive, opts)
OAUTH3.urls.clientToken = function (directive, opts) { OAUTH3.urls.clientToken = function (directive, opts) {
var tokenDir = directive.access_token; var tokenDir = directive.access_token;
if (!tokenDir) { if (!tokenDir) {
@ -295,7 +294,7 @@ OAUTH3.urls.credentialMeta = function (directive, opts) {
.replace(':id', opts.email) .replace(':id', opts.email)
}; };
OAUTH3.authn = OAUTH3.authn || {}; OAUTH3.authn = {};
OAUTH3.authn.loginMeta = function (directive, opts) { OAUTH3.authn.loginMeta = function (directive, opts) {
var url = OAUTH3.urls.credentialMeta(directive, opts); var url = OAUTH3.urls.credentialMeta(directive, opts);
return OAUTH3.request({ return OAUTH3.request({
@ -371,8 +370,8 @@ OAUTH3.authn.resourceOwnerPassword = function (directive, opts) {
OAUTH3.authz = {}; OAUTH3.authz = {};
OAUTH3.authz.scopes = function (providerUri, session, clientParams) { OAUTH3.authz.scopes = function (providerUri, session, clientParams) {
var clientUri = OAUTH3.uri.normalize(clientParams.client_uri || OAUTH3._browser.window.document.referrer); var clientUri = OAUTH3.uri.normalize(clientParams.client_uri || OAUTH3._browser.window.document.referrer);
var scope = clientParams.scope || 'authn@oauth3.org'; var scope = clientParams.scope || 'oauth3_authn';
if ('authn@oauth3.org' === scope.toString()) { if ('oauth3_authn' === scope) {
// implicit ppid grant is automatic // implicit ppid grant is automatic
console.warn('[security] fix scope checking on backend so that we can do automatic grants'); console.warn('[security] fix scope checking on backend so that we can do automatic grants');
// TODO check user preference if implicit ppid grant is allowed // TODO check user preference if implicit ppid grant is allowed
@ -574,85 +573,68 @@ OAUTH3.authz.redirectWithToken = function (providerUri, session, clientParams, s
}; };
OAUTH3.requests = {}; OAUTH3.requests = {};
//OAUTH3.accounts = {};
OAUTH3.requests.accounts = {}; OAUTH3.requests.accounts = {};
OAUTH3.urls.accounts = {}; OAUTH3.requests.accounts.update = function (directive, session, opts) {
OAUTH3.urls.accounts._ = function (directives, directive, session, opts) { var dir = directive.update_account || {
opts = opts || {}; method: 'POST'
var dir = directive || { , url: OAUTH3.url.normalize(directive.api) + '/api/issuer@oauth3.org/accounts/:accountId'
//url: OAUTH3.url.normalize(directives.api) + '/api/issuer@oauth3.org/accounts/:accountId'
url: OAUTH3.url.normalize(directives.api) + '/api/issuer@oauth3.org/acl/profiles/:accountId'
//, method: 'GET'
, bearer: 'Bearer' , bearer: 'Bearer'
}; };
var url = dir.url var url = dir.url
.replace(/:accountId/, opts.accountId || '') .replace(/:accountId/, opts.accountId)
.replace(/\/$/, '')
; ;
return { return OAUTH3.request({
url: url method: dir.method || 'POST'
//, method: dir.method || 'POST' , url: url
, session: session
/*
, headers: { , headers: {
'Authorization': (dir.bearer || 'Bearer') + ' ' + (session.access_token || session.accessToken) 'Authorization': (dir.bearer || 'Bearer') + ' ' + session.accessToken
} }
*/ , json: {
};
};
OAUTH3.urls.accounts.get = function (directives, session) {
var urlObj = OAUTH3.urls.accounts._(directives, directives.account, session);
urlObj.method = (directives.account || { method: 'GET' }).method;
return urlObj;
};
OAUTH3.urls.accounts.update = function (directives, session, opts) {
var urlObj = OAUTH3.urls.accounts._(directives, directives.update_account, session, opts);
urlObj.method = (directives.update_account || { method: 'POST' }).method;
urlObj.json = {
name: opts.name name: opts.name
, comment: opts.comment , comment: opts.comment
, displayName: opts.displayName , displayName: opts.displayName
, priority: opts.priority , priority: opts.priority
}; }
return urlObj; });
}; };
OAUTH3.urls.accounts.create = function (directives, session, account) { OAUTH3.requests.accounts.create = function (directive, session, account) {
var urlObj = OAUTH3.urls.accounts._(directives, directives.create_account, session); var dir = directive.create_account || {
var profile = { method: 'POST'
, url: OAUTH3.url.normalize(directive.api) + '/api/issuer@oauth3.org/accounts'
, bearer: 'Bearer'
};
var data = {
// TODO fix the server to just use one scheme
// account = { nick, self: { comment, username } }
// account = { name, comment, display_name, priority }
account: {
nick: account.display_name nick: account.display_name
// "name" is unique and what would be reserved in a url {{name}}.issuer.org or issuer.org/users/{{name}}
, name: account.name , name: account.name
, comment: account.comment , comment: account.comment
, display_name: account.display_name , display_name: account.display_name
, priority: account.priority , priority: account.priority
, self: {
nick: account.display_name
, name: account.name
, comment: account.comment
, display_name: account.display_name
, priority: account.priority
}
}
, logins: [
{
token: session.access_token
}
]
}; };
var credentials = [ { token: session.access_token } ];
urlObj.method = (directives.create_account || { method: 'POST' }).method; return OAUTH3.request({
urlObj.json = { method: dir.method || 'POST'
// TODO fix the server to just use one scheme , url: dir.url
// account = { nick, self: { comment, username } } , session: session
// account = { name, comment, display_name, priority } , data: data
credentials: credentials });
, profile: profile
// 'account' is deprecated in favor of 'profile'
, account: profile
// 'logins' is deprecated in favor of 'credentials'
, logins: credentials
};
return urlObj;
};
OAUTH3.requests.accounts.get = function (directives, session) {
var urlObj = OAUTH3.urls.accounts.get(directives, session);
return OAUTH3.request(urlObj);
};
OAUTH3.requests.accounts.update = function (directives, session, opts) {
var urlObj = OAUTH3.urls.accounts.update(directives, session, opts);
return OAUTH3.request(urlObj);
};
OAUTH3.requests.accounts.create = function (directive, session, account) {
var urlObj = OAUTH3.urls.accounts.create(directives, session, account);
return OAUTH3.request(urlObj);
}; };
OAUTH3.hooks.grants = { OAUTH3.hooks.grants = {

View File

@ -27,9 +27,9 @@
OAUTH3.authz.scopes = function () { OAUTH3.authz.scopes = function () {
return OAUTH3.PromiseA.resolve({ return OAUTH3.PromiseA.resolve({
pending: [ 'authn@oauth3.org' ] // not yet accepted pending: ['oauth3_authn'] // not yet accepted
, granted: [] // all granted, ever , granted: [] // all granted, ever
, requested: [ 'authn@oauth3.org' ] // all requested, now , requested: ['oauth3_authn'] // all requested, now
, accepted: [] // granted (ever) and requested (now) , accepted: [] // granted (ever) and requested (now)
}); });
}; };

View File

@ -1 +0,0 @@
_apis

View File

@ -0,0 +1,105 @@
<!DOCTYPE html>
<html>
<head>
<style>
body {
background-color: #ffcccc;
}
</style>
</head>
<body>
OAuth3 RPC
<script src="../../assets/oauth3.org/oauth3.core.js"></script>
<script>
;(function () {
'use strict';
// Taken from oauth3.core.js
// 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.");
}
console.log(prefix, 'hash||search:');
console.log(window.location.hash || window.location.search);
console.log(prefix, 'params:');
console.log(params);
var fileWhiteList = [
"directives.json"
, "scopes.json" ];
//Serving arbitrary files/paths is probably not a good idea.
//Let's make sure this is something we want to serve.
if(fileWhiteList.indexOf(params.discoverFile) === -1) {
//Nope!
var redirect = params.redirect_uri + '?' + OAUTH3.query.stringify({
state: params.state
, error: "No access to requested file: " + params.discoverFile
, error_code: "E_ACCESS_DENIED"
, debug: params.debug || undefined
});
console.error(prefix, "Requested file is not listed as a discoverable file:"
, fileWhiteList);
console.log("Redirecting with error: ", redirect)
if (!params.debug) {
window.location = redirect;
} else {
// yes, we're violating the security lint with purpose
document.body.innerHTML += window.location.host + window.location.pathname
+ '<br/><br/>You\'ve passed the \'debug\' parameter so we\'re pausing'
+ ' to let you look at logs or whatever it is that you intended to do.'
+ '<br/><br/>The requested file was not a discoverable file (see console for details).'
+ '<br/><br/>Continue with error redirect: <a href="' + redirect + '">' + redirect + '</' + 'a>';
}
return;
}
OAUTH3.request({ url: params.discoverfile }).then(function (resp) {
var urlsafe64 = OAUTH3._base64.encodeUrlSafe(JSON.stringify(resp.data, null, 0));
var redirect;
var returnParams;
console.log(prefix, 'file contents');
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 //kept for now, probably should remove this.
, result: urlsafe64
, debug: params.debug || undefined
})
console.log(prefix, 'redirect');
console.log(redirect);
if (!params.debug) {
window.location = redirect;
} else {
// yes, we're violating the security lint with purpose
document.body.innerHTML += window.location.host + window.location.pathname
+ '<br/><br/>You\'ve passed the \'debug\' parameter so we\'re pausing'
+ ' to let you look at logs or whatever it is that you intended to do.'
+ '<br/><br/>Continue with redirect: <a href="' + redirect + '">' + redirect + '</' + 'a>';
}
});
}());
</script>
</body>
</html>