1330 lines
		
	
	
		
			47 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			1330 lines
		
	
	
		
			47 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* global Promise */
 | |
| ;(function (exports) {
 | |
|   'use strict';
 | |
| 
 | |
|   if ('undefined' !== typeof window && 'https:' !== window.location.protocol) {
 | |
|     window.alert("You must use https. We suggest using caddy as your webserver (or serve-https if testing locally)");
 | |
|   }
 | |
| 
 | |
|   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;
 | |
|       }
 | |
|     }
 | |
|   , _binStr: {
 | |
|       bufferToBinStr: function (buf) {
 | |
|         return Array.prototype.map.call(new Uint8Array(buf), function(ch) {
 | |
|           return String.fromCharCode(ch);
 | |
|         }).join('');
 | |
|       }
 | |
|     , binStrToBuffer: function (str) {
 | |
|         var buf;
 | |
| 
 | |
|         if ('undefined' !== typeof Uint8Array) {
 | |
|           buf = new Uint8Array(str.length);
 | |
|         } else {
 | |
|           buf = [];
 | |
|         }
 | |
| 
 | |
|         Array.prototype.forEach.call(str, function (ch, ind) {
 | |
|           buf[ind] = ch.charCodeAt(0);
 | |
|         });
 | |
|         return buf;
 | |
|       }
 | |
|     }
 | |
|   , _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);
 | |
|       }
 | |
|     , btoa: function (b64) {
 | |
|         // for directive passing in .well-known/oauth3
 | |
|         // http://stackoverflow.com/questions/9677985/uncaught-typeerror-illegal-invocation-in-chrome
 | |
|         return (exports.btoa || require('btoa'))(b64);
 | |
|       }
 | |
|     , 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);
 | |
|       }
 | |
|     , encodeUrlSafe: function (b64) {
 | |
|         // for directive passing in .well-known/oauth3
 | |
|         // Base64 to URL-safe Base64
 | |
|         b64 = OAUTH3._base64.btoa(b64);
 | |
|         b64 = b64.replace(/\+/g, '-').replace(/\//g, '_');
 | |
|         b64 = b64.replace(/=+/g, '');
 | |
|         return b64;
 | |
|       }
 | |
|     , urlSafeToBuffer: function (str) {
 | |
|         return OAUTH3._binStr.binStrToBuffer(OAUTH3._base64.decodeUrlSafe(str));
 | |
|       }
 | |
|     , bufferToUrlSafe: function (buf) {
 | |
|         return OAUTH3._base64.encodeUrlSafe(OAUTH3._binStr.bufferToBinStr(buf));
 | |
|       }
 | |
|     }
 | |
|   , 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: {
 | |
|       parse: function (search) {
 | |
|         // needed for .well-known/oauth3
 | |
|         // parse a query or a hash
 | |
|         if (-1 !== ['#', '?'].indexOf(search[0])) {
 | |
|           search = search.substring(1);
 | |
|         }
 | |
|         // Solve for case of search within hash
 | |
|         // example: #/authorization_dialog/?state=...&redirect_uri=...
 | |
|         var queryIndex = search.indexOf('?');
 | |
|         if (-1 !== queryIndex) {
 | |
|           search = search.substr(queryIndex + 1);
 | |
|         }
 | |
| 
 | |
|         var args = search.split('&');
 | |
|         var argsParsed = {};
 | |
|         var i, arg, kvp, key, value;
 | |
| 
 | |
|         for (i = 0; i < args.length; i += 1) {
 | |
|           arg = args[i];
 | |
|           if (-1 === arg.indexOf('=')) {
 | |
|             argsParsed[decodeURIComponent(arg).trim()] = true;
 | |
|           }
 | |
|           else {
 | |
|             kvp = arg.split('=');
 | |
|             key = decodeURIComponent(kvp[0]).trim();
 | |
|             value = decodeURIComponent(kvp[1]).trim();
 | |
|             argsParsed[key] = value;
 | |
|           }
 | |
|         }
 | |
|         return argsParsed;
 | |
|       }
 | |
|     , 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(
 | |
|           OAUTH3._browser.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) {
 | |
|           return JSON.parse(OAUTH3._base64.decodeUrlSafe(urlsafe64));
 | |
|         });
 | |
| 
 | |
|         return { header: jsons[0], payload: jsons[1] };
 | |
|       }
 | |
