433 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			433 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* global Promise */
 | |
| (function (exports) {
 | |
|   'use strict';
 | |
| 
 | |
|   var oauth3 = {};
 | |
| 
 | |
|   var core = exports.OAUTH3_CORE || require('./oauth3.core.js');
 | |
| 
 | |
|   oauth3.requests = {};
 | |
| 
 | |
|   if ('undefined' !== typeof Promise) {
 | |
|     oauth3.PromiseA = Promise;
 | |
|   } else {
 | |
|     console.warn("[oauth3.js] Remember to call oauth3.providePromise(Promise) with a proper Promise implementation");
 | |
|   }
 | |
| 
 | |
|   oauth3.providePromise = function (PromiseA) {
 | |
|     oauth3.PromiseA = PromiseA;
 | |
|     if (oauth3._testPromise) {
 | |
|       return oauth3._testPromise(PromiseA).then(function () {
 | |
|         oauth3.PromiseA = PromiseA;
 | |
|       });
 | |
|     }
 | |
| 
 | |
|     oauth3.PromiseA = PromiseA;
 | |
|     return PromiseA.resolve();
 | |
|   };
 | |
| 
 | |
|   // TODO move recase out
 | |
|   /*
 | |
|   oauth3._recaseRequest = function (recase, req) {
 | |
|     // convert JavaScript camelCase to oauth3/ruby snake_case
 | |
|     if (req.data && 'object' === typeof req.data) {
 | |
|       req.originalData = req.data;
 | |
|       req.data = recase.snakeCopy(req.data);
 | |
|     }
 | |
| 
 | |
|     return req;
 | |
|   };
 | |
|   oauth3._recaseResponse = function (recase, resp) {
 | |
|     // convert oauth3/ruby snake_case to JavaScript camelCase
 | |
|     if (resp.data && 'object' === typeof resp.data) {
 | |
|       resp.originalData = resp.data;
 | |
|       resp.data = recase.camelCopy(resp.data);
 | |
|     }
 | |
|     return resp;
 | |
|   };
 | |
|   */
 | |
| 
 | |
|   oauth3.hooks = {
 | |
|     checkSession: function (preq, opts) {
 | |
|       if (!preq.session) {
 | |
|         console.warn('[oauth3.hooks.checkSession] no session');
 | |
|         return oauth3.PromiseA.resolve(null);
 | |
|       }
 | |
|       var freshness = oauth3.core.jwt.getFreshness(preq.session.token, opts.staletime);
 | |
|       console.info('[oauth3.hooks.checkSession] freshness', freshness, preq.session);
 | |
| 
 | |
|       switch (freshness) {
 | |
|         case 'stale':
 | |
|           return oauth3.hooks.sessionStale(preq.session);
 | |
|         case 'expired':
 | |
|           return oauth3.hooks.sessionExpired(preq.session).then(function (newSession) {
 | |
|             preq.session = newSession;
 | |
|             return newSession;
 | |
|           });
 | |
|         //case 'fresh':
 | |
|         default:
 | |
|           return oauth3.PromiseA.resolve(preq.session);
 | |
|       }
 | |
|     }
 | |
|   , sessionStale: function (staleSession) {
 | |
|       console.info('[oauth3.hooks.sessionStale] called');
 | |
|       if (oauth3.hooks._stalePromise) {
 | |
|         return oauth3.PromiseA.resolve(staleSession);
 | |
|       }
 | |
| 
 | |
|       oauth3.hooks._stalePromise = oauth3.requests.refreshToken(
 | |
|         staleSession.provider_uri
 | |
|       , { client_uri: staleSession.client_uri
 | |
|         , session: staleSession
 | |
|         , debug: staleSession.debug
 | |
|         }
 | |
|       ).then(function (newSession) {
 | |
|         oauth3.hooks._stalePromise = null;
 | |
|         return newSession; // oauth3.hooks.refreshSession(staleSession, newSession);
 | |
|       }, function () {
 | |
|         oauth3.hooks._stalePromise = null;
 | |
|       });
 | |
| 
 | |
|       return oauth3.PromiseA.resolve(staleSession);
 | |
|     }
 | |
|   , sessionExpired: function (expiredSession) {
 | |
|       console.info('[oauth3.hooks.sessionExpired] called');
 | |
|       return oauth3.requests.refreshToken(
 | |
|         expiredSession.provider_uri
 | |
|       , { client_uri: expiredSession.client_uri
 | |
|         , session: expiredSession
 | |
|         , debug: expiredSession.debug
 | |
|         }
 | |
|       ).then(function (newSession) {
 | |
|         return newSession; // oauth3.hooks.refreshSession(expiredSession, newSession);
 | |
|       });
 | |
|     }
 | |
|   , refreshSession: function (oldSession, newSession) {
 | |
|       var providerUri = oldSession.provider_uri;
 | |
|       var clientUri = oldSession.client_uri;
 | |
| 
 | |
|       console.info('[oauth3.hooks.refreshSession] oldSession', JSON.parse(JSON.stringify(oldSession)));
 | |
|       console.info('[oauth3.hooks.refreshSession] newSession', newSession);
 | |
|       Object.keys(oldSession).forEach(function (key) {
 | |
|         oldSession[key] = undefined;
 | |
|       });
 | |
|       Object.keys(newSession).forEach(function (key) {
 | |
|         oldSession[key] = newSession[key];
 | |
|       });
 | |
| 
 | |
|       // info about the session of this API call
 | |
|       oldSession.provider_uri = providerUri;  // aud
 | |
|       oldSession.client_uri = clientUri;      // azp
 | |
| 
 | |
|       // info about the newly-discovered token
 | |
|       oldSession.token = oldSession.meta = core.jwt.decode(oldSession.access_token).payload;
 | |
| 
 | |
|       oldSession.token.sub = oldSession.token.sub || oldSession.token.acx.id;
 | |
|       oldSession.token.client_uri = clientUri;
 | |
|       oldSession.token.provider_uri = providerUri;
 | |
| 
 | |
|       if (oldSession.refresh_token || oldSession.refreshToken) {
 | |
|         oldSession.refresh = core.jwt.decode(oldSession.refresh_token || oldSession.refreshToken).payload;
 | |
|         oldSession.refresh.sub = oldSession.refresh.sub || oldSession.refresh.acx.id;
 | |
|         oldSession.refresh.provider_uri = providerUri;
 | |
|       }
 | |
| 
 | |
|       console.info('[oauth3.hooks.refreshSession] refreshedSession', oldSession);
 | |
| 
 | |
|       // set for a set of audiences
 | |
|       return oauth3.PromiseA.resolve(oauth3.hooks.setSession(providerUri, oldSession));
 | |
|     }
 | |
|   , setSession: function (providerUri, newSession) {
 | |
|       if (!providerUri) {
 | |
|         console.error(new Error('no providerUri').stack);
 | |
|       }
 | |
|       providerUri = oauth3.core.normalizeUri(providerUri);
 | |
|       console.warn('[oauth3.hooks.setSession] PLEASE IMPLEMENT -- Your Fault');
 | |
|       console.warn(newSession);
 | |
|       if (!oauth3.hooks._sessions) { oauth3.hooks._sessions = {}; }
 | |
|       oauth3.hooks._sessions[providerUri] = newSession;
 | |
|       return newSession;
 | |
|     }
 | |
|   , getSession: function (providerUri) {
 | |
|       providerUri = oauth3.core.normalizeUri(providerUri);
 | |
|       console.warn('[oauth3.hooks.getSession] PLEASE IMPLEMENT -- Your Fault');
 | |
|       if (!oauth3.hooks._sessions) { oauth3.hooks._sessions = {}; }
 | |
|       return oauth3.hooks._sessions[providerUri];
 | |
|     }
 | |
|   , setDirectives: function (providerUri, directives) {
 | |
|       providerUri = oauth3.core.normalizeUri(providerUri);
 | |
|       console.warn('[oauth3.hooks.setDirectives] PLEASE IMPLEMENT -- Your Fault');
 | |
|       console.warn(directives);
 | |
|       if (!oauth3.hooks._directives) { oauth3.hooks._directives = {}; }
 | |
|       window.localStorage.setItem('directives-' + providerUri, JSON.stringify(directives));
 | |
|       oauth3.hooks._directives[providerUri] = directives;
 | |
|       return directives;
 | |
|     }
 | |
|   , getDirectives: function (providerUri) {
 | |
|       providerUri = oauth3.core.normalizeUri(providerUri);
 | |
|       console.warn('[oauth3.hooks.getDirectives] PLEASE IMPLEMENT -- Your Fault');
 | |
|       if (!oauth3.hooks._directives) { oauth3.hooks._directives = {}; }
 | |
|       return JSON.parse(window.localStorage.getItem('directives-' + providerUri) || '{}');
 | |
|       //return oauth3.hooks._directives[providerUri];
 | |
|     }
 | |
| 
 | |
|     // Provider Only
 | |
|   , setGrants: function (clientUri, newGrants) {
 | |
|       clientUri = oauth3.core.normalizeUri(clientUri);
 | |
|       console.warn('[oauth3.hooks.setGrants] PLEASE IMPLEMENT -- Your Fault');
 | |
|       console.warn(newGrants);
 | |
|       if (!oauth3.hooks._grants) { oauth3.hooks._grants = {}; }
 | |
|       console.log('clientUri, newGrants');
 | |
|       console.log(clientUri, newGrants);
 | |
|       oauth3.hooks._grants[clientUri] = newGrants;
 | |
|       return newGrants;
 | |
|     }
 | |
|   , getGrants: function (clientUri) {
 | |
|       clientUri = oauth3.core.normalizeUri(clientUri);
 | |
|       console.warn('[oauth3.hooks.getGrants] PLEASE IMPLEMENT -- Your Fault');
 | |
|       if (!oauth3.hooks._grants) { oauth3.hooks._grants = {}; }
 | |
|       console.log('clientUri, existingGrants');
 | |
|       console.log(clientUri, oauth3.hooks._grants[clientUri]);
 | |
|       return oauth3.hooks._grants[clientUri];
 | |
|     }
 | |
|   };
 | |
| 
 | |
|   // TODO simplify (nix recase)
 | |
|   oauth3.provideRequest = function (rawRequest, opts) {
 | |
|     opts = opts || {};
 | |
|     //var Recase = exports.Recase || require('recase');
 | |
|     // TODO make insensitive to providing exceptions
 | |
|     //var recase = Recase.create({ exceptions: {} });
 | |
| 
 | |
|     function lintAndRequest(preq) {
 | |
|       function goGetHer() {
 | |
|         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);
 | |
|         }
 | |
| 
 | |
|         if (!oauth3._lintRequest) {
 | |
|           return rawRequest(preq);
 | |
|         }
 | |
|         return oauth3._lintRequest(preq, opts).then(function (preq) {
 | |
|           return rawRequest(preq);
 | |
|         });
 | |
|       }
 | |
| 
 | |
|       if (!preq.session) {
 | |
|         return goGetHer();
 | |
|       }
 | |
| 
 | |
|       console.warn('lintAndRequest checkSession', preq);
 | |
|       return oauth3.hooks.checkSession(preq, opts).then(goGetHer);
 | |
|     }
 | |
| 
 | |
|     if (opts.rawCase) {
 | |
|       oauth3.request = lintAndRequest;
 | |
|       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 lintAndRequest(req, opts);
 | |
|       }
 | |
| 
 | |
|       //req = oauth3._recaseRequest(recase, req);
 | |
|       return lintAndRequest(req, opts).then(function (res) {
 | |
|         //return oauth3._recaseResponse(recase, res);
 | |
|         return res;
 | |
|       });
 | |
|     };
 | |
