WIP implicit-grant-only in a single file
This commit is contained in:
		
							parent
							
								
									4657fcdb12
								
							
						
					
					
						commit
						bbc557c349
					
				
							
								
								
									
										276
									
								
								oauth3.implicit.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										276
									
								
								oauth3.implicit.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,276 @@ | |||||||
|  | /* global Promise */ | ||||||
|  | ;(function (exports) { | ||||||
|  |   'use strict'; | ||||||
|  | 
 | ||||||
|  |   var OAUTH3 = exports.OAUTH3 = { | ||||||
|  |     utils: { | ||||||
|  |       atob: function (base64) { | ||||||
|  |         return (exports.atob || require('atob'))(base64); | ||||||
|  |       } | ||||||
|  |     , urlSafeBase64ToBase64: 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 b64; | ||||||
|  |       } | ||||||
|  |     , uri: { | ||||||
|  |         normalize: function (uri) { | ||||||
|  |           // tested with
 | ||||||
|  |           //   example.com
 | ||||||
|  |           //   example.com/
 | ||||||
|  |           //   http://example.com
 | ||||||
|  |           //   https://example.com/
 | ||||||
|  |           return uri | ||||||
|  |             .replace(/^(https?:\/\/)?/i, '') | ||||||
|  |             .replace(/\/?$/, '') | ||||||
|  |             ; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     , url: { | ||||||
|  |         normalize: function (url) { | ||||||
|  |           // tested with
 | ||||||
|  |           //   example.com
 | ||||||
|  |           //   example.com/
 | ||||||
|  |           //   http://example.com
 | ||||||
|  |           //   https://example.com/
 | ||||||
|  |           return url | ||||||
|  |             .replace(/^(https?:\/\/)?/i, 'https://') | ||||||
|  |             .replace(/\/?$/, '') | ||||||
|  |             ; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       , getDefaultAppUrl: function () { | ||||||
|  |         console.warn('[deprecated] using window.location.{protocol, host, pathname} when opts.client_id should be used'); | ||||||
|  |         return window.location.protocol | ||||||
|  |           + '//' + window.location.host | ||||||
|  |           + (window.location.pathname).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.utils.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; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   , 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.utils.url.normalize(opts.client_id || opts.client_uri); | ||||||
|  |         providerUri = OAUTH3.utils.url.normalize(providerUri); | ||||||
|  | 
 | ||||||
|  |         var params = { | ||||||
|  |           action: 'directives' | ||||||
|  |         , 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.utils.query.stringify(params) | ||||||
|  |         , state: params.state | ||||||
|  |         , method: 'GET' | ||||||
|  |         , query: params | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         return result; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   , hooks: { | ||||||
|  |       directives: { | ||||||
|  |         get: function (providerUri) { | ||||||
|  |           providerUri = OAUTH3.utils.uri.normalize(providerUri); | ||||||
|  |           console.warn('[Warn] You should implement: OAUTH3.hooks.directives.get = function (providerUri) { return directives; }'); | ||||||
|  |           if (!OAUTH3.hooks.directives._cache) { OAUTH3.hooks.directives._cache = {}; } | ||||||
|  |           return JSON.parse(window.localStorage.getItem('directives-' + providerUri) || '{}'); | ||||||
|  |         } | ||||||
|  |       , set: function (providerUri, directives) { | ||||||
|  |           providerUri = OAUTH3.utils.uri.normalize(providerUri); | ||||||
|  |           console.warn('[Warn] You should implement: OAUTH3.hooks.directives.set = function (providerUri, directives) { return directives; }'); | ||||||
|  |           console.warn(directives); | ||||||
|  |           if (!OAUTH3.hooks.directives._cache) { OAUTH3.hooks.directives._cache = {}; } | ||||||
|  |           window.localStorage.setItem('directives-' + providerUri, JSON.stringify(directives)); | ||||||
|  |           OAUTH3.hooks.directives._cache[providerUri] = directives; | ||||||
|  |           return directives; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   , discover: function (providerUri, opts) { | ||||||
|  |       if (!providerUri) { | ||||||
|  |         throw new Error('oauth3.discover(providerUri, opts) received providerUri as ' + providerUri); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       return OAUTH3.PromiseA.resolve(OAUTH3.hooks.directives.get(providerUri)).then(function (directives) { | ||||||
|  |         if (directives && directives.issuer) { | ||||||
|  |           return OAUTH3.PromiseA.resolve(directives); | ||||||
|  |         } | ||||||
|  |         return OAUTH3._discoverHelper(providerUri, opts).then(function (directives) { | ||||||
|  |           directives.issuer = directives.issuer || OAUTH3.utils.url.normalize(providerUri); | ||||||
|  |           console.log('discoverHelper', directives); | ||||||
|  |           // OAUTH3.PromiseA.resolve() is taken care of because this is wrapped
 | ||||||
|  |           return OAUTH3.hooks.directives.set(providerUri, directives); | ||||||
|  |         }); | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |     // this is the browser version
 | ||||||
|  |   , _discoverHelper: function (providerUri, opts) { | ||||||
|  |       return OAUTH3._browser.discover(providerUri, opts); | ||||||
|  |     } | ||||||
|  |   , _browser: { | ||||||
|  |       discover: function (providerUri, opts) { | ||||||
|  |         opts = opts || {}; | ||||||
|  |         //opts.debug = true;
 | ||||||
|  |         providerUri = OAUTH3.utils.url.normalize(providerUri); | ||||||
|  |         if (window.location.hostname.match(providerUri)) { | ||||||
|  |           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.utils.url.normalize(providerUri) + '/.well-known/oauth3/directives.json' | ||||||
|  |           }); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (!window.location.hostname.match(opts.client_id || opts.client_uri)) { | ||||||
|  |           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, window.location.hostname); | ||||||
|  |         } | ||||||
|  |         var discObj = OAUTH3.urls.discover( | ||||||
|  |           providerUri | ||||||
|  |         , { client_id: (opts.client_id || opts.client_uri || OAUTH3.utils.getDefaultAppUrl()), debug: opts.debug } | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         // TODO ability to reuse iframe instead of closing
 | ||||||
|  |         return OAUTH3._browser.iframe.insert(discObj.url, discObj.state, opts).then(function (params) { | ||||||
|  |           OAUTH3._browser.iframe.remove(discObj.state); | ||||||
|  |           if (params.error) { | ||||||
|  |             return OAUTH3.utils._formatError(providerUri, params.error); | ||||||
|  |           } | ||||||
|  |           var directives = JSON.parse(OAUTH3.utils.atob(OAUTH3.utils.urlSafeBase64ToBase64(params.result || params.directives))); | ||||||
|  |           return directives; | ||||||
|  |         }, function (err) { | ||||||
|  |           OAUTH3._browser.iframe.remove(discObj.state); | ||||||
|  |           return OAUTH3.PromiseA.reject(err); | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |     , iframe: { | ||||||
|  |         _frames: {} | ||||||
|  |       , insert: function (url, state, opts) { | ||||||
|  |           opts = opts || {}; | ||||||
|  |           if (opts.debug) { | ||||||
|  |             opts.timeout = opts.timeout || 15 * 60 * 1000; | ||||||
|  |           } | ||||||
|  |           var promise = new OAUTH3.PromiseA(function (resolve, reject) { | ||||||
|  |             var tok; | ||||||
|  | 
 | ||||||
|  |             function cleanup() { | ||||||
|  |               delete window['--oauth3-callback-' + state]; | ||||||
|  |               clearTimeout(tok); | ||||||
|  |               tok = null; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             window['--oauth3-callback-' + state] = function (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 || 15 * 1000); | ||||||
|  | 
 | ||||||
|  |             // TODO hidden / non-hidden (via directive even)
 | ||||||
|  |             var framesrc = '<iframe class="js-oauth3-iframe" src="' + url + '" '; | ||||||
|  |             if (opts.debug) { | ||||||
|  |               framesrc += ' width="800px" height="800px" style="opacity: 0.8;" frameborder="1"'; | ||||||
|  |             } | ||||||
|  |             else { | ||||||
|  |               framesrc += ' width="1px" height="1px" frameborder="0"'; | ||||||
|  |             } | ||||||
|  |             framesrc += '></iframe>'; | ||||||
|  | 
 | ||||||
|  |             OAUTH3._browser.iframe._frames[state] = window.document.createElement('div'); | ||||||
|  |             OAUTH3._browser.iframe._frames[state].innerHTML = framesrc; | ||||||
|  | 
 | ||||||
|  |             window.document.body.appendChild(OAUTH3._browser.iframe._frames[state]); | ||||||
|  |           }); | ||||||
|  | 
 | ||||||
|  |           // TODO periodically garbage collect expired handlers from window object
 | ||||||
|  |           return promise; | ||||||
|  |         } | ||||||
|  |       , remove: function (state) { | ||||||
|  |           if (OAUTH3._browser.iframe._frames[state]) { | ||||||
|  |             OAUTH3._browser.iframe._frames[state].remove(); | ||||||
|  |           } | ||||||
|  |           delete OAUTH3._browser.iframe._frames[state]; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |   if ('undefined' !== typeof Promise) { | ||||||
|  |     OAUTH3.PromiseA = Promise; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | }('undefined' !== typeof exports ? exports : window)); | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user