|     , verify: function (jwk, token) {
 | |
|         var parts = token.split(/\./g);
 | |
|         var data = OAUTH3._binStr.binStrToBuffer(parts.slice(0, 2).join('.'));
 | |
|         var signature = OAUTH3._base64.urlSafeToBuffer(parts[2]);
 | |
| 
 | |
|         return OAUTH3.crypto.core.verify(jwk, data, signature);
 | |
|       }
 | |
|     , 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
 | |
|         , subject: opts.subject
 | |
|         , state: state
 | |
|         };
 | |
|         var result;
 | |
| 
 | |
|         console.log('implicitGrant opts.subject: ', opts.subject);
 | |
| 
 | |
|         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": "<<id>>", "scope": "<<scope>>"
 | |
|         //    , "username": "<<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=<<id>>
 | |
|         //    &access_token=<<token>>
 | |
|         //    &sub=<<ppid>>
 | |
|         //
 | |
|         // 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);
 | |
|           return OAUTH3.PromiseA.resolve(OAUTH3.hooks.directives._getCached(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);
 | |
|           if (!OAUTH3.hooks.directives._cache) { OAUTH3.hooks.directives._cache = {}; }
 | |
|           return OAUTH3.hooks.directives._cache[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));
 | |
|         }
 | |
|       , _get: function (providerUri) {
 | |
|           if (!OAUTH3._hooks || !OAUTH3._hooks.directives || !OAUTH3._hooks.directives.get) {
 | |
|             console.warn('[Warn] Please implement OAUTH3._hooks.directives.get = function (providerUri) { return PromiseA<directives>; }');
 | |
|             return JSON.parse(window.localStorage.getItem('directives-' + providerUri) || '{}');
 | |
|           }
 | |
|           return OAUTH3._hooks.directives.get(providerUri);
 | |
|         }
 | |
|       , _set: function (providerUri, directives) {
 | |
|           if (!OAUTH3._hooks || !OAUTH3._hooks.directives || !OAUTH3._hooks.directives.set) {
 | |
|             console.warn('[Warn] Please implement OAUTH3._hooks.directives.set = function (providerUri, directives) { return PromiseA<directives>; }');
 | |
|             window.localStorage.setItem('directives-' + providerUri, JSON.stringify(directives));
 | |
|             return directives;
 | |
|           }
 | |
|           return OAUTH3._hooks.directives.set(providerUri, directives);
 | |
|         }
 | |
|       }
 | |
|     , session: {
 | |
|         refresh: function (oldSession, newSession) {
 | |
|           var providerUri = oldSession.provider_uri;
 | |
|           var clientUri = oldSession.client_uri;
 | |
| 
 | |
|           Object.keys(['access_token', 'token', 'client_uri', 'refresh', 'refresh_token', 'expires_in', 'provider_uri', 'scope', 'token_type']).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.axs||[])[0]||{}).appScopedId
 | |
|             || ((oldSession.token.axs||[])[0]||{}).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.axs||[])[0]||{}).appScopedId
 | |
|               || ((oldSession.refresh.axs||[])[0]||{}).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) {
 | |
|           opts = 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);
 | |
|           });
 | |
|         }
 | |
|       , _getCached: function (providerUri, id) {
 | |
|           providerUri = OAUTH3.uri.normalize(providerUri);
 | |
|           if (!OAUTH3.hooks.session._cache) { OAUTH3.hooks.session._cache = {}; }
 | |
|           if (id) {
 | |
|             return OAUTH3.hooks.session._cache[providerUri + id];
 | |
|           }
 | |
|           return OAUTH3.hooks.session._cache[providerUri];
 | |
|         }
 | |
|       , set: function (providerUri, newSession, id) {
 | |
|           if (!providerUri) {
 | |
|             console.error(new Error('no providerUri').stack);
 | |
|             throw new Error("providerUri is not set");
 | |
|           }
 | |
|           providerUri = OAUTH3.uri.normalize(providerUri);
 | |
|           if (!OAUTH3.hooks.session._cache) { OAUTH3.hooks.session._cache = {}; }
 | |
|           OAUTH3.hooks.session._cache[providerUri + (id || newSession.id || newSession.token.id || '')] = newSession;
 | |
|           if (!id) {
 | |
|             OAUTH3.hooks.session._cache[providerUri] = newSession;
 | |
|           }
 | |
|           return OAUTH3.PromiseA.resolve(OAUTH3.hooks.session._set(providerUri, newSession));
 | |
|         }
 | |