| 
 | |
|     /*
 | |
|     return oauth3._testRequest(request).then(function () {
 | |
|       oauth3.request = request;
 | |
|     });
 | |
|     */
 | |
|   };
 | |
| 
 | |
|   // TODO merge with regular token access point and new response_type=federated ?
 | |
|   oauth3.requests.clientToken = function (providerUri, opts) {
 | |
|     return oauth3.discover(providerUri, opts).then(function (directive) {
 | |
|       return oauth3.request(core.urls.grants(directive, opts)).then(function (grantsResult) {
 | |
|         return grantsResult.originalData || grantsResult.data;
 | |
|       });
 | |
|     });
 | |
|   };
 | |
|   oauth3.requests.grants = function (providerUri, opts) {
 | |
|     return oauth3.discover(providerUri, {
 | |
|       client_id: providerUri
 | |
|     , debug: opts.debug
 | |
|     }).then(function (directive) {
 | |
|       return oauth3.request(core.urls.grants(directive, opts)).then(function (grantsResult) {
 | |
|         if ('POST' === opts.method) {
 | |
|           // TODO this is clientToken
 | |
|           return grantsResult.originalData || grantsResult.data;
 | |
|         }
 | |
| 
 | |
|         var grants = grantsResult.originalData || grantsResult.data;
 | |
|         // TODO
 | |
|         if (grants.error) {
 | |
|           return oauth3.PromiseA.reject(oauth3.core.formatError(grants.error));
 | |
|         }
 | |
| 
 | |
|         console.warn('requests.grants', grants);
 | |
| 
 | |
|         oauth3.hooks.setGrants(opts.client_id + '-client', grants.client);
 | |
|         grants.grants.forEach(function (grant) {
 | |
|           var clientId = grant.client_id || grant.oauth_client_id || grant.oauthClientId;
 | |
|           // TODO should save as an array
 | |
|           oauth3.hooks.setGrants(clientId, [ grant ]);
 | |
|         });
 | |
| 
 | |
|         return {
 | |
|           client: oauth3.hooks.getGrants(opts.client_id + '-client')
 | |
|         , grants: oauth3.hooks.getGrants(opts.client_id) || []
 | |
|         };
 | |
|       });
 | |
|     });
 | |
|   };
 | |
