739 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			739 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| (function (exports) {
 | |
|   'use strict';
 | |
| 
 | |
|   var oauth3 = {};
 | |
|   var logins = {};
 | |
|   oauth3.requests = logins;
 | |
| 
 | |
|   if ('undefined' !== typeof Promise) {
 | |
|     oauth3.PromiseA = Promise;
 | |
|   } else {
 | |
|     console.warn("[oauth3.js] Remember to call oauth3.providePromise(Promise) with a proper Promise implementation");
 | |
|   }
 | |
| 
 | |
|   // TODO move to a test / lint suite?
 | |
|   oauth3._testPromise = function (PromiseA) {
 | |
|     var promise;
 | |
|     var x = 1;
 | |
| 
 | |
|     // tests that this promise has all of the necessary api
 | |
|     promise = new PromiseA(function (resolve, reject) {
 | |
|       if (x === 1) {
 | |
|         throw new Error("bad promise, create not asynchronous");
 | |
|       }
 | |
| 
 | |
|       PromiseA.resolve().then(function () {
 | |
|         var promise2;
 | |
| 
 | |
|         if (x === 1 || x === 2) {
 | |
|           throw new Error("bad promise, resolve not asynchronous");
 | |
|         }
 | |
| 
 | |
|         promise2 = PromiseA.reject().then(reject, function () {
 | |
|           if (x === 1 || x === 2 || x === 3) {
 | |
|             throw new Error("bad promise, reject not asynchronous");
 | |
|           }
 | |
| 
 | |
|           if ('undefined' === typeof angular) {
 | |
|             throw new Error("[NOT AN ERROR] Dear angular users: ignore this error-handling test");
 | |
|           } else {
 | |
|             return PromiseA.reject(new Error("[NOT AN ERROR] ignore this error-handling test"));
 | |
|           }
 | |
|         });
 | |
| 
 | |
|         x = 4;
 | |
| 
 | |
|         return promise2;
 | |
|       }).catch(function (e) {
 | |
|         if (e.message.match('NOT AN ERROR')) {
 | |
|           resolve({ success: true });
 | |
|         } else {
 | |
|           reject(e);
 | |
|         }
 | |
|       });
 | |
| 
 | |
|       x = 3;
 | |
|     });
 | |
| 
 | |
|     x = 2;
 | |
|     return promise;
 | |
|   };
 | |
| 
 | |
|   oauth3.providePromise = function (PromiseA) {
 | |
|     oauth3.PromiseA = PromiseA;
 | |
|     return oauth3._testPromise(PromiseA).then(function () {
 | |
|       oauth3.PromiseA = PromiseA;
 | |
|     });
 | |
|   };
 | |
| 
 | |
|   oauth3.provideRequest = function (request, opts) {
 | |
|     opts = opts || {};
 | |
|     var Recase = exports.Recase || require('recase');
 | |
|     // TODO make insensitive to providing exceptions
 | |
|     var recase = Recase.create({ exceptions: {} });
 | |
| 
 | |
|     if (opts.rawCase) {
 | |
|       oauth3.request = request;
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // Wrap oauth3 api calls in snake_case / camelCase conversion
 | |
|     oauth3.request = function (req, opts) {
 | |
|       //console.log('[D] [oauth3 req.url]', req.url);
 | |
|       opts = opts || {};
 | |
| 
 | |
|       if (opts.rawCase) {
 | |
|         return request(req);
 | |
|       }
 | |
| 
 | |
|       // convert JavaScript camelCase to oauth3 snake_case
 | |
|       if (req.data && 'object' === typeof req.data) {
 | |
|         req.originalData = req.data;
 | |
|         req.data = recase.snakeCopy(req.data);
 | |
|       }
 | |
| 
 | |
|       //console.log('[F] [oauth3 req.url]', req.url);
 | |
|       return request(req).then(function (resp) {
 | |
|         // convert oauth3 snake_case to JavaScript camelCase
 | |
|         if (resp.data && 'object' === typeof resp.data) {
 | |
|           resp.originalData = resp.data;
 | |
|           resp.data = recase.camelCopy(resp.data);
 | |
|         }
 | |
|         return resp;
 | |
|       });
 | |
|     };
 | |
| 
 | |
|     /*
 | |
|     return oauth3._testRequest(request).then(function () {
 | |
|       oauth3.request = request;
 | |
|     });
 | |
|     */
 | |
|   };
 | |
| 
 | |
|   logins.authorizationRedirect = function (providerUri, opts) {
 | |
|     // TODO get own directives
 | |
|     return oauth3.authorizationRedirect(
 | |
|       providerUri
 | |
|     , opts.authorizationRedirect
 | |
|     , opts
 | |
|     ).then(function (prequest) {
 | |
|       if (!prequest.state) {
 | |
|         throw new Error("[Devolper Error] [authorization redirect] prequest.state is empty");
 | |
|       }
 | |
| 
 | |
|       return oauth3.frameRequest(prequest.url, prequest.state, opts);
 | |
|     });
 | |
|   };
 | |
| 
 | |
|   logins.implicitGrant = function (providerUri, opts) {
 | |
|     // TODO OAuth3 provider should use the redirect URI as the appId?
 | |
|     return oauth3.implicitGrant(
 | |
|       providerUri
 | |
|       // TODO OAuth3 provider should referer / origin as the appId?
 | |
|     , opts
 | |
|     ).then(function (prequest) {
 | |
|       // console.log('[debug] prequest', prequest);
 | |
|       if (!prequest.state) {
 | |
|         throw new Error("[Devolper Error] [implicit grant] prequest.state is empty");
 | |
|       }
 | |
| 
 | |
|       return oauth3.frameRequest(prequest.url, prequest.state, opts);
 | |
|     });
 | |
|   };
 | |
| 
 | |
|   logins.resourceOwnerPassword = function (providerUri, username, passphrase, opts) {
 | |
|     console.log('DEBUG logins.resourceOwnerPassword opts', opts);
 | |
|     //var scope = opts.scope;
 | |
|     //var appId = opts.appId;
 | |
|     return oauth3.resourceOwnerPassword(
 | |
|       providerUri
 | |
|     , username
 | |
|     , passphrase
 | |
|     , opts
 | |
|     //, scope
 | |
|     //, appId
 | |
|     ).then(function (request) {
 | |
|       console.log('DEBUG oauth3.resourceOwnerPassword', request);
 | |
|       return oauth3.request({
 | |
|         url: request.url
 | |
|       , method: request.method
 | |
|       , data: request.data
 | |
|       });
 | |
|     });
 | |
|   };
 | |
| 
 | |
|   oauth3.frameRequest = function (url, state, opts) {
 | |
|     var promise;
 | |
| 
 | |
|     if ('background' === opts.type) {
 | |
|       promise = oauth3.insertIframe(url, state, opts);
 | |
|     } else if ('popup' === opts.type) {
 | |
|       promise = oauth3.openWindow(url, state, opts);
 | |
|     } else {
 | |
|       throw new Error("login framing method not specified or not type yet implemented");
 | |
|     }
 | |
| 
 | |
|     return promise.then(function (params) {
 | |
|       var err;
 | |
| 
 | |
|       if (params.error || params.error_description) {
 | |
|         err = new Error(params.error_description || "Unknown response error");
 | |
|         err.code = params.error || "E_UKNOWN_ERROR";
 | |
|         err.params = params;
 | |
|         return oauth3.PromiseA.reject(err);
 | |
|       }
 | |
| 
 | |
|       return params;
 | |
|     });
 | |
|   };
 | |
| 
 | |
|   oauth3.login = function (providerUri, opts) {
 | |
|     console.log('##### DEBUG oauth3.login providerUri, opts');
 | |
|     console.log(providerUri);
 | |
|     console.log(opts);
 | |
|     // Four styles of login:
 | |
|     //   * background (hidden iframe)
 | |
|     //   * iframe (visible iframe, needs border color and width x height params)
 | |
|     //   * popup (needs width x height and positioning? params)
 | |
|     //   * window (params?)
 | |
| 
 | |
|     // Two strategies
 | |
|     //  * authorization_redirect (to server authorization code)
 | |
|     //  * implicit_grant (default, browser-only)
 | |
|     // If both are selected, implicit happens first and then the other happens in background
 | |
| 
 | |
|     var promise;
 | |
| 
 | |
|     if (opts.username || opts.password) {
 | |
|       /* jshint ignore:start */
 | |
|       // ingore "confusing use of !"
 | |
|       if (!opts.username !== !opts.password) {
 | |
|         throw new Error("you did not specify both username and password");
 | |
|       }
 | |
|       /* jshint ignore:end */
 | |
| 
 | |
|       var username = opts.username;
 | |
|       var password = opts.password;
 | |
|       delete opts.username;
 | |
|       delete opts.password;
 | |
| 
 | |
|       return logins.resourceOwnerPassword(providerUri, username, password, opts).then(function (resp) {
 | |
|         if (!resp || !resp.data) {
 | |
|           var err = new Error("bad response");
 | |
|           err.response = resp;
 | |
|           err.data = resp && resp.data || undefined;
 | |
|           return oauth3.PromiseA.reject(err);
 | |
|         }
 | |
|         return resp.data;
 | |
|       });
 | |
|     }
 | |
| 
 | |
|     // TODO support dual-strategy login
 | |
|     // by default, always get implicitGrant (for client)
 | |
|     // and optionally do authorizationCode (for server session)
 | |
|     if ('background' === opts.type || opts.background) {
 | |
|       opts.type = 'background';
 | |
|       opts.background = true;
 | |
|     }
 | |
|     else {
 | |
|       opts.type = 'popup';
 | |
|       opts.popup = true;
 | |
|     }
 | |
|     if (opts.authorizationRedirect) {
 | |
|       promise = logins.authorizationRedirect(providerUri, opts);
 | |
|     }
 | |
|     else {
 | |
|       promise = logins.implicitGrant(providerUri, opts);
 | |
|     }
 | |
| 
 | |
|     return promise;
 | |
|   };
 | |
| 
 | |
|   oauth3.backgroundLogin = function (providerUri, opts) {
 | |
|     opts = opts || {};
 | |
|     opts.type = 'background';
 | |
|     return oauth3.login(providerUri, opts);
 | |
|   };
 | |
| 
 | |
|   oauth3.insertIframe = function (url, state, opts) {
 | |
|     opts = opts || {};
 | |
|     var promise = new oauth3.PromiseA(function (resolve, reject) {
 | |
|       var tok;
 | |
|       var $iframe;
 | |
| 
 | |
|       function cleanup() {
 | |
|         delete window['__oauth3_' + state];
 | |
|         $iframe.remove();
 | |
|         clearTimeout(tok);
 | |
|         tok = null;
 | |
|       }
 | |
| 
 | |
|       window['__oauth3_' + state] = function (params) {
 | |
|         //console.info('[iframe] complete', params);
 | |
|         resolve(params);
 | |
|         cleanup();
 | |
|       };
 | |
| 
 | |
|       tok = setTimeout(function () {
 | |
|         var err = new Error("the iframe request did not complete within 15 seconds");
 | |
|         err.code = "E_TIMEOUT";
 | |
|         reject(err);
 | |
|         cleanup();
 | |
|       }, opts.timeout || 15000);
 | |
| 
 | |
|       // TODO hidden / non-hidden (via directive even)
 | |
|       $iframe = $(
 | |
|         '<iframe src="' + url
 | |
|       //+ '" width="800px" height="800px" style="opacity: 0.8;" frameborder="1"></iframe>'
 | |
|       + '" width="1px" height="1px" frameborder="0"></iframe>'
 | |
|       );
 | |
| 
 | |
|       $('body').append($iframe);
 | |
|     });
 | |
| 
 | |
|     // TODO periodically garbage collect expired handlers from window object
 | |
|     return promise;
 | |
|   };
 | |
| 
 | |
|   oauth3.openWindow = function (url, state, opts) {
 | |
|     var promise = new oauth3.PromiseA(function (resolve, reject) {
 | |
|       var winref;
 | |
|       var tok;
 | |
| 
 | |
|       function cleanup() {
 | |
|         delete window['__oauth3_' + state];
 | |
|         clearTimeout(tok);
 | |
|         tok = null;
 | |
|         // this is last in case the window self-closes synchronously
 | |
|         // (should never happen, but that's a negotiable implementation detail)
 | |
|         //winref.close();
 | |
|       }
 | |
| 
 | |
|       window['__oauth3_' + state] = function (params) {
 | |
|         //console.info('[popup] (or window) complete', params);
 | |
|         resolve(params);
 | |
|         cleanup();
 | |
|       };
 | |
| 
 | |
|       tok = setTimeout(function () {
 | |
|         var err = new Error("the windowed request did not complete within 3 minutes");
 | |
|         err.code = "E_TIMEOUT";
 | |
|         reject(err);
 | |
|         cleanup();
 | |
|       }, opts.timeout || 3 * 60 * 1000);
 | |
| 
 | |
|       // TODO allow size changes (via directive even)
 | |
|       winref = window.open(url, 'oauth3-login-' + state, 'height=720,width=620');
 | |
|       if (!winref) {
 | |
|         reject("TODO: open the iframe first and discover oauth3 directives before popup");
 | |
|         cleanup();
 | |
|       }
 | |
|     });
 | |
| 
 | |
|     // TODO periodically garbage collect expired handlers from window object
 | |
|     return promise;
 | |
|   };
 | |
| 
 | |
|   oauth3.logout = function (providerUri, opts) {
 | |
|     opts = opts || {};
 | |
| 
 | |
|     // Oauth3.init({ logout: function () {} });
 | |
|     //return Oauth3.logout();
 | |
| 
 | |
|     var state = parseInt(Math.random().toString().replace('0.', ''), 10).toString('36');
 | |
|     var url = providerUri.replace(/\/$/, '') + (opts.providerOauth3Html || '/oauth3.html');
 | |
|     var redirectUri = opts.redirectUri
 | |
|       || (window.location.protocol + '//' + (window.location.host + window.location.pathname) + 'oauth3.html')
 | |
|       ;
 | |
|     var params = {
 | |
|       // logout=true for all logins/accounts
 | |
|       // logout=app-scoped-login-id for a single login
 | |
|       action: 'logout'
 | |
|       // TODO specify specific accounts / logins to delete from session
 | |
|     , accounts: true
 | |
|     , logins: true
 | |
|     , redirect_uri: redirectUri
 | |
|     , state: state
 | |
|     };
 | |
| 
 | |
|     //console.log('DEBUG oauth3.logout NIX insertIframe');
 | |
|     //console.log(url, params.redirect_uri);
 | |
|     //console.log(state);
 | |
|     //console.log(params); // redirect_uri
 | |
|     //console.log(opts);
 | |
| 
 | |
|     if (url === params.redirect_uri) {
 | |
|       return oauth3.PromiseA.resolve();
 | |
|     }
 | |
| 
 | |
|     url += '#' + oauth3.querystringify(params);
 | |
| 
 | |
|     return oauth3.insertIframe(url, state, opts);
 | |
|   };
 | |
| 
 | |
|   oauth3.stringifyscope = function (scope) {
 | |
|     if (Array.isArray(scope)) {
 | |
|       scope = scope.join(' ');
 | |
|     }
 | |
|     return scope;
 | |
|   };
 | |
| 
 | |
|   oauth3.querystringify = function (params) {
 | |
|     var qs = [];
 | |
| 
 | |
|     Object.keys(params).forEach(function (key) {
 | |
|       if ('scope' === key) {
 | |
|         params[key] = oauth3.stringifyscope(params[key]);
 | |
|       }
 | |
|       qs.push(encodeURIComponent(key) + '=' + encodeURIComponent(params[key]));
 | |
|     });
 | |
| 
 | |
|     return qs.join('&');
 | |
|   };
 | |
| 
 | |
|   oauth3.createState = function () {
 | |
|     // TODO mo' betta' random function
 | |
|     // maybe gather some entropy from mouse / keyboard events?
 | |
|     // (probably not, just use webCrypto or be sucky)
 | |
|     return parseInt(Math.random().toString().replace('0.', ''), 10).toString('36');
 | |
|   };
 | |
| 
 | |
|   oauth3.normalizeProviderUri = function (providerUri) {
 | |
|     // tested with
 | |
|     //   example.com
 | |
|     //   example.com/
 | |
|     //   http://example.com
 | |
|     //   https://example.com/
 | |
|     providerUri = providerUri
 | |
|       .replace(/^(https?:\/\/)?/, 'https://')
 | |
|       .replace(/\/?$/, '')
 | |
|       ;
 | |
| 
 | |
|     return providerUri;
 | |
|   };
 | |
| 
 | |
|   oauth3._discoverHelper = function (providerUri, opts) {
 | |
|     opts = opts || {};
 | |
|     var state = oauth3.createState();
 | |
|     var params;
 | |
|     var url;
 | |
| 
 | |
|     params = {
 | |
|       action: 'directives'
 | |
|     , state: state
 | |
|       // TODO this should be configurable (i.e. I want a dev vs production oauth3.html)
 | |
|     , redirect_uri: window.location.protocol + '//' + window.location.host
 | |
|         + window.location.pathname + 'oauth3.html'
 | |
|     };
 | |
| 
 | |
|     url = providerUri + '/oauth3.html#' + oauth3.querystringify(params);
 | |
| 
 | |
|     return oauth3.insertIframe(url, state, opts).then(function (directives) {
 | |
|       return directives;
 | |
|     }, function (err) {
 | |
|       return oauth3.PromiseA.reject(err);
 | |
|     });
 | |
|   };
 | |
| 
 | |
|   oauth3.discover = function (providerUri, opts) {
 | |
|     opts = opts || {};
 | |
| 
 | |
|     console.log('DEBUG oauth3.discover', providerUri);
 | |
|     console.log(opts);
 | |
|     if (opts.directives) {
 | |
|       return oauth3.PromiseA.resolve(opts.directives);
 | |
|     }
 | |
| 
 | |
|     var promise;
 | |
|     var promise2;
 | |
|     var directives;
 | |
|     var updatedAt;
 | |
|     var fresh;
 | |
| 
 | |
|     providerUri = oauth3.normalizeProviderUri(providerUri);
 | |
|     try {
 | |
|       directives = JSON.parse(localStorage.getItem('oauth3.' + providerUri + '.directives'));
 | |
|       console.log('DEBUG oauth3.discover cache', directives);
 | |
|       updatedAt = localStorage.getItem('oauth3.' + providerUri + '.directives.updated_at');
 | |
|       console.log('DEBUG oauth3.discover updatedAt', updatedAt);
 | |
|       updatedAt = new Date(updatedAt).valueOf();
 | |
|       console.log('DEBUG oauth3.discover updatedAt', updatedAt);
 | |
|     } catch(e) {
 | |
|       // ignore
 | |
|     }
 | |
| 
 | |
|     fresh = (Date.now() - updatedAt) < (24 * 60 * 60 * 1000);
 | |
| 
 | |
|     if (directives) {
 | |
|       promise = oauth3.PromiseA.resolve(directives);
 | |
| 
 | |
|       if (fresh) {
 | |
|         //console.log('[local] [fresh directives]', directives);
 | |
|         return promise;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     promise2 = oauth3._discoverHelper(providerUri, opts).then(function (params) {
 | |
|       console.log('DEBUG oauth3._discoverHelper', params);
 | |
|       var err;
 | |
| 
 | |
|       if (!params.directives) {
 | |
|         err = new Error(params.error_description || "Unknown error when discoving provider '" + providerUri + "'");
 | |
|         err.code = params.error || "E_UNKNOWN_ERROR";
 | |
|         return oauth3.PromiseA.reject(err);
 | |
|       }
 | |
| 
 | |
|       try {
 | |
|         directives = JSON.parse(atob(params.directives));
 | |
|         console.log('DEBUG oauth3._discoverHelper directives', directives);
 | |
|       } catch(e) {
 | |
|         err = new Error(params.error_description || "could not parse directives for provider '" + providerUri + "'");
 | |
|         err.code = params.error || "E_PARSE_DIRECTIVE";
 | |
|         return oauth3.PromiseA.reject(err);
 | |
|       }
 | |
| 
 | |
|       if (
 | |
|           (directives.authorization_dialog && directives.authorization_dialog.url)
 | |
|         || (directives.access_token && directives.access_token.url)
 | |
|       ) {
 | |
|         // TODO lint directives
 | |
|         localStorage.setItem('oauth3.' + providerUri + '.directives', JSON.stringify(directives));
 | |
|         localStorage.setItem('oauth3.' + providerUri + '.directives.updated_at', new Date().toISOString());
 | |
| 
 | |
|         return oauth3.PromiseA.resolve(directives);
 | |
|       } else {
 | |
|         // ignore
 | |
|         console.error("the directives provided by '" + providerUri + "' were invalid.");
 | |
|         params.error = params.error || "E_INVALID_DIRECTIVE";
 | |
|         params.error_description = params.error_description
 | |
|           || "directives did not include authorization_dialog.url";
 | |
|         err = new Error(params.error_description || "Unknown error when discoving provider '" + providerUri + "'");
 | |
|         err.code = params.error;
 | |
|         return oauth3.PromiseA.reject(err);
 | |
|       }
 | |
|     });
 | |
| 
 | |
|     return promise || promise2;
 | |
|   };
 | |
| 
 | |
|   oauth3.authorizationRedirect = function (providerUri, authorizationRedirect, opts) {
 | |
|     //console.log('[authorizationRedirect]');
 | |
|     //
 | |
|     // Example Authorization Redirect - from Browser to Consumer API
 | |
|     // (for generating a session securely on your own server)
 | |
|     //
 | |
|     // i.e. GET https://<<CONSUMER>>.com/api/org.oauth3.consumer/authorization_redirect/<<PROVIDER>>.com
 | |
|     //
 | |
|     // GET https://myapp.com/api/org.oauth3.consumer/authorization_redirect/`encodeURIComponent('example.com')`
 | |
|     //  &scope=`encodeURIComponent('profile.login profile.email')`
 | |
|     //
 | |
|     // (optional)
 | |
|     //  &state=`Math.random()`
 | |
|     //  &redirect_uri=`encodeURIComponent('https://myapp.com/oauth3.html')`
 | |
|     //
 | |
|     // NOTE: This is not a request sent to the provider, but rather a request sent to the
 | |
|     // consumer (your own API) which then sets some state and redirects.
 | |
|     // This will initiate the `authorization_code` request on your server
 | |
|     //
 | |
|     opts = opts || {};
 | |
| 
 | |
|     return oauth3.discover(providerUri, opts).then(function (directive) {
 | |
|       if (!directive) {
 | |
|         throw new Error("Developer Error: directive should exist when discovery is successful");
 | |
|       }
 | |
| 
 | |
|       var scope = opts.scope || directive.authn_scope;
 | |
| 
 | |
|       var state = Math.random().toString().replace(/^0\./, '');
 | |
|       var params = {};
 | |
|       var slimProviderUri = encodeURIComponent(providerUri.replace(/^(https?|spdy):\/\//, ''));
 | |
| 
 | |
|       params.state = state;
 | |
|       if (scope) {
 | |
|         params.scope = scope;
 | |
|       }
 | |
|       if (opts.redirectUri) {
 | |
|         // this is really only for debugging
 | |
|         params.redirect_uri = opts.redirectUri;
 | |
|       }
 | |
|       // Note: the type check is necessary because we allow 'true'
 | |
|       // as an automatic mechanism when it isn't necessary to specify
 | |
|       if ('string' !== typeof authorizationRedirect) {
 | |
|         // TODO oauth3.json for self?
 | |
|         authorizationRedirect = 'https://' + window.location.host
 | |
|           + '/api/org.oauth3.consumer/authorization_redirect/:provider_uri';
 | |
|       }
 | |
|       authorizationRedirect = authorizationRedirect
 | |
|         .replace(/!(provider_uri)/, slimProviderUri)
 | |
|         .replace(/:provider_uri/, slimProviderUri)
 | |
|         .replace(/#{provider_uri}/, slimProviderUri)
 | |
|         .replace(/{{provider_uri}}/, slimProviderUri)
 | |
|         ;
 | |
| 
 | |
|       return oauth3.PromiseA.resolve({
 | |
|         url: authorizationRedirect + '?' + oauth3.querystringify(params)
 | |
|       , method: 'GET'
 | |
|       , state: state    // this becomes browser_state
 | |
|       , params: params  // includes scope, final redirect_uri?
 | |
|       });
 | |
|     });
 | |
|   };
 | |
| 
 | |
|   oauth3.authorizationCode = function (/*providerUri, scope, redirectUri, clientId*/) {
 | |
|     //
 | |
|     // Example Authorization Code Request
 | |
|     // (not for use in the browser)
 | |
|     //
 | |
|     // GET https://example.com/api/org.oauth3.provider/authorization_dialog
 | |
|     //  ?response_type=code
 | |
|     //  &scope=`encodeURIComponent('profile.login profile.email')`
 | |
|     //  &state=`Math.random()`
 | |
|     //  &client_id=xxxxxxxxxxx
 | |
|     //  &redirect_uri=`encodeURIComponent('https://myapp.com/oauth3.html')`
 | |
|     //
 | |
|     // NOTE: `redirect_uri` itself may also contain URI-encoded components
 | |
|     //
 | |
|     // NOTE: This probably shouldn't be done in the browser because the server
 | |
|     //   needs to initiate the state. If it is done in a browser, the browser
 | |
|     //   should probably request 'state' from the server beforehand
 | |
|     //
 | |
| 
 | |
|     throw new Error("not implemented");
 | |
|   };
 | |
| 
 | |
|   oauth3.implicitGrant = function (providerUri, opts) {
 | |
|     //console.log('[implicitGrant]');
 | |
|     //
 | |
|     // 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=`Math.random()`
 | |
|     //  &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';
 | |
| 
 | |
|     return oauth3.discover(providerUri, opts).then(function (directive) {
 | |
|       var redirectUri = opts.redirectUri;
 | |
|       var scope = opts.scope || directive.authn_scope;
 | |
|       var clientId = opts.appId;
 | |
|       var args = directive[type];
 | |
|       var uri = args.url;
 | |
|       var state = Math.random().toString().replace(/^0\./, '');
 | |
|       var params = {};
 | |
|       var loc;
 | |
|       var result;
 | |
| 
 | |
|       params.state = state;
 | |
|       params.response_type = responseType;
 | |
|       if (scope) {
 | |
|         if (Array.isArray(scope)) {
 | |
|           scope = scope.join(' ');
 | |
|         }
 | |
|         params.scope = scope;
 | |
|       }
 | |
|       if (clientId) {
 | |
|         // In OAuth3 client_id is optional for implicit grant
 | |
|         params.client_id = clientId;
 | |
|       }
 | |
|       if (!redirectUri) {
 | |
|         loc = window.location;
 | |
|         redirectUri = loc.protocol + '//' + loc.host + loc.pathname;
 | |
|         if ('/' !== redirectUri[redirectUri.length - 1]) {
 | |
|           redirectUri += '/';
 | |
|         }
 | |
|         redirectUri += 'oauth3.html';
 | |
|       }
 | |
|       params.redirect_uri = redirectUri;
 | |
| 
 | |
|       uri += '?' + oauth3.querystringify(params);
 | |
| 
 | |
|       result = {
 | |
|         url: uri
 | |
|       , state: state
 | |
|       , method: args.method
 | |
|       , query: params
 | |
|       };
 | |
|       return oauth3.PromiseA.resolve(result);
 | |
|     });
 | |
|   };
 | |
| 
 | |
|   oauth3.resourceOwnerPassword = function (providerUri, username, passphrase, opts) {
 | |
|     //
 | |
|     // Example Resource Owner Password Request
 | |
|     // (generally for 1st party and direct-partner mobile apps, and webapps)
 | |
|     //
 | |
|     // POST https://example.com/api/org.oauth3.provider/access_token
 | |
|     //    { "grant_type": "password", "client_id": "<<id>>", "scope": "<<scope>>"
 | |
|     //    , "username": "<<username>>", "password": "password" }
 | |
|     //
 | |
|     opts = opts || {};
 | |
|     var type = 'access_token';
 | |
|     var grantType = 'password';
 | |
| 
 | |
|     return oauth3.discover(providerUri, opts).then(function (directive) {
 | |
|       var scope = opts.scope || directive.authn_scope;
 | |
|       var clientId = opts.appId;
 | |
|       var clientAgreeTos = opts.clientAgreeTos;
 | |
|       var clientUri = opts.clientUri;
 | |
|       var args = directive[type];
 | |
|       var params = {
 | |
|         "grant_type": grantType
 | |
|       , "username": username
 | |
|       , "password": passphrase
 | |
|       //, "totp": opts.totp
 | |
|       };
 | |
|       var uri = args.url;
 | |
|       var body;
 | |
|       if (opts.totp) {
 | |
|         params.totp = opts.totp;
 | |
|       }
 | |
| 
 | |
|       if (clientId) {
 | |
|         params.clientId = clientId;
 | |
|       }
 | |
|       if (clientUri) {
 | |
|         params.clientUri = clientUri;
 | |
|         params.clientAgreeTos = clientAgreeTos;
 | |
|         if (!clientAgreeTos) {
 | |
|           throw new Error('Developer Error: missing clientAgreeTos uri');
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       if (scope) {
 | |
|         if (Array.isArray(scope)) {
 | |
|           scope = scope.join(' ');
 | |
|         }
 | |
|         params.scope = scope;
 | |
|       }
 | |
| 
 | |
|       if ('GET' === args.method.toUpperCase()) {
 | |
|         uri += '?' + oauth3.querystringify(params);
 | |
|       } else {
 | |
|         body = params;
 | |
|       }
 | |
| 
 | |
|       return {
 | |
|         url: uri
 | |
|       , method: args.method
 | |
|       , data: body
 | |
|       };
 | |
|     });
 | |
|   };
 | |
| 
 | |
|   exports.OAUTH3 = oauth3.oauth3 = oauth3.OAUTH3 = oauth3;
 | |
|   exports.oauth3 = exports.OAUTH3;
 | |
| 
 | |
|   if ('undefined' !== typeof module) {
 | |
|     module.exports = oauth3;
 | |
|   }
 | |
| }('undefined' !== typeof exports ? exports : window));
 |