|       , get: function (providerUri, id) {
 | |
|           providerUri = OAUTH3.uri.normalize(providerUri);
 | |
|           if (!providerUri) {
 | |
|             throw new Error("providerUri is not set");
 | |
|           }
 | |
| 
 | |
|           return OAUTH3.PromiseA.resolve(
 | |
|             OAUTH3.hooks.session._getCached(providerUri, id) || OAUTH3.hooks.session._get(providerUri, id)
 | |
|           ).then(function (session) {
 | |
|             var s = session || { token: {} };
 | |
|             OAUTH3.hooks.session._cache[providerUri + (id || s.id || s.token.id || '')] = session;
 | |
|             if (!id) {
 | |
|               OAUTH3.hooks.session._cache[providerUri] = session;
 | |
|             }
 | |
|             return session;
 | |
|           });
 | |
|         }
 | |
|       , _get: function (providerUri, id) {
 | |
|           if (!OAUTH3._hooks || !OAUTH3._hooks.sessions || !OAUTH3._hooks.sessions.all) {
 | |
|             console.warn('[Warn] Please implement OAUTH3._hooks.sessions.all = function ([providerUri]) { return PromiseA<sessions>; }');
 | |
|           }
 | |
|           if (!OAUTH3._hooks || !OAUTH3._hooks.sessions || !OAUTH3._hooks.sessions.get) {
 | |
|             console.warn('[Warn] Please implement OAUTH3._hooks.sessions.get = function (providerUri[, id]) { return PromiseA<session>; }');
 | |
|             return JSON.parse(window.sessionStorage.getItem('session-' + providerUri + (id || '')) || 'null');
 | |
|           }
 | |
|           return OAUTH3._hooks.sessions.get(providerUri, id);
 | |
|         }
 | |
|       , _set: function (providerUri, newSession, id) {
 | |
|           if (!OAUTH3._hooks || !OAUTH3._hooks.sessions || !OAUTH3._hooks.sessions.set) {
 | |
|             console.warn('[Warn] Please implement OAUTH3._hooks.sessions.set = function (providerUri, newSession[, id]) { return PromiseA<newSession>; }');
 | |
|             window.sessionStorage.setItem('session-' + providerUri, JSON.stringify(newSession));
 | |
|             window.sessionStorage.setItem('session-' + providerUri + (id || newSession.id || newSession.token.id || ''), JSON.stringify(newSession));
 | |
|             return newSession;
 | |
|           }
 | |
|           return OAUTH3._hooks.sessions.set(providerUri, newSession, id);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   , 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);
 | |
|           directives.api = OAUTH3.url.normalize((directives.api||':hostname').replace(/:hostname/, OAUTH3.uri.normalize(directives.issuer) || OAUTH3.uri.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);
 | |
|       }
 | |
| 
 | |
|       if (!preq.session) {
 | |
|         return fetch();
 | |
|       }
 | |
| 
 | |
|       return OAUTH3.hooks.session.check(preq, opts).then(fetch);
 | |
|     }
 | |
|   , _requestHelper: function (preq, opts) {
 | |
|       /*
 | |
|       if (opts && opts.directives) {
 | |
|         preq.url = OAUTH3.url.resolve(opts.directives.issuer, preq.url);
 | |
|       }
 | |
|       */
 | |
|       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
 | |
|         , scope: opts.scope
 | |
|         , subject: opts.subject
 | |
|         , 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 (directives) {
 | |
|         var prequest = OAUTH3.urls.refreshToken(directives, opts);
 | |
| 
 | |
|         prequest.url = OAUTH3.url.resolve(directives.api, prequest.url);
 | |
|         return OAUTH3.request(prequest/*, { directives: directive }*/).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.session || { provider_uri: providerUri, client_uri: opts.client_uri || opts.clientUri }
 | |
|           , 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));
 | |
|         }
 | |
| 
 | |
|         OAUTH3.hooks.session._cache = {};
 | |
|         return params;
 | |
|       });
 | |
|     }
 | |
| 
 | |
| 
 | |
|     //
 | |
|     // Let the Code Waste begin!!
 | |
|     //
 | |
