diff --git a/README.md b/README.md
index 78cb34c..ec7cb33 100644
--- a/README.md
+++ b/README.md
@@ -12,16 +12,151 @@ Also, instead of complicated (or worse - insecure) CLI and Desktop login methods
you can easily integrate an OAuth3 flow (or broker) into any node.js app (i.e. Electron, Node-Webkit)
with 0 pain.
-Installation
+If you have no idea what you're doing
------------
-**Easy Install** for Web Apps (including Mobile):
+(people who know what they're doing should skip ahead to the tl;dr instructions)
-1. In your web site / web app folder create a folder called `assets`
-2. Inside of `assets` create another folder called `org.oauth3`
-3. Download [oauth.js-v1.zip](https://git.daplie.com/Daplie/oauth3.js/repository/archive.zip?ref=v1)
-4. Double-click to unzip the folder.
-5. Copy `oauth3.js` and `oauth3.browser.js` to `assets/org.oauth3`
+1. Create a folder for your project named after your app, such as `example.com/`
+2. Inside of the folder `example.com/` a folder called `assets/`
+3. Inside of the folder `example.com/assets` a folder called `org.oauth3/`
+4. Download [oauth.js-v1.zip](https://git.daplie.com/Daplie/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/org.oauth3/`
+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 `` to your `index.html`
+9. Add `` to your `index.html`
+10. Create files in `example.com` called `app.js` and `index.html` and put this in it:
+
+`index.html`:
+```html
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+`app.js`:
+```js
+var OAUTH3 = window.OAUTH3;
+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) {
+ // example https://oauth3.org
+ return auth.setProvider(providerUri);
+}
+
+
+// This opens up the login window for the specified provider
+//
+function onClickLogin() {
+
+ return auth.authenticate().then(function (session) {
+
+ console.info('Authentication was Successful:');
+ console.log(session);
+
+ // You can use the PPID (or preferrably a hash of it) as the login for your app
+ // (it securely functions as both username and password which is known only by your app)
+ // If you use a hash of it as an ID, you can also use the PPID itself as a decryption key
+ //
+ console.info('Secure PPID (aka subject):', session.token.sub);
+
+ return auth.request({
+ url: 'https://oauth3.org/api/org.oauth3.provider/inspect'
+ , session: session
+ }).then(function (resp) {
+
+ console.info("Inspect Token:");
+ console.log(resp.data);
+
+ });
+
+ }, function (err) {
+ console.error('Authentication Failed:');
+ console.log(err);
+ });
+
+}
+
+
+// This opens up the logout window
+//
+function onClickLogout() {
+
+ return auth.logout().then(function () {
+ localStorage.clear();
+
+ console.info('Logout was Successful');
+
+ }, function (err) {
+ console.error('Logout Failed:');
+ console.log(err);
+ });
+
+}
+
+
+// initialize the provider to be oauth3.org (or any compatible provider)
+//
+onChangeProvider('oauth3.org');
+
+
+$('body').on('click', '.js-login', onClickLogin);
+$('body').on('click', '.js-logout', onClickLogout);
+$('body').on('change', 'input.js-provider-uri', onChangeProvider);
+```
+
+Copy the `example.com/` folder to your webserver.
+
+
+Example
+-------
+
+If you had a simple website / webapp for `example.com` with only the most necessary files,
+it might look like this:
+
+```
+example.com
+│
+│
+├── .well-known (hidden)
+│ └── oauth3
+│ ├── callback.html
+│ ├── directives.json
+│ └── index.html
+├── assets
+│ └── org.oauth3
+│ └── oauth3.core.js
+│
+│
+├── css
+│ └── main.css
+├── index.html
+└── js
+ └── app.js
+```
+
+Installation (if you know what you're doing)
+------------
**Advanced Installation with `git`**
@@ -63,40 +198,13 @@ ln -sf ../bower_components/oauth3/.well-known/oauth3 .well-known/oauth3
ln -sf ../bower_components/oauth3 assets/org.oauth3
```
-Example
--------
-
-If you had a simple website / webapp for `example.com` with only the most necessary files,
-it might look like this:
-
-```
-example.com
-│
-│
-├── .well-known
-│ └── oauth3
-│ ├── callback.html
-│ ├── directives.json
-│ └── index.html
-├── assets
-│ └── org.oauth3
-│ └── oauth3.implicit.js
-│
-│
-├── css
-│ └── main.css
-├── index.html
-└── js
- └── app.js
-```
-
Usage
-----
Update your HTML to include the the following script tag:
```html
-
+
```
You can create a very simple demo application like this:
@@ -169,13 +277,46 @@ We've created an `Oauth3` service just for you:
You can include that in addition to the standard file or,
if you don't want an extra request, just paste it into your `app.js`.
-Stable API
+Simple API
+----------
+
+We include a small wrapper function of just a few lines in the bottom of `oauth3.core.js`
+which exposes a `create` method to make using the underlying library require typing fewer keystrokes.
+
+```
+auth = OAUTH3.create(location); // takes a location object, such as window.location
+ // to create the Client URI (your app's id)
+ // and save it to an internal state
+
+promise = auth.init(location); // set and fetch your own site/app's configuration details
+// promises your site's config
+
+promise = auth.setProvider(url); // changes the Provider URI (the site you're logging into),
+// 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 = auth.authenticate(); // opens login window for the provider and returns a session
+ // (must be called after the setProvider promise has completed)
+
+promise = auth.authorize(permissions); // authenticates (if not authenticated) and opens a window to
+ // authorize a particular scope (contacts, photos, whatever)
+
+promise = auth.request({ url, method, data }); // make an (authorized) request to a provider's resource
+ // (contacts, photos, whatever)
+
+promise = auth.logout(); // opens logout window for the provider
+
+auth.session(); // returns the current session, if any
+```
+
+
+Real API
----------
```
-OAUTH3.utils.clientUri(window.location); // produces the default `client_uri` of your app (also used as `client_id`)
+OAUTH3.clientUri(window.location); // produces the default `client_uri` of your app (also used as `client_id`)
OAUTH3.discover(providerUri, { client_id: clientUri }); // Promises the config file for the provider and caches it in memory.
@@ -203,7 +344,7 @@ OAUTH3.urls
-Staging API
+Core API (staging)
----------
These APIs are NOT yet public, stable APIs, but they are good to be aware of
@@ -217,13 +358,24 @@ Public utilities for browser and node.js:
OAUTH3.jwt
.decode(''); // { iat, iss, aud, sub, exp, ttl }
-OAUTH3.utils
+OAUTH3
.query.stringify({ access_token: '...', debug: true }); // access_token=...&debug=true
.scope.stringify([ 'profile', 'contacts' ]); // 'profile,contacts'
.uri.normalize('https://oauth3.org/connect/'); // 'oauth3.org/connect'
.url.normalize('oauth3.org/connect/'); // 'https://oauth3.org/connect'
.url.resolve('oauth3.org/connect/', '/api/'); // 'https://oauth3.org/connect/api'
- .atob(''); // '' (typically json ascii)
+```
+
+Issuer API (staging)
+-------------------
+
+These additional methods are
+
+```
+OAUTH3
+ .query.parse('#/?foo=bar&baz=qux'); // { access_token: '...', debug: 'true' }
+ .scope.parse('profile,contacts'); // [ 'profile', 'contacts' ]
+ .url.redirect(clientParams, grants, tokenOrError); // securely redirect to client (or give security or other error)
```
Internal API
@@ -236,11 +388,12 @@ This APIs will absolutely change before they are made public
OAUTH3.jwt
.freshness(tokenMeta, staletimeSeconds, _now); // returns 'fresh', 'stale', or 'expired' (by seconds before expiry / ttl)
-OAUTH3.utils
+OAUTH3
.url._normalizePath('oauth3.org/connect/'); // 'oauth3.org/connect'
- ._urlSafeBase64ToBase64(b64); // makes base64 safe for window.atob
.randomState(); // a 128-bit crypto-random string
._insecureRandomState(); // a fallback for randomState() in old browsers
+ ._base64.atob(''); // '' (typically json ascii)
+ ._base64.decodeUrlSafe(b64); // makes base64 safe for window.atob and then calls atob
OAUTH3._browser // a collection of things a browser needs to perform requests
```
diff --git a/oauth3.core.js b/oauth3.core.js
index bc4ad35..15b2432 100644
--- a/oauth3.core.js
+++ b/oauth3.core.js
@@ -937,7 +937,7 @@
location = OAUTH3._browser.window.location;
}
- return {
+ var result = {
_clientUri: OAUTH3.clientUri(location)
, _providerUri: null
, init: function (location) {
@@ -1003,6 +1003,10 @@
return OAUTH3.logout(this._providerUri, opts);
}
};
+ result.authenticate = result.login;
+ result.authorize = result.login;
+ result.expire = result.logout;
+ return result;
};
}('undefined' !== typeof exports ? exports : window));
diff --git a/oauth3.implicit.js b/oauth3.implicit.js
deleted file mode 100644
index bc4ad35..0000000
--- a/oauth3.implicit.js
+++ /dev/null
@@ -1,1008 +0,0 @@
-/* global Promise */
-;(function (exports) {
- 'use strict';
-
- var OAUTH3 = exports.OAUTH3 = {
- clientUri: function (location) {
- return OAUTH3.uri.normalize(location.host + location.pathname);
- }
- , error: {
- parse: function (providerUri, params) {
- var err = new Error(params.error_description || params.error.message || "Unknown error with provider '" + providerUri + "'");
- err.uri = params.error_uri || params.error.uri;
- err.code = params.error.code || params.error;
- return err;
- }
- }
- , _base64: {
- atob: function (base64) {
- // atob must be called from the global context
- // http://stackoverflow.com/questions/9677985/uncaught-typeerror-illegal-invocation-in-chrome
- return (exports.atob || require('atob'))(base64);
- }
- , decodeUrlSafe: function (b64) {
- // URL-safe Base64 to Base64
- // https://en.wikipedia.org/wiki/Base64
- // https://gist.github.com/catwell/3046205
- var mod = b64.length % 4;
- if (2 === mod) { b64 += '=='; }
- if (3 === mod) { b64 += '='; }
- b64 = b64.replace(/-/g, '+').replace(/_/g, '/');
- return OAUTH3._base64.atob(b64);
- }
- }
- , uri: {
- normalize: function (uri) {
- if ('string' !== typeof uri) {
- console.error((new Error('stack')).stack);
- }
- // tested with
- // example.com
- // example.com/
- // http://example.com
- // https://example.com/
- return uri
- .replace(/^(https?:\/\/)?/i, '')
- .replace(/\/?$/, '')
- ;
- }
- }
- , url: {
- normalize: function (url) {
- if ('string' !== typeof url) {
- console.error((new Error('stack')).stack);
- }
- // tested with
- // example.com
- // example.com/
- // http://example.com
- // https://example.com/
- return url
- .replace(/^(https?:\/\/)?/i, 'https://')
- .replace(/\/?$/, '')
- ;
- }
- , resolve: function (base, next) {
- if (/^https:\/\//i.test(next)) {
- return next;
- }
- return this.normalize(base) + '/' + this._normalizePath(next);
- }
- , _normalizePath: function (path) {
- return path.replace(/^\//, '').replace(/\/$/, '');
- }
- }
- , query: {
- stringify: function (params) {
- var qs = [];
-
- Object.keys(params).forEach(function (key) {
- // TODO nullify instead?
- if ('undefined' === typeof params[key]) {
- return;
- }
-
- if ('scope' === key) {
- params[key] = OAUTH3.scope.stringify(params[key]);
- }
-
- qs.push(encodeURIComponent(key) + '=' + encodeURIComponent(params[key]));
- });
-
- return qs.join('&');
- }
- }
- , scope: {
- stringify: function (scope) {
- if (Array.isArray(scope)) {
- scope = scope.join(' ');
- }
- return scope;
- }
- }
- , randomState: function () {
- // TODO put in different file for browser vs node
- try {
- return Array.prototype.slice.call(
- window.crypto.getRandomValues(new Uint8Array(16))
- ).map(function (ch) { return (ch).toString(16); }).join('');
- } catch(e) {
- return OAUTH3.utils._insecureRandomState();
- }
- }
- , _insecureRandomState: function () {
- var i;
- var ch;
- var str;
- // TODO use fisher-yates on 0..255 and select [0] 16 times
- // [security] https://medium.com/@betable/tifu-by-using-math-random-f1c308c4fd9d#.5qx0bf95a
- // https://github.com/v8/v8/blob/b0e4dce6091a8777bda80d962df76525dc6c5ea9/src/js/math.js#L135-L144
- // Note: newer versions of v8 do not have this bug, but other engines may still
- console.warn('[security] crypto.getRandomValues() failed, falling back to Math.random()');
- str = '';
- for (i = 0; i < 32; i += 1) {
- ch = Math.round(Math.random() * 255).toString(16);
- if (ch.length < 2) { ch = '0' + ch; }
- str += ch;
- }
- return str;
- }
- , jwt: {
- // decode only (no verification)
- decode: function (str) {
-
- // 'abc.qrs.xyz'
- // [ 'abc', 'qrs', 'xyz' ]
- // [ {}, {}, 'foo' ]
- // { header: {}, payload: {}, signature: '' }
- var parts = str.split(/\./g);
- var jsons = parts.slice(0, 2).map(function (urlsafe64) {
- var b64 = OAUTH3._base64.decodeUrlSafe(urlsafe64);
- return b64;
- });
-
- return {
- header: JSON.parse(jsons[0])
- , payload: JSON.parse(jsons[1])
- , signature: parts[2] // should remain url-safe base64
- };
- }
- , freshness: function (tokenMeta, staletime, _now) {
- staletime = staletime || (15 * 60);
- var now = _now || Date.now();
- var fresh = ((parseInt(tokenMeta.exp, 10) || 0) - Math.round(now / 1000));
-
- if (fresh >= staletime) {
- return 'fresh';
- }
-
- if (fresh <= 0) {
- return 'expired';
- }
-
- return 'stale';
- }
- }
- , urls: {
- discover: function (providerUri, opts) {
- if (!providerUri) {
- throw new Error("cannot discover without providerUri");
- }
- if (!opts.client_id) {
- throw new Error("cannot discover 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()
- , redirect_uri: clientId + (opts.client_callback_path || '/.well-known/oauth3/callback.html#/')
- , response_type: 'rpc'
- , _method: 'GET'
- , _pathname: '.well-known/oauth3/directives.json'
- , debug: opts.debug || undefined
- };
-
- var result = {
- url: providerUri + '/.well-known/oauth3/#/?' + OAUTH3.query.stringify(params)
- , state: params.state
- , method: 'GET'
- , query: params
- };
-
- return result;
- }
- , implicitGrant: function (directive, opts) {
- //
- // Example Implicit Grant Request
- // (for generating a browser-only session, not a session on your server)
- //
- // GET https://example.com/api/org.oauth3.provider/authorization_dialog
- // ?response_type=token
- // &scope=`encodeURIComponent('profile.login profile.email')`
- // &state=`cryptoutil.random().toString('hex')`
- // &client_id=xxxxxxxxxxx
- // &redirect_uri=`encodeURIComponent('https://myapp.com/oauth3.html')`
- //
- // NOTE: `redirect_uri` itself may also contain URI-encoded components
- //
-
- opts = opts || {};
- var type = 'authorization_dialog';
- var responseType = 'token';
-
- var scope = opts.scope || directive.authn_scope;
- var args = directive[type];
- var uri = args.url;
- var state = opts.state || OAUTH3.utils.randomState();
- var params = {
- debug: opts.debug || undefined
- , client_uri: opts.client_uri || opts.clientUri || undefined
- , client_id: opts.client_id || opts.client_uri || undefined
- , state: state
- };
- var result;
-
- params.response_type = responseType;
- if (scope) {
- params.scope = OAUTH3.scope.stringify(scope);
- }
- if (!opts.redirect_uri) {
- // TODO consider making this optional
- //console.warn("auto-generating redirect_uri from hard-coded callback.html"
- // + " (should be configurable... but then redirect_uri could just be manually-generated)");
- opts.redirect_uri = OAUTH3.url.resolve(
- OAUTH3.url.normalize(params.client_uri)
- , '.well-known/oauth3/callback.html'
- );
- }
- params.redirect_uri = opts.redirect_uri;
-
- uri += '?' + OAUTH3.query.stringify(params);
-
- result = {
- url: uri
- , state: state
- , method: args.method
- , query: params
- };
-
- return result;
- }
- , refreshToken: function (directive, opts) {
- // grant_type=refresh_token
-
- // Example Refresh Token Request
- // (generally for 1st or 3rd party server-side, mobile, and desktop apps)
- //
- // POST https://example.com/api/oauth3/access_token
- // { "grant_type": "refresh_token", "client_id": "<>", "scope": "<>"
- // , "username": "<>", "password": "password" }
- //
- opts = opts || {};
- var type = 'access_token';
- var grantType = 'refresh_token';
-
- var scope = opts.scope || directive.authn_scope;
- var clientSecret = opts.client_secret;
- var args = directive[type];
- var params = {
- "grant_type": grantType
- , "refresh_token": opts.refresh_token || (opts.session && opts.session.refresh_token)
- , "response_type": 'token'
- , "client_id": opts.client_id || opts.client_uri
- , "client_uri": opts.client_uri
- //, "scope": undefined
- //, "client_secret": undefined
- , debug: opts.debug || undefined
- };
- var uri = args.url;
- var body;
-
- if (clientSecret) {
- // TODO not allowed in the browser
- console.warn("if this is a browser, you must not use client_secret");
- params.client_secret = clientSecret;
- }
-
- if (scope) {
- params.scope = OAUTH3.scope.stringify(scope);
- }
-
- if ('GET' === args.method.toUpperCase()) {
- uri += '?' + OAUTH3.query.stringify(params);
- } else {
- body = params;
- }
-
- return {
- url: uri
- , method: args.method
- , data: body
- };
- }
- , logout: function (directive, opts) {
- // action=logout
-
- // Example Logout Request
- // (generally for 1st or 3rd party server-side, mobile, and desktop apps)
- //
- // GET https://example.com/#/logout/
- // ?client_id=<>
- // &access_token=<>
- // &sub=<>
- //
- // Note that the use of # keeps certain parameters from traveling across
- // the network at all (and we use https anyway)
- //
- opts = opts || {};
- var action = 'logout';
- var args = directive[action];
- var state = opts.state || OAUTH3.utils.randomState();
- var params = {
- action: action
- //, response_type: 'confirmation'
- , client_id: opts.client_id || opts.client_uri
- , client_uri: opts.client_uri || opts.client_id
- , state: state
- , redirect_uri: opts.redirect_uri = OAUTH3.url.resolve(
- OAUTH3.url.normalize(opts.client_uri || opts.client_id)
- , '.well-known/oauth3/callback.html'
- )
- , debug: opts.debug
- };
- var uri = args.url;
- var body;
-
- if ('GET' === args.method.toUpperCase()) {
- uri += '?' + OAUTH3.query.stringify(params);
- } else {
- body = params;
- }
-
- return {
- url: OAUTH3.url.resolve(directive.issuer, uri)
- , method: args.method
- , state: state
- , data: body
- };
- }
- }
- , hooks: {
- directives: {
- _get: function (providerUri) {
- providerUri = OAUTH3.uri.normalize(providerUri);
- if (!OAUTH3.hooks.directives._cache) { OAUTH3.hooks.directives._cache = {}; }
- return OAUTH3.PromiseA.resolve(OAUTH3.hooks.directives._cache[providerUri]
- || OAUTH3.hooks.directives.get(providerUri))
- .then(function (directives) {
- // or do .then(this._set) to keep DRY?
- OAUTH3.hooks.directives._cache[providerUri] = directives;
- return directives;
- });
- }
- , _getCached: function (providerUri) {
- providerUri = OAUTH3.uri.normalize(providerUri);
- return OAUTH3.hooks.directives._cache[providerUri];
- }
- , get: function (providerUri) {
- console.warn('[Warn] You should implement: OAUTH3.hooks.directives.get = function (providerUri) { return directives; }');
- return JSON.parse(window.localStorage.getItem('directives-' + providerUri) || '{}');
- }
- , _set: function (providerUri, directives) {
- providerUri = OAUTH3.uri.normalize(providerUri);
- if (!OAUTH3.hooks.directives._cache) { OAUTH3.hooks.directives._cache = {}; }
- OAUTH3.hooks.directives._cache[providerUri] = directives;
- return OAUTH3.PromiseA.resolve(OAUTH3.hooks.directives.set(providerUri, directives));
- }
- , set: function (providerUri, directives) {
- console.warn('[Warn] You should implement: OAUTH3.hooks.directives.set = function (providerUri, directives) { return directives; }');
- window.localStorage.setItem('directives-' + providerUri, JSON.stringify(directives));
- return directives;
- }
- }
- , session: {
- refresh: function (oldSession, newSession) {
- var providerUri = oldSession.provider_uri;
- var clientUri = oldSession.client_uri;
-
- Object.keys(oldSession).forEach(function (key) {
- oldSession[key] = undefined;
- });
- Object.keys(newSession).forEach(function (key) {
- oldSession[key] = newSession[key];
- });
-
- // info about the session of this API call
- oldSession.provider_uri = providerUri; // aud
- oldSession.client_uri = clientUri; // azp
-
- // info about the newly-discovered token
- oldSession.token = OAUTH3.jwt.decode(oldSession.access_token).payload;
-
- oldSession.token.sub = oldSession.token.sub || oldSession.token.acx.id;
- oldSession.token.client_uri = clientUri;
- oldSession.token.provider_uri = providerUri;
-
- if (oldSession.refresh_token) {
- oldSession.refresh = OAUTH3.jwt.decode(oldSession.refresh_token).payload;
- oldSession.refresh.sub = oldSession.refresh.sub || oldSession.refresh.acx.id;
- oldSession.refresh.provider_uri = providerUri;
- }
-
- // set for a set of audiences
- return OAUTH3.PromiseA.resolve(OAUTH3.hooks.session.set(providerUri, oldSession));
- }
- , check: function (preq, opts) {
- if (!preq.session) {
- return OAUTH3.PromiseA.resolve(null);
- }
- var freshness = OAUTH3.jwt.freshness(preq.session.token, opts.staletime);
-
- switch (freshness) {
- case 'stale':
- return OAUTH3.hooks.session.stale(preq.session);
- case 'expired':
- return OAUTH3.hooks.session.expired(preq.session).then(function (newSession) {
- preq.session = newSession;
- return newSession;
- });
- //case 'fresh':
- default:
- return OAUTH3.PromiseA.resolve(preq.session);
- }
- }
- , stale: function (staleSession) {
- if (OAUTH3.hooks.session._stalePromise) {
- return OAUTH3.PromiseA.resolve(staleSession);
- }
-
- OAUTH3.hooks.session._stalePromise = OAUTH3._refreshToken(
- staleSession.provider_uri
- , { client_uri: staleSession.client_uri
- , session: staleSession
- , debug: staleSession.debug
- }
- ).then(function (newSession) {
- OAUTH3.hooks.session._stalePromise = null;
- return newSession; // oauth3.hooks.refreshSession(staleSession, newSession);
- }, function () {
- OAUTH3.hooks.session._stalePromise = null;
- });
-
- return OAUTH3.PromiseA.resolve(staleSession);
- }
- , expired: function (expiredSession) {
- return OAUTH3._refreshToken(
- expiredSession.provider_uri
- , { client_uri: expiredSession.client_uri
- , session: expiredSession
- , debug: expiredSession.debug
- }
- ).then(function (newSession) {
- return newSession; // oauth3.hooks.refreshSession(expiredSession, newSession);
- });
- }
- , set: function (providerUri, newSession) {
- if (!providerUri) {
- console.error(new Error('no providerUri').stack);
- throw new Error("providerUri is not set");
- }
- providerUri = OAUTH3.uri.normalize(providerUri);
- console.warn('[Warn] Please implement OAUTH3.hooks.session.set = function (providerUri, newSession) { return PromiseA; }');
- if (!OAUTH3.hooks.session._sessions) { OAUTH3.hooks.session._sessions = {}; }
- OAUTH3.hooks.session._sessions[providerUri] = newSession;
- return OAUTH3.PromiseA.resolve(newSession);
- }
- , _getCached: function (providerUri) {
- providerUri = OAUTH3.uri.normalize(providerUri);
- return OAUTH3.hooks.session._sessions[providerUri];
- }
- , get: function (providerUri) {
- providerUri = OAUTH3.uri.normalize(providerUri);
- if (!providerUri) {
- throw new Error("providerUri is not set");
- }
- console.warn('[Warn] Please implement OAUTH3.hooks.session.get = function (providerUri) { return PromiseA; }');
- if (!OAUTH3.hooks.session._sessions) { OAUTH3.hooks.session._sessions = {}; }
- return OAUTH3.PromiseA.resolve(OAUTH3.hooks.session._sessions[providerUri]);
- }
- }
- }
- , discover: 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) {
- if (directives && directives.issuer) {
- return directives;
- }
- return OAUTH3._discoverHelper(providerUri, opts).then(function (directives) {
- directives.azp = directives.azp || OAUTH3.url.normalize(providerUri);
- directives.issuer = directives.issuer || OAUTH3.url.normalize(providerUri);
- // OAUTH3.PromiseA.resolve() is taken care of because this is wrapped
- return OAUTH3.hooks.directives._set(providerUri, directives);
- });
- });
- }
- , _discoverHelper: function(providerUri, opts) {
- return OAUTH3._browser.discover(providerUri, opts);
- }
- , request: function (preq, opts) {
- function fetch() {
- if (preq.session) {
- // TODO check session.token.aud against preq.url to make sure they match
- console.warn("[security] session audience checking has not been implemented yet (it's up to you to check)");
- preq.headers = preq.headers || {};
- preq.headers.Authorization = 'Bearer ' + (preq.session.access_token || preq.session.accessToken);
- }
-
- return OAUTH3._requestHelper(preq, opts);
- }
-
- OAUTH3.url.resolve(preq.providerUri || preq.provider_uri || preq.directives && preq.directives.issuer, preq.url);
-
- if (!preq.session) {
- return fetch();
- }
-
- return OAUTH3.hooks.session.check(preq, opts).then(fetch);
- }
- , _requestHelper: function (preq, opts) {
- return OAUTH3._browser.request(preq, opts);
- }
- , implicitGrant: function(directives, opts) {
- var promise;
- var providerUri = directives.azp || directives.issuer || directives;
-
- if (opts.broker) {
- // Discovery can happen in-flow because we know that this is
- // a valid oauth3 provider
- promise = OAUTH3._discoverThenImplicitGrant(providerUri, opts);
- }
- else {
- // Discovery must take place before calling implicitGrant
- promise = OAUTH3._implicitGrant(OAUTH3.hooks.directives._getCached(providerUri), opts);
- }
-
- return promise.then(function (tokens) {
- // TODO abstract browser bits away
- try {
- OAUTH3._browser.closeFrame(tokens.state || opts._state, opts);
- } catch(e) {
- console.warn("[implicitGrant] TODO abstract browser bits away");
- }
- opts._state = undefined;
- return OAUTH3.hooks.session.refresh(
- opts.session || {
- provider_uri: providerUri
- , client_id: opts.client_id
- , client_uri: opts.client_uri || opts.clientUri
- }
- , tokens
- );
- });
- }
- , _discoverThenImplicitGrant: function(providerUri, opts) {
- opts.windowType = opts.windowType || 'popup';
- return OAUTH3.discover(providerUri, opts).then(function (directives) {
- return OAUTH3._implicitGrant(directives, opts).then(function (tokens) {
- return tokens;
- });
- });
- }
- , _implicitGrant: function(directives, opts) {
- // TODO this may need to be synchronous for browser security policy
- // Do some stuff
- var authReq = OAUTH3.urls.implicitGrant(
- directives
- , { redirect_uri: opts.redirect_uri
- , client_id: opts.client_id || opts.client_uri
- , client_uri: opts.client_uri || opts.client_id
- , state: opts._state || undefined
- , debug: opts.debug
- }
- );
-
- if (opts.debug) {
- window.alert("DEBUG MODE: Pausing so you can look at logs and whatnot :) Fire at will!");
- }
-
- return OAUTH3._browser.frameRequest(
- OAUTH3.url.resolve(directives.issuer, authReq.url)
- , authReq.state // state should recycle params
- , { windowType: opts.windowType
- , reuseWindow: opts.broker && '-broker'
- , debug: opts.debug
- }
- ).then(function (tokens) {
- if (tokens.error) {
- // TODO directives.audience
- return OAUTH3.PromiseA.reject(OAUTH3.error.parse(directives.issuer /*providerUri*/, tokens));
- }
-
- return tokens;
- });
- }
- , _refreshToken: function (providerUri, opts) {
- return OAUTH3.discover(providerUri, opts).then(function (directive) {
- var prequest = OAUTH3.urls.refreshToken(directive, opts);
-
- return OAUTH3.request(prequest).then(function (req) {
- var data = req.data;
- data.provider_uri = providerUri;
- if (data.error) {
- return OAUTH3.PromiseA.reject(OAUTH3.error.parse(providerUri, data));
- }
- return OAUTH3.hooks.session.refresh(opts, data);
- });
- });
- }
- , logout: function(providerUri, opts) {
- return OAUTH3._logoutHelper(OAUTH3.hooks.directives._getCached(providerUri), opts);
- }
- , _logoutHelper: function(directives, opts) {
- var logoutReq = OAUTH3.urls.logout(
- directives
- , { client_id: (opts.client_id || opts.client_uri || OAUTH3.clientUri(OAUTH3._browser.window.location))
- , windowType: 'popup' // we'll figure out background later
- , broker: opts.broker
- //, state: opts._state
- , debug: opts.debug
- }
- );
-
- return OAUTH3._browser.frameRequest(
- OAUTH3.url.resolve(directives.issuer, logoutReq.url)
- , logoutReq.state // state should recycle params
- , { windowType: 'popup'
- , reuseWindow: opts.broker && '-broker'
- , debug: opts.debug
- }
- ).then(function (params) {
- OAUTH3._browser.closeFrame(params.state || opts._state, opts);
-
- if (params.error) {
- // TODO directives.audience
- return OAUTH3.PromiseA.reject(OAUTH3.error.parse(directives.issuer /*providerUri*/, params));
- }
-
- return params;
- });
- }
-
-
- //
- // Let the Code Waste begin!!
- //
- , _browser: {
- window: window
- // TODO we don't need to include this if we're using jQuery or angular
- , discover: function(providerUri, opts) {
- opts = opts || {};
- providerUri = OAUTH3.url.normalize(providerUri);
-
- if (providerUri.match(OAUTH3._browser.window.location.hostname)) {
- console.warn("It looks like you're a provider checking for your own directive,"
- + " 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;
- });
- }
-
- 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.discover(
- 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
- }
- );
- opts._state = discReq.state;
- //var discReq = OAUTH3.urls.discover(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
- // eventually it should be the browser (and postMessage may be a viable option now), but whatever...
-
- // TODO allow postMessage from providerUri in addition to callback
- // TODO allow node to open a desktop browser window
- opts._windowType = opts.windowType;
- opts.windowType = opts.windowType || 'background';
- 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));
- }
-
- // TODO params should have response_type indicating json, binary, etc
- var directives = JSON.parse(OAUTH3._base64.decodeUrlSafe(params.result || params.directives));
- // caller will call OAUTH3.hooks.directives._set(providerUri, directives);
- return directives;
- });
- }
- , request: function (preq, _sys) {
- return new OAUTH3.PromiseA(function (resolve, reject) {
- var xhr;
- try {
- xhr = new XMLHttpRequest(_sys);
- } catch(e) {
- xhr = new XMLHttpRequest();
- }
- xhr.onreadystatechange = function () {
- var data;
- if (xhr.readyState !== XMLHttpRequest.DONE) {
- // nothing to do here
- return;
- }
-
- if (xhr.status !== 200) {
- reject(new Error('bad status code: ' + xhr.status));
- return;
- }
-
- try {
- data = JSON.parse(xhr.responseText);
- } catch(e) {
- data = xhr.responseText;
- }
-
- resolve({
- request: xhr
- , data: data
- , status: xhr.status
- });
- };
- xhr.open(preq.method, preq.url, true);
- var headers = preq.headers || {};
- Object.keys(headers).forEach(function (key) {
- xhr.setRequestHeader(key, headers[key]);
- });
- xhr.send();
- });
- }
- , frameRequest: function (url, state, opts) {
- opts = opts || {};
- var previousFrame = OAUTH3._browser._frames[state];
-
- var windowType = opts.windowType;
- if (!windowType) {
- windowType = 'popup';
- }
-
- var timeout = opts.timeout;
- 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) {
- // TODO periodically garbage collect expired handlers from window object
- var tok;
-
- function cleanup() {
- delete window['--oauth3-callback-' + state];
- clearTimeout(tok);
- tok = null;
- // the actual close is done later (by the caller) so that the window/frame
- // can be reused or self-closes synchronously itself / by parent
- // (probably won't ever happen, but that's a negotiable implementation detail)
- }
-
-
- window['--oauth3-callback-' + state] = function (params) {
- resolve(params);
- cleanup();
- };
-
- 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]) {
- reject(new Error("TODO: open the iframe first and discover oauth3 directives before popup"));
- cleanup();
- }
- }, 0);
-
- if ('background' === windowType) {
- if (previousFrame) {
- previousFrame.location = url;
- //promise = previousFrame.promise;
- }
- else {
- OAUTH3._browser._frames[state] = OAUTH3._browser.iframe(url, state, opts);
- }
- } else if ('popup' === windowType) {
- if (previousFrame) {
- previousFrame.location = url;
- if (opts.debug) {
- previousFrame.focus();
- }
- }
- else {
- OAUTH3._browser._frames[state] = OAUTH3._browser.frame(url, state, opts);
- }
- } else if ('inline' === windowType) {
- // callback function will never execute and would need to redirect back to current page
- // rather than the callback.html
- url += '&original_url=' + OAUTH3._browser.window.location.href;
- OAUTH3._browser.window.location = url;
- //promise = OAUTH3.PromiseA.resolve({ url: url });
- return;
- } else {
- throw new Error("login framing method options.windowType="
- + opts.windowType + " not type yet implemented");
- }
-
- }).then(function (params) {
- if (params.error) {
- // TODO directives.issuer || directives.audience
- return OAUTH3.PromiseA.reject(OAUTH3.error.parse('https://oauth3.org', params));
- }
- return params;
- });
- }
- , closeFrame: function (state, opts) {
- opts = opts || {};
- function close() {
- try {
- OAUTH3._browser._frames[state].close();
- } catch(e) {
- try {
- OAUTH3._browser._frames[state].remove();
- } catch(e) {
- console.error(new Error("Could not clase window/iframe. closeFrame may have been called twice."));
- }
- }
-
- delete OAUTH3._browser._frames[state];
- }
-
- if (opts.debug) {
- if (window.confirm("DEBUG MODE: okay to close oauth3 window?")) {
- close();
- }
- }
- else {
- close();
- }
- }
- , _frames: {}
- , iframe: function (url, state, opts) {
- var framesrc = '';
-
- var frame = OAUTH3._browser.window.document.createElement('div');
- frame.innerHTML = framesrc;
- OAUTH3._browser.window.document.body.appendChild(frame);
-
- return frame;
- }
- , frame: function (url, state, opts) {
-
- // TODO allow size changes (via directive even)
- return window.open(
- url
- , 'oauth3-login-' + (opts.reuseWindow || state)
- , 'height=' + (opts.height || 720) + ',width=' + (opts.width || 620)
- );
- }
- }
- };
- OAUTH3.login = OAUTH3.implicitGrant;
-
- // TODO get rid of these
- OAUTH3.utils = {
- clientUri: OAUTH3.clientUri
- , query: OAUTH3.query
- , scope: OAUTH3.scope
- , uri: OAUTH3.uri
- , url: OAUTH3.url
- , _error: OAUTH3.error
- , _formatError: OAUTH3.error
- , _urlSafeBase64ToBase64: OAUTH3._urlSafeBase64ToBase64
- , randomState: OAUTH3.randomState
- , _insecureRandomState: OAUTH3._insecureRandomState
- };
-
- if ('undefined' !== typeof Promise) {
- OAUTH3.PromiseA = Promise;
- }
-
- // this is not necessary, but it's relatively small
- // and gives people the 3-line examples they love so much
- OAUTH3.create = function (location/*, opts*/) {
- if (!location) {
- location = OAUTH3._browser.window.location;
- }
-
- return {
- _clientUri: OAUTH3.clientUri(location)
- , _providerUri: null
- , init: function (location) {
- var me = this;
- var p = OAUTH3.PromiseA.resolve();
-
- if (location) {
- this._clientUri = OAUTH3.clientUri(location);
- }
- if (this._providerUri) {
- p = OAUTH3.discover(this._providerUri, { client_id: this._clientUri }).then(function (/*directives*/) {
- $('.js-signin').removeAttr('disabled');
- });
- }
-
- return OAUTH3.discover(this._clientUri, { client_id: this._clientUri }).then(function (clientDirectives) {
- me._clientDirectives = clientDirectives;
- return p.then(function () {
- return clientDirectives;
- });
- });
- }
- , setProvider: function (providerUri) {
- var me = this;
- me._providerUri = providerUri;
- return me.init().then(function () {
- // this should be synchronous the second time around
- return OAUTH3.discover(me._providerUri, { client_id: me._clientUri }).then(function (directives) {
- console.log("setProvider", directives);
- me._providerDirectives = directives;
- return directives;
- });
- });
- }
- , login: function (opts) {
- var me = this;
- opts = opts || {};
- opts.client_uri = me._clientUri;
-
- console.log('login', me._providerDirectives);
- return OAUTH3.implicitGrant(me._providerDirectives, opts).then(function (session) {
- me._session = true;
- return session;
- });
- }
- , session: function () {
- return JSON.parse(JSON.stringify(OAUTH3.hooks.session._getCached(this._providerUri)));
- }
- , request: function (preq) {
- preq.client_uri = this._clientUri;
- preq.client_id = this._clientUri;
- if (this._session) {
- preq.session = preq.session || OAUTH3.hooks.session._getCached(this._providerUri);
- }
- return OAUTH3.request(preq);
- }
- , logout: function (opts) {
- opts = opts || {};
- opts.client_uri = this._clientUri;
- opts.client_id = this._clientUri;
- opts.session = OAUTH3.hooks.session._getCached(this._providerUri);
-
- return OAUTH3.logout(this._providerUri, opts);
- }
- };
- };
-
-}('undefined' !== typeof exports ? exports : window));
diff --git a/oauth3.implicit.js b/oauth3.implicit.js
new file mode 120000
index 0000000..9131872
--- /dev/null
+++ b/oauth3.implicit.js
@@ -0,0 +1 @@
+oauth3.core.js
\ No newline at end of file