|   oauth3.requests.loginCode = function (providerUri, opts) {
 | |
|     return oauth3.discover(providerUri, opts).then(function (directive) {
 | |
|       var prequest = core.urls.loginCode(directive, opts);
 | |
| 
 | |
|       return oauth3.request(prequest).then(function (res) {
 | |
|         // result = { uuid, expires_at }
 | |
|         return {
 | |
|           otpUuid: res.data.uuid
 | |
|         , otpExpires: res.data.expires_at
 | |
|         };
 | |
|       });
 | |
|     });
 | |
|   };
 | |
|   oauth3.loginCode = oauth3.requests.loginCode;
 | |
| 
 | |
|   oauth3.requests.resourceOwnerPassword = function (providerUri, opts) {
 | |
|     //var scope = opts.scope;
 | |
|     //var appId = opts.appId;
 | |
|     return oauth3.discover(providerUri, opts).then(function (directive) {
 | |
|       var prequest = core.urls.resourceOwnerPassword(directive, opts);
 | |
| 
 | |
|       return oauth3.request(prequest).then(function (req) {
 | |
|         var data = (req.originalData || req.data);
 | |
|         data.provider_uri = providerUri;
 | |
|         if (data.error) {
 | |
|           return oauth3.PromiseA.reject(oauth3.core.formatError(providerUri, data.error));
 | |
|         }
 | |
|         return oauth3.hooks.refreshSession(
 | |
|           opts.session || { provider_uri: providerUri, client_uri: opts.client_uri || opts.clientUri }
 | |
|         , data
 | |
|         );
 | |
|       });
 | |
|     });
 | |
|   };
 | |