|   , _browser: {
 | |
|       window: 'undefined' !== typeof window ? window : null
 | |
|       // TODO we don't need to include this if we're using jQuery or angular
 | |
|     , discover: function(providerUri, opts) {
 | |
|         opts = opts || {};
 | |
|         providerUri = OAUTH3.url.normalize(providerUri);
 | |
| 
 | |
|         if (OAUTH3.uri.normalize(providerUri).replace(/\/.*/, '') === OAUTH3.uri.normalize(OAUTH3._browser.window.location.hostname)) {
 | |
|           console.warn("It looks like you're a provider checking for your own directive,"
 | |
|             + " 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;
 | |
|           var headers = preq.headers || {};
 | |
|           var multipart;
 | |
| 
 | |
|           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
 | |
|             , headers: null // TODO
 | |
|             , data: data
 | |
|             , status: xhr.status
 | |
|             });
 | |
|           };
 | |
| 
 | |
|           console.log('$$$$$$$$$$$$$$$$$$$$$$$$ preq.progress:');
 | |
|           console.log(preq.progress);
 | |
|           if (preq.progress) {
 | |
|             xhr.upload.onprogress = function (ev) {
 | |
|               console.log('[progress]');
 | |
|               console.log(ev);
 | |
|               preq.progress({
 | |
|                 loaded: ev.loaded
 | |
|               , total: ev.total
 | |
|               });
 | |
|               if (OAUTH3._digest) {
 | |
|                 // $rootScope.$digest();
 | |
|                 OAUTH3._digest();
 | |
|               }
 | |
|             };
 | |
|           }
 | |
|           xhr.open(preq.method || 'GET', preq.url, true);
 | |
| 
 | |
|           if (preq.data) {
 | |
|             headers['Content-Type'] = 'application/json'; // TODO XXX TODO utf8
 | |
|           }
 | |
|           Object.keys(headers).forEach(function (key) {
 | |
|             xhr.setRequestHeader(key, headers[key]);
 | |
|           });
 | |
|           if (preq.multipart && !(preq.multipart instanceof window.FormData)) {
 | |
|             multipart = new window.FormData();
 | |
|             Object.keys(preq.multipart).forEach(function (key) {
 | |
|               multipart.append(key, preq.multipart[key]);
 | |
|             });
 | |
|           }
 | |
|           else {
 | |
|             multipart = preq.multipart;
 | |
|           }
 | |
| 
 | |
|           if (multipart) {
 | |
|             xhr.send(multipart);
 | |
|           }
 | |
|           else {
 | |
|             xhr.send(JSON.stringify(preq.data));
 | |
|           }
 | |
|         });
 | |
|       }
 | |
|     , 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 = '<iframe class="js-oauth3-iframe" src="' + url + '" ';
 | |
|         if (opts.debug) {
 | |
|           framesrc += ' width="' + (opts.height || 600) + 'px"'
 | |
|             + ' height="' + (opts.width || 720) + 'px"'
 | |
|             + ' style="opacity: 0.8;" frameborder="1"';
 | |
|         }
 | |
|         else {
 | |
|           framesrc += ' width="1px" height="1px" frameborder="0"';
 | |
|         }
 | |
|         framesrc += '></iframe>';
 | |
| 
 | |
|         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)
 | |
|         );
 | |
|       }
 | |
|     }
 | |
|   , api: function (providerUri, opts) {
 | |
|       if (!OAUTH3.api[opts.api]) {
 | |
|         return OAUTH3.PromiseA.reject(new Error("No API for '" + opts.api + "'"));
 | |
|       }
 | |
| 
 | |
|       return OAUTH3.api[opts.api](providerUri, opts);
 | |
|     }
 | |
|   , _pkgs: {}
 | |
|   , pkg: function (providerUri, pkgname, method, opts) {
 | |
|       if (!OAUTH3._pkgs[pkgname]) {
 | |
|         return OAUTH3.PromiseA.reject(new Error("No Package for '" + pkgname + "'"));
 | |
|       }
 | |
| 
 | |
|       if (!OAUTH3._pkgs[pkgname][method]) {
 | |
|         return OAUTH3.PromiseA.reject(new Error("No method '" + method + "' in package '" + pkgname + "'"));
 | |
|       }
 | |
|       // opts = { audience: providerUri }
 | |
|       return OAUTH3._pkgs[pkgname][method](opts);
 | |
|     }
 | |
|   };
 | |
|   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;
 | |
|     }
 | |
