Compare commits

..

No commits in common. "master" and "rpc" have entirely different histories.
master ... rpc

13 changed files with 128 additions and 318 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.rest.walnut.js](https://git.oauth3.org/OAuth3/issuer.rest.walnut.js)
| [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
(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)
5. Double-click to unzip the folder.
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="app.js"></script>` to your `index.html`
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`:
```js
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
// 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
return oauth3.setIdentityProvider(providerUri);
}
@ -86,13 +87,11 @@ function onClickLogin() {
console.info('Secure PPID (aka subject):', session.token.sub);
return oauth3.request({
url: 'https://api.oauth3.org/api/issuer@oauth3.org/jwks/:sub/:kid'
.replace(/:sub/g, session.token.sub)
.replace(/:kid/g, session.token.kid || session.token.iss)
url: 'https://oauth3.org/api/issuer@oauth3.org/inspect'
, session: session
}).then(function (resp) {
console.info("Signing Public Key JWK:");
console.info("Inspect Token:");
console.log(resp.data);
});
@ -145,13 +144,13 @@ it might look like this:
example.com
├── _apis
│   └── oauth3.org
├── .well-known (hidden)
│   └── oauth3
│   ├── callback.html
│   ├── directives.json
│   └── index.html
├── assets
│   └── oauth3.org
│   └── org.oauth3
│   └── oauth3.core.js
@ -172,17 +171,17 @@ Installation (if you know what you're doing)
pushd /path/to/your/web/app
# clone the project as assets/oauth3.org
# clone the project as assets/org.oauth3
mkdir -p assets
git clone git@git.oauth3.org:OAuth3/oauth3.js.git assets/oauth3.org
pushd assets/oauth3.org
git clone git@git.daplie.com:OAuth3/oauth3.js.git assets/org.oauth3
pushd assets/org.oauth3
git checkout v1
popd
# symlink `_apis/oauth3.org` to `assets/oauth3.org/_apis/oauth3.org`
mkdir -p _apis
ln -sf ../assets/oauth3.org/_apis/oauth3 _apis/oauth3.org
# symlink `.well-known/oauth3` to `assets/org.oauth3/.well-known/oauth3`
mkdir -p .well-known
ln -sf ../assets/org.oauth3/.well-known/oauth3 .well-known/oauth3
```
**Advanced Installation with `bower`**
@ -192,17 +191,17 @@ ln -sf ../assets/oauth3.org/_apis/oauth3 _apis/oauth3.org
bower install oauth3
# create a `_apis` folder and an `assets` folder
mkdir -p _apis assets
# create a `.well-known` folder and an `assets` folder
mkdir -p .well-known assets
# symlink `_apis/oauth3.org` to `bower_components/oauth3.org/_apis/oauth3.org`
ln -sf ../bower_components/oauth3.org/_apis/oauth3.org _apis/oauth3.org
# symlink `.well-known/oauth3` to `bower_components/oauth3/.well-known/oauth3`
ln -sf ../bower_components/oauth3/.well-known/oauth3 .well-known/oauth3
# symlink `assets/oauth3.org` to `bower_components/oauth3.org`
ln -sf ../bower_components/oauth3.org/_apis/oauth3.org _apis/oauth3.org
ln -sf ../bower_components/oauth3.org assets/oauth3.org
# symlink `assets/org.oauth3` to `bower_components/oauth3`
ln -sf ../bower_components/oauth3/.well-known/oauth3 .well-known/oauth3
ln -sf ../bower_components/oauth3 assets/org.oauth3
```
Usage
@ -211,7 +210,7 @@ Usage
Update your HTML to include the the following script tag:
```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:
@ -290,7 +289,7 @@ You're all set. Nothing else is needed.
We've created an `Oauth3` service just for you:
```html
<script src="assets/oauth3.org/oauth3.ng.js"></script>
<script src="assets/org.oauth3/oauth3.ng.js"></script>
```
```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 }
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
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)
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
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' });
@ -353,10 +353,6 @@ promise = oauth3.logout(); // opens logout window for t
oauth3.session(); // returns the current session, if any
```
<!-- TODO
Track down the old https://labs.daplie.com/docs/ for API schemas
--
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)
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.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 B

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: {
parse: function (scope) {
return (scope||'').toString().split(/[+, ]+/g);
return (scope||'').split(/[+, ]+/g);
}
, stringify: function (scope) {
if (Array.isArray(scope)) {
@ -747,21 +747,6 @@
*/
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) {
var promise;
var providerUri = directives.azp || directives.issuer || directives;
@ -872,19 +857,12 @@
});
});
}
, logout: function(issuerUri, opts) {
var directives;
if ('string' !== typeof issuerUri) {
directives = issuerUri;
return OAUTH3._logoutHelper(directives, opts);
}
return OAUTH3.hooks.directives.get(issuerUri).then(function (directives) {
, logout: function(providerUri, opts) {
return OAUTH3.hooks.directives.get(providerUri).then(function (directives) {
return OAUTH3._logoutHelper(directives, opts);
});
}
, _logoutHelper: function(directives, opts) {
var issuerUri = directives.issuer_uri || directives.provider_uri;
, _logoutHelper: function(providerUri, directives, opts) {
var logoutReq = OAUTH3.urls.logout(
directives
, { client_id: (opts.client_id || opts.client_uri || OAUTH3.clientUri(OAUTH3._browser.window.location))
@ -907,10 +885,10 @@
if (params.error) {
// 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;
});
}
@ -938,20 +916,20 @@
else {
return OAUTH3.request({
method: 'GET'
, url: OAUTH3.url.normalize(providerUri) + '/' + opts._pathname // '/.well-known/oauth3/' + discoverFile
, 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)) {
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..."
+ " this probably won't end well");
console.warn(opts.client_id || opts.client_uri, OAUTH3._browser.window.location.hostname);
}
var discReq = OAUTH3.urls[opts._rpc || 'rpc'](
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'
@ -974,36 +952,27 @@
// TODO allow node to open a desktop browser window
opts._windowType = opts.windowType;
opts.windowType = opts.windowType || 'background';
return OAUTH3._browser.testPixel(providerUri).then(function () {
return OAUTH3._browser.frameRequest(
OAUTH3.url.resolve(providerUri, discReq.url)
, discReq.state
// why not just pass opts whole?
, { windowType: opts.windowType
, reuseWindow: opts.broker && '-broker'
, debug: opts.debug
}
).then(function (params) {
opts.windowType = opts._windowType;
return OAUTH3._browser.frameRequest(
OAUTH3.url.resolve(providerUri, discReq.url)
, discReq.state
// why not just pass opts whole?
, { windowType: opts.windowType
, reuseWindow: opts.broker && '-broker'
, debug: opts.debug
}
).then(function (params) {
opts.windowType = opts._windowType;
// caller will call OAUTH3._browser.closeFrame(discReq.state, { debug: opts.debug || params.debug });
if (params.error) {
// TODO directives.issuer || directives.audience
return OAUTH3.PromiseA.reject(OAUTH3.error.parse(providerUri, params));
}
// caller will call OAUTH3._browser.closeFrame(discReq.state, { debug: opts.debug || params.debug });
if (params.error) {
// TODO directives.issuer || directives.audience
return OAUTH3.PromiseA.reject(OAUTH3.error.parse(providerUri, params));
}
// TODO params should have response_type indicating json, binary, etc
var result;
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);
return result;
});
// TODO params should have response_type indicating json, binary, etc
var result = JSON.parse(OAUTH3._base64.decodeUrlSafe(params.data || params.result || params.directives));
// caller will call OAUTH3.hooks.directives.set(providerUri, directives);
return result;
});
}
, request: function (preq, _sys) {
@ -1102,28 +1071,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) {
opts = opts || {};
var previousFrame = OAUTH3._browser._frames[state];
@ -1134,10 +1081,11 @@
}
var timeout = opts.timeout;
if ('background' === windowType) {
if (!timeout) {
timeout = 7 * 1000;
}
if (opts.debug) {
timeout = timeout || 3 * 60 * 1000;
}
else {
timeout = timeout || ('background' === windowType ? 15 * 1000 : 3 * 60 * 1000);
}
return new OAUTH3.PromiseA(function (resolve, reject) {
@ -1159,16 +1107,14 @@
cleanup();
};
if (timeout) {
tok = setTimeout(function () {
var err = new Error(
"the '" + windowType + "' request did not complete within " + Math.round(timeout / 1000) + "s"
);
err.code = "E_TIMEOUT";
reject(err);
cleanup();
}, timeout);
}
tok = setTimeout(function () {
var err = new Error(
"the '" + windowType + "' request did not complete within " + Math.round(timeout / 1000) + "s"
);
err.code = "E_TIMEOUT";
reject(err);
cleanup();
}, timeout);
setTimeout(function () {
if (!OAUTH3._browser._frames[state]) {
@ -1371,23 +1317,6 @@
OAUTH3.utils = {
clientUri: OAUTH3.clientUri
, 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
, uri: OAUTH3.uri
, url: OAUTH3.url

View File

@ -218,7 +218,6 @@ OAUTH3.urls.grants = function (directive, opts) {
, session: opts.session
};
};
//OAUTH3.urls.accessToken = function (directive, opts)
OAUTH3.urls.clientToken = function (directive, opts) {
var tokenDir = directive.access_token;
if (!tokenDir) {
@ -295,7 +294,7 @@ OAUTH3.urls.credentialMeta = function (directive, opts) {
.replace(':id', opts.email)
};
OAUTH3.authn = OAUTH3.authn || {};
OAUTH3.authn = {};
OAUTH3.authn.loginMeta = function (directive, opts) {
var url = OAUTH3.urls.credentialMeta(directive, opts);
return OAUTH3.request({
@ -371,8 +370,8 @@ OAUTH3.authn.resourceOwnerPassword = function (directive, opts) {
OAUTH3.authz = {};
OAUTH3.authz.scopes = function (providerUri, session, clientParams) {
var clientUri = OAUTH3.uri.normalize(clientParams.client_uri || OAUTH3._browser.window.document.referrer);
var scope = clientParams.scope || 'authn@oauth3.org';
if ('authn@oauth3.org' === scope.toString()) {
var scope = clientParams.scope || 'oauth3_authn';
if ('oauth3_authn' === scope) {
// implicit ppid grant is automatic
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
@ -574,85 +573,68 @@ OAUTH3.authz.redirectWithToken = function (providerUri, session, clientParams, s
};
OAUTH3.requests = {};
//OAUTH3.accounts = {};
OAUTH3.requests.accounts = {};
OAUTH3.urls.accounts = {};
OAUTH3.urls.accounts._ = function (directives, directive, session, opts) {
opts = opts || {};
var dir = directive || {
//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'
OAUTH3.requests.accounts.update = function (directive, session, opts) {
var dir = directive.update_account || {
method: 'POST'
, url: OAUTH3.url.normalize(directive.api) + '/api/issuer@oauth3.org/accounts/:accountId'
, bearer: 'Bearer'
};
var url = dir.url
.replace(/:accountId/, opts.accountId || '')
.replace(/\/$/, '')
.replace(/:accountId/, opts.accountId)
;
return {
url: url
//, method: dir.method || 'POST'
, session: session
/*
return OAUTH3.request({
method: dir.method || 'POST'
, url: url
, headers: {
'Authorization': (dir.bearer || 'Bearer') + ' ' + (session.access_token || session.accessToken)
'Authorization': (dir.bearer || 'Bearer') + ' ' + session.accessToken
}
*/
};
, json: {
name: opts.name
, comment: opts.comment
, displayName: opts.displayName
, priority: opts.priority
}
});
};
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
, comment: opts.comment
, displayName: opts.displayName
, priority: opts.priority
OAUTH3.requests.accounts.create = function (directive, session, account) {
var dir = directive.create_account || {
method: 'POST'
, url: OAUTH3.url.normalize(directive.api) + '/api/issuer@oauth3.org/accounts'
, bearer: 'Bearer'
};
return urlObj;
};
OAUTH3.urls.accounts.create = function (directives, session, account) {
var urlObj = OAUTH3.urls.accounts._(directives, directives.create_account, session);
var profile = {
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
, comment: account.comment
, display_name: account.display_name
, priority: account.priority
};
var credentials = [ { token: session.access_token } ];
urlObj.method = (directives.create_account || { method: 'POST' }).method;
urlObj.json = {
var data = {
// TODO fix the server to just use one scheme
// account = { nick, self: { comment, username } }
// account = { name, comment, display_name, priority }
credentials: credentials
, profile: profile
// 'account' is deprecated in favor of 'profile'
, account: profile
// 'logins' is deprecated in favor of 'credentials'
, logins: credentials
account: {
nick: account.display_name
, name: account.name
, comment: account.comment
, display_name: account.display_name
, 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
}
]
};
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);
return OAUTH3.request({
method: dir.method || 'POST'
, url: dir.url
, session: session
, data: data
});
};
OAUTH3.hooks.grants = {

View File

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

View File

@ -1 +0,0 @@
_apis

View File

@ -128,7 +128,7 @@
onError(err);
}
OAUTH3.request({ url: params._pathname.replace(/^\.well-known\/oauth3\//, '') }).then(function (resp) {
OAUTH3.request({ url: 'directives.json' }).then(function (resp) {
urlsafe64 = OAUTH3._base64.encodeUrlSafe(JSON.stringify(resp.data, null, 0));
onSuccess(urlsafe64);