|   oauth3.resourceOwnerPassword = oauth3.requests.resourceOwnerPassword;
 | |
| 
 | |
|   oauth3.requests.refreshToken = function (providerUri, opts) {
 | |
|     console.info('[oauth3.requests.refreshToken] called', providerUri, opts);
 | |
|     return oauth3.discover(providerUri, opts).then(function (directive) {
 | |
|       var prequest = core.urls.refreshToken(directive, opts);
 | |
| 
 | |
|       return oauth3.request(prequest).then(function (req) {
 | |
|         var data = (req.originalData || req.data);
 | |
|         data.provider_uri = providerUri;
 | |
|         if (data.error) {
 | |
|           return oauth3.PromiseA.reject(oauth3.core.formatError(providerUri, data));
 | |
|         }
 | |
|         return oauth3.hooks.refreshSession(opts, data);
 | |
|       });
 | |
|     });
 | |
|   };
 | |
|   oauth3.refreshToken = oauth3.requests.refreshToken;
 | |
| 
 | |
|   // TODO It'll be very interesting to see if we can do some browser popup stuff from the CLI
 | |
|   oauth3.requests._error_description = 'Not Implemented: Please override by including <script src="oauth3.browser.js"></script>';
 | |
|   oauth3.requests.authorizationRedirect = function (/*providerUri, opts*/) {
 | |
|     throw new Error(oauth3.requests._error_description);
 | |
|   };
 | |
|   oauth3.requests.implicitGrant = function (/*providerUri, opts*/) {
 | |
|     throw new Error(oauth3.requests._error_description);
 | |
|   };
 | |
|   oauth3.requests.logout = function (/*providerUri, opts*/) {
 | |
|     throw new Error(oauth3.requests._error_description);
 | |
|   };
 | |
| 
 | |
|   oauth3.login = function (providerUri, 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 || opts.otp)) {
 | |
|         throw new Error("you did not specify both username and password");
 | |
|       }
 | |
|       /* jshint ignore:end */
 | |
| 
 | |
|       return oauth3.requests.resourceOwnerPassword(providerUri, 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 = oauth3.requests.authorizationRedirect(providerUri, opts);
 | |
|     }
 | |
|     else {
 | |
|       promise = oauth3.requests.implicitGrant(providerUri, opts);
 | |
|     }
 | |
| 
 | |
|     return promise;
 | |
|   };
 | |
| 
 | |
|   oauth3.backgroundLogin = function (providerUri, opts) {
 | |
|     opts = opts || {};
 | |
|     opts.type = 'background';
 | |
|     return oauth3.login(providerUri, opts);
 | |
|   };
 | |
| 
 | |
|   oauth3.core = core;
 | |
|   oauth3.querystringify = core.querystringify;
 | |
|   oauth3.scopestringify = core.stringifyscope;
 | |
|   oauth3.stringifyscope = core.stringifyscope;
 | |
| 
 | |
|   exports.OAUTH3 = oauth3.oauth3 = oauth3.OAUTH3 = oauth3;
 | |
|   exports.oauth3 = exports.OAUTH3;
 | |
| 
 | |
|   if ('undefined' !== typeof module) {
 | |
|     module.exports = oauth3;
 | |
|   }
 | |
| }('undefined' !== typeof exports ? exports : window));
 |