| 
 | |
|     var result = {
 | |
|       _clientUri: OAUTH3.clientUri(location)
 | |
|     , _identityProviderUri: null
 | |
|     , _resourceProviderUri: null
 | |
|     , _identityProviderDirectives: null
 | |
|     , _resourceProviderDirectives: null
 | |
|     //, _resourceProviderMap: null // map between xyz.com and org.oauth3.domains
 | |
|     , _init: function (location, opts) {
 | |
|         var me = this;
 | |
|         if (!opts) {
 | |
|           opts = location;
 | |
|         }
 | |
|         if (location && location.location) {
 | |
|           location = location.location;
 | |
|         }
 | |
|         if (opts && opts.location) {
 | |
|           me._clientUri = OAUTH3.clientUri(opts.location);
 | |
|         }
 | |
|         if (location && (location.host || location.hostname)) {
 | |
|           me._clientUri = OAUTH3.clientUri(location);
 | |
|         }
 | |
|         if (opts) {
 | |
|           if (opts.providerUri) {
 | |
|             me._identityProviderUri = opts.providerUri;
 | |
|             me._resourceProviderUri = opts.providerUri;
 | |
|           }
 | |
|           if (opts.issuer || opts.identityProviderUri) {
 | |
|             me._identityProviderUri = opts.issuer || opts.identityProviderUri;
 | |
|           }
 | |
|           if (opts.audience || opts.resourceProviderUri) {
 | |
|             me._resourceProviderUri = opts.audience || opts.resourceProviderUri;
 | |
|           }
 | |
|           if (opts.session) {
 | |
|             if (!me._identityProviderUri) {
 | |
|               throw new Error("'providerUri' was not supplied");
 | |
|             }
 | |
|             opts.session.provider_uri = me._identityProviderUri;
 | |
|             opts.session.client_uri = me._clientUri;
 | |
|             me.session(opts.session, opts.sessionId);
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     , _initClient: function (location/*, opts*/) {
 | |
|         var me = this;
 | |
|         return OAUTH3.discover(me._clientUri, { client_id: me._clientUri }).then(function (clientDirectives) {
 | |
|           me._clientDirectives = clientDirectives;
 | |
|           return clientDirectives;
 | |
|         });
 | |
|       }
 | |
|     , init: function (location/*, opts*/) {
 | |
|         var me = this;
 | |
|         var p1 = OAUTH3.PromiseA.resolve();
 | |
|         var p2 = OAUTH3.PromiseA.resolve();
 | |
| 
 | |
|         me._init(location, opts);
 | |
| 
 | |
|         if (me._identityProviderUri) {
 | |
|           // returns directives
 | |
|           p1 = me.setIssuer(me._identityProviderUri);
 | |
|         }
 | |
|         if (me._resourceProviderUri) {
 | |
|           // returns directives
 | |
|           p2 = me.setAudience(me._resourceProviderUri);
 | |
|         }
 | |
| 
 | |
|         return p1.then(function () {
 | |
|           return p2.then(function () {
 | |
|             return me._initClient();
 | |
|           });
 | |
|         });
 | |
|       }
 | |
|     , setProvider: function (providerUri) {
 | |
|         var me = this;
 | |
|         return me._initClient().then(function () {
 | |
|           return me.setIdentityProvider(providerUri).then(function () {
 | |
|             // TODO how to say "Use xyz.com for org.oauth3.domains, but abc.com for org.oauth3.dns"?
 | |
|             return me.setResourceProvider(providerUri);
 | |
|           });
 | |
|         });
 | |
|       }
 | |
|     , setIdentityProvider: function (providerUri) {
 | |
|         var me = this;
 | |
|         me._identityProviderUri = providerUri;
 | |
|         return me._initClient().then(function () {
 | |
|           // this should be synchronous the second time around
 | |
|           return OAUTH3.discover(me._identityProviderUri, { client_id: me._clientUri }).then(function (directives) {
 | |
|             me._identityProviderDirectives = directives;
 | |
|             return directives;
 | |
|           });
 | |
|         });
 | |
|       }
 | |
|     , setResourceProvider: function (providerUri) {
 | |
|         var me = this;
 | |
|         me._resourceProviderUri = providerUri;
 | |
|         return me._initClient().then(function () {
 | |
|           // this should be synchronous the second time around
 | |
|           return OAUTH3.discover(me._resourceProviderUri, { client_id: me._clientUri }).then(function (directives) {
 | |
|             me._resourceProviderDirectives = directives;
 | |
|             return directives;
 | |
|           });
 | |
|         });
 | |
|       }
 | |
|     , checkSession: function () {
 | |
|         return OAUTH3.hooks.session.get(this._identityProviderUri);
 | |
|       }
 | |
|     , login: function (opts) {
 | |
|         var me = this;
 | |
|         if (me.session()) {
 | |
|           me._session = true;
 | |
|           return OAUTH3.PromiseA.resolve(me.session());
 | |
|         }
 | |
| 
 | |
|         opts = opts || {};
 | |
|         opts.client_uri = me._clientUri;
 | |
| 
 | |
|         return OAUTH3.implicitGrant(me._identityProviderDirectives, opts).then(function (session) {
 | |
|           me._session = true;
 | |
|           return session;
 | |
|         });
 | |
|       }
 | |
|     , session: function (session, id) {
 | |
|         if (!session) {
 | |
|           return JSON.parse(JSON.stringify(OAUTH3.hooks.session._getCached(this._identityProviderUri) || null));
 | |
|         }
 | |
|         return OAUTH3.hooks.session.set(this._identityProviderUri, session, id);
 | |
|       }
 | |
|     , request: function (preq, opts) {
 | |
|         opts = opts || {};
 | |
|         preq.client_uri = this._clientUri;
 | |
|         preq.client_id = this._clientUri;
 | |
|         preq.method = preq.method || 'GET';
 | |
|         if (this._session) {
 | |
|           preq.session = preq.session || this.session(); // OAUTH3.hooks.session._getCached(this._identityProviderUri);
 | |
|         }
 | |
|         // TODO maybe use a baseUrl from the directives file?
 | |
|         preq.url = OAUTH3.url.resolve(this._resourceProviderUri, preq.url);
 | |
| 
 | |
|         return OAUTH3.request(preq, opts);
 | |
|       }
 | |
|     , logout: function (opts) {
 | |
|         this._session = false;
 | |
|         opts = opts || {};
 | |
|         opts.client_uri = this._clientUri;
 | |
|         opts.client_id = this._clientUri;
 | |
|         opts.session = OAUTH3.hooks.session._getCached(this._identityProviderUri);
 | |
| 
 | |
|         return OAUTH3.logout(this._identityProviderUri, opts);
 | |
|       }
 | |
|     , api: function (api, opts) {
 | |
|         opts = opts || {};
 | |
|         opts.api = api;
 | |
|         opts.session = OAUTH3.hooks.session._getCached(this._identityProviderUri);
 | |
| 
 | |
|         return OAUTH3.api(this._resourceProviderDirectives.api, opts);
 | |
|       }
 | |
|     , pkg: function (pkgname) {
 | |
|         var me = this;
 | |
|         var issuer = me._identityProviderUri;
 | |
|         var audience = me._resourceProviderDirectives.api;
 | |
|         var pkg;
 | |
|         var result = {};
 | |
| 
 | |
|         // TODO dynamically load package? https://publisher.com/.well-known/packages.js@oauth3.org/pkg@publisher.com.json
 | |
|         if (!OAUTH3._pkgs[pkgname]) {
 | |
|           return OAUTH3.PromiseA.reject(new Error("No Package for '" + pkgname + "'"));
 | |
|         }
 | |
| 
 | |
|         pkg = OAUTH3._pkgs[pkgname];
 | |
|         Object.keys(pkg).forEach(function (key) {
 | |
|           result[key] = function (opts) {
 | |
|             opts = opts || {};
 | |
|             opts.session = OAUTH3.hooks.session._getCached(issuer);
 | |
|             opts.audience = audience;
 | |
|             return pkg[key](opts);
 | |
|           };
 | |
|         });
 | |
| 
 | |
|         return result;
 | |
|       }
 | |
|     };
 | |
|     result.setIssuer = result.setIdentityProvider;
 | |
|     result.setAudience = result.setResourceProvider;
 | |
|     result.authenticate = result.login;
 | |
|     result.authorize = result.login;
 | |
|     result.expire = result.logout;
 | |
| 
 | |
|     result._init(location, opts);
 | |
| 
 | |
|     return result;
 | |
|   };
 | |
| 
 | |
| }('undefined' !== typeof exports ? exports : window));
 |