cleanup cruft
This commit is contained in:
parent
6b0470607c
commit
1d3f4ca8bd
|
@ -1,14 +0,0 @@
|
||||||
git push --tags
|
|
||||||
|
|
||||||
git checkout v1.0
|
|
||||||
git push
|
|
||||||
|
|
||||||
git checkout v1
|
|
||||||
git merge v1.0
|
|
||||||
git push
|
|
||||||
|
|
||||||
git checkout master
|
|
||||||
git merge v1
|
|
||||||
git push
|
|
||||||
|
|
||||||
git checkout v1.0
|
|
|
@ -1,560 +0,0 @@
|
||||||
;(function (exports) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var OAUTH3 = exports.OAUTH3;
|
|
||||||
var OAUTH3_CORE = exports.OAUTH3_CORE;
|
|
||||||
|
|
||||||
function getDefaultAppUrl() {
|
|
||||||
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(/\/?$/, '')
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
var browser = exports.OAUTH3_BROWSER = {
|
|
||||||
window: window
|
|
||||||
, clientUri: function (location) {
|
|
||||||
return OAUTH3_CORE.normalizeUri(location.host + location.pathname);
|
|
||||||
}
|
|
||||||
, discover: function (providerUri, opts) {
|
|
||||||
if (!providerUri) {
|
|
||||||
throw new Error('oauth3.discover(providerUri, opts) received providerUri as ' + providerUri);
|
|
||||||
}
|
|
||||||
var directives = OAUTH3.hooks.getDirectives(providerUri);
|
|
||||||
if (directives && directives.issuer) {
|
|
||||||
return OAUTH3.PromiseA.resolve(directives);
|
|
||||||
}
|
|
||||||
return browser._discoverHelper(providerUri, opts).then(function (directives) {
|
|
||||||
directives.issuer = directives.issuer || OAUTH3_CORE.normalizeUrl(providerUri);
|
|
||||||
console.log('discoverHelper', directives);
|
|
||||||
return OAUTH3.hooks.setDirectives(providerUri, directives);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
, _discoverHelper: function (providerUri, opts) {
|
|
||||||
opts = opts || {};
|
|
||||||
//opts.debug = true;
|
|
||||||
providerUri = OAUTH3_CORE.normalizeUrl(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.core.normalizeUrl(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_CORE.urls.discover(providerUri, { client_id: (opts.client_id || opts.client_uri || getDefaultAppUrl()), debug: opts.debug });
|
|
||||||
|
|
||||||
// TODO ability to reuse iframe instead of closing
|
|
||||||
return browser.insertIframe(discObj.url, discObj.state, opts).then(function (params) {
|
|
||||||
if (params.error) {
|
|
||||||
return OAUTH3_CORE.formatError(providerUri, params.error);
|
|
||||||
}
|
|
||||||
var directives = JSON.parse(atob(OAUTH3_CORE.utils.urlSafeBase64ToBase64(params.result || params.directives)));
|
|
||||||
return directives;
|
|
||||||
}, function (err) {
|
|
||||||
return OAUTH3.PromiseA.reject(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
, discoverAuthorizationDialog: function(providerUri, opts) {
|
|
||||||
var discObj = OAUTH3.core.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
|
|
||||||
var discWin = OAUTH3.openWindow(discObj.url, discObj.state, { reuseWindow: 'conquerer' });
|
|
||||||
return discWin.then(function (params) {
|
|
||||||
console.log('discwin params');
|
|
||||||
console.log(params);
|
|
||||||
// discWin.child
|
|
||||||
// TODO params should have response_type indicating json, binary, etc
|
|
||||||
var directives = JSON.parse(atob(OAUTH3.core.utils.urlSafeBase64ToBase64(params.result || params.directives)));
|
|
||||||
console.log('directives');
|
|
||||||
console.log(directives);
|
|
||||||
|
|
||||||
// Do some stuff
|
|
||||||
var authObj = OAUTH3.core.implicitGrant(
|
|
||||||
directives
|
|
||||||
, { redirect_uri: opts.redirect_uri
|
|
||||||
, debug: opts.debug
|
|
||||||
, client_id: opts.client_id || opts.client_uri
|
|
||||||
, client_uri: opts.client_uri || opts.client_id
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (params.debug) {
|
|
||||||
window.alert("DEBUG MODE: Pausing so you can look at logs and whatnot :) Fire at will!");
|
|
||||||
}
|
|
||||||
|
|
||||||
return new OAUTH3.PromiseA(function (resolve, reject) {
|
|
||||||
// TODO check if authObj.url is relative or full
|
|
||||||
discWin.child.location = OAUTH3.core.urls.resolve(providerUri, authObj.url);
|
|
||||||
|
|
||||||
if (params.debug) {
|
|
||||||
discWin.child.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
window['--oauth3-callback-' + authObj.state] = function (tokens) {
|
|
||||||
if (tokens.error) {
|
|
||||||
return reject(OAUTH3.core.formatError(tokens.error));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params.debug || tokens.debug) {
|
|
||||||
if (window.confirm("DEBUG MODE: okay to close oauth3 window?")) {
|
|
||||||
discWin.child.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
discWin.child.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve(tokens);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}).then(function (tokens) {
|
|
||||||
return OAUTH3.hooks.refreshSession(
|
|
||||||
opts.session || {
|
|
||||||
provider_uri: providerUri
|
|
||||||
, client_id: opts.client_id
|
|
||||||
, client_uri: opts.client_uri || opts.clientUri
|
|
||||||
}
|
|
||||||
, tokens
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
, frameRequest: function (url, state, opts) {
|
|
||||||
var promise;
|
|
||||||
|
|
||||||
if (!opts.windowType) {
|
|
||||||
opts.windowType = 'popup';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('background' === opts.windowType) {
|
|
||||||
promise = browser.insertIframe(url, state, opts);
|
|
||||||
} else if ('popup' === opts.windowType) {
|
|
||||||
promise = browser.openWindow(url, state, opts);
|
|
||||||
} else if ('inline' === opts.windowType) {
|
|
||||||
// callback function will never execute and would need to redirect back to current page
|
|
||||||
// rather than the callback.html
|
|
||||||
url += '&original_url=' + browser.window.location.href;
|
|
||||||
promise = browser.window.location = url;
|
|
||||||
} else {
|
|
||||||
throw new Error("login framing method options.windowType 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;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
, insertIframe: 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;
|
|
||||||
var iframeDiv;
|
|
||||||
|
|
||||||
function cleanup() {
|
|
||||||
delete window['--oauth3-callback-' + state];
|
|
||||||
iframeDiv.remove();
|
|
||||||
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>';
|
|
||||||
|
|
||||||
iframeDiv = window.document.createElement('div');
|
|
||||||
iframeDiv.innerHTML = framesrc;
|
|
||||||
window.document.body.appendChild(iframeDiv);
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO periodically garbage collect expired handlers from window object
|
|
||||||
return promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
, openWindow: function (url, state, opts) {
|
|
||||||
if (opts.debug) {
|
|
||||||
opts.timeout = opts.timeout || 15 * 60 * 1000;
|
|
||||||
}
|
|
||||||
var promise = new OAUTH3.PromiseA(function (resolve, reject) {
|
|
||||||
var tok;
|
|
||||||
|
|
||||||
function cleanup() {
|
|
||||||
clearTimeout(tok);
|
|
||||||
tok = null;
|
|
||||||
delete window['--oauth3-callback-' + state];
|
|
||||||
// this is last in case the window self-closes synchronously
|
|
||||||
// (should never happen, but that's a negotiable implementation detail)
|
|
||||||
if (!opts.reuseWindow) {
|
|
||||||
promise.child.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window['--oauth3-callback-' + state] = function (params) {
|
|
||||||
console.log('YOLO!!');
|
|
||||||
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);
|
|
||||||
|
|
||||||
setTimeout(function () {
|
|
||||||
if (!promise.child) {
|
|
||||||
reject("TODO: open the iframe first and discover oauth3 directives before popup");
|
|
||||||
cleanup();
|
|
||||||
}
|
|
||||||
}, 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO allow size changes (via directive even)
|
|
||||||
promise.child = window.open(
|
|
||||||
url
|
|
||||||
, 'oauth3-login-' + (opts.reuseWindow || state)
|
|
||||||
, 'height=' + (opts.height || 720) + ',width=' + (opts.width || 620)
|
|
||||||
);
|
|
||||||
// TODO periodically garbage collect expired handlers from window object
|
|
||||||
return promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Logins
|
|
||||||
//
|
|
||||||
, authn: {
|
|
||||||
authorizationRedirect: function (providerUri, opts) {
|
|
||||||
// TODO get own directives
|
|
||||||
|
|
||||||
return OAUTH3.discover(providerUri, opts).then(function (directive) {
|
|
||||||
var prequest = OAUTH3_CORE.urls.authorizationRedirect(
|
|
||||||
directive
|
|
||||||
, opts
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!prequest.state) {
|
|
||||||
throw new Error("[Devolper Error] [authorization redirect] prequest.state is empty");
|
|
||||||
}
|
|
||||||
|
|
||||||
return browser.frameRequest(prequest.url, prequest.state, opts);
|
|
||||||
}).then(function (tokens) {
|
|
||||||
return OAUTH3.hooks.refreshSession(
|
|
||||||
opts.session || {
|
|
||||||
provider_uri: providerUri
|
|
||||||
, client_id: opts.client_id
|
|
||||||
, client_uri: opts.client_uri || opts.clientUri
|
|
||||||
}
|
|
||||||
, tokens
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
, implicitGrant: function (providerUri, opts) {
|
|
||||||
// TODO let broker=true change behavior to open discover inline with frameRequest
|
|
||||||
// TODO OAuth3 provider should use the redirect URI as the appId?
|
|
||||||
return OAUTH3.discover(providerUri, opts).then(function (directive) {
|
|
||||||
var prequest = OAUTH3_CORE.urls.implicitGrant(
|
|
||||||
directive
|
|
||||||
// TODO OAuth3 provider should referrer / referer / origin as the appId?
|
|
||||||
, opts
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!prequest.state) {
|
|
||||||
throw new Error("[Devolper Error] [implicit grant] prequest.state is empty");
|
|
||||||
}
|
|
||||||
|
|
||||||
return browser.frameRequest(prequest.url, prequest.state, opts);
|
|
||||||
}).then(function (tokens) {
|
|
||||||
return OAUTH3.hooks.refreshSession(
|
|
||||||
opts.session || {
|
|
||||||
provider_uri: providerUri
|
|
||||||
, client_id: opts.client_id
|
|
||||||
, client_uri: opts.client_uri || opts.clientUri
|
|
||||||
}
|
|
||||||
, tokens
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
, logout: function (providerUri, opts) {
|
|
||||||
opts = opts || {};
|
|
||||||
|
|
||||||
return OAUTH3.discover(providerUri, opts).then(function (directive) {
|
|
||||||
var prequest = OAUTH3_CORE.urls.logout(
|
|
||||||
directive
|
|
||||||
, opts
|
|
||||||
);
|
|
||||||
// Oauth3.init({ logout: function () {} });
|
|
||||||
//return Oauth3.logout();
|
|
||||||
|
|
||||||
var redirectUri = opts.redirect_uri || 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: prequest.state
|
|
||||||
, debug: opts.debug
|
|
||||||
};
|
|
||||||
|
|
||||||
if (prequest.url === params.redirect_uri) {
|
|
||||||
return OAUTH3.PromiseA.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
prequest.url += '#' + OAUTH3_CORE.querystringify(params);
|
|
||||||
|
|
||||||
return OAUTH3.insertIframe(prequest.url, prequest.state, opts);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
, isIframe: function isIframe () {
|
|
||||||
try {
|
|
||||||
return window.self !== window.top;
|
|
||||||
} catch (e) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
, parseUrl: function (url) {
|
|
||||||
var parser = document.createElement('a');
|
|
||||||
parser.href = url;
|
|
||||||
return parser;
|
|
||||||
}
|
|
||||||
, isRedirectHostSafe: function (referrerUrl, redirectUrl) {
|
|
||||||
var src = browser.parseUrl(referrerUrl);
|
|
||||||
var dst = browser.parseUrl(redirectUrl);
|
|
||||||
|
|
||||||
// TODO how should we handle subdomains?
|
|
||||||
// It should be safe for api.example.com to redirect to example.com
|
|
||||||
// But it may not be safe for to example.com to redirect to aj.example.com
|
|
||||||
// It is also probably not safe for sally.example.com to redirect to john.example.com
|
|
||||||
// The client should have a list of allowed URLs to choose from and perhaps a wildcard will do
|
|
||||||
//
|
|
||||||
// api.example.com.evil.com SHOULD NOT match example.com
|
|
||||||
return dst.hostname === src.hostname;
|
|
||||||
}
|
|
||||||
, checkRedirect: function (client, query) {
|
|
||||||
console.warn("[security] URL path checking not yet implemented");
|
|
||||||
|
|
||||||
var clientUrl = OAUTH3.core.normalizeUrl(client.url);
|
|
||||||
var redirectUrl = OAUTH3.core.normalizeUrl(query.redirect_uri);
|
|
||||||
|
|
||||||
// General rule:
|
|
||||||
// I can callback to a shorter domain (fewer subs) or a shorter path (on the same domain)
|
|
||||||
// but not a longer (more subs) or different domain or a longer path (on the same domain)
|
|
||||||
|
|
||||||
|
|
||||||
// We can callback to an explicitly listed domain (TODO and path)
|
|
||||||
if (browser.isRedirectHostSafe(clientUrl, redirectUrl)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
, redirect: function (redirect) {
|
|
||||||
if (parser.search) {
|
|
||||||
parser.search += '&';
|
|
||||||
} else {
|
|
||||||
parser.search += '?';
|
|
||||||
}
|
|
||||||
|
|
||||||
parser.search += 'error=E_NO_SESSION';
|
|
||||||
redirectUri = parser.href;
|
|
||||||
|
|
||||||
window.location.href = redirectUri;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
, hackFormSubmit: function (opts) {
|
|
||||||
opts = opts || {};
|
|
||||||
scope.authorizationDecisionUri = DaplieApiConfig.providerUri + '/api/org.oauth3.provider/authorization_decision';
|
|
||||||
scope.updateScope();
|
|
||||||
|
|
||||||
var redirectUri = scope.appQuery.redirect_uri.replace(/^https?:\/\//i, 'https://');
|
|
||||||
var separator;
|
|
||||||
|
|
||||||
// TODO check that we appropriately use '#' for implicit and '?' for code
|
|
||||||
// (server-side) in an OAuth2 backwards-compatible way
|
|
||||||
if ('token' === scope.appQuery.response_type) {
|
|
||||||
separator = '#';
|
|
||||||
}
|
|
||||||
else if ('code' === scope.appQuery.response_type) {
|
|
||||||
separator = '?';
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
separator = '#';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (scope.pendingScope.length && !opts.allow) {
|
|
||||||
redirectUri += separator + Oauth3.querystringify({
|
|
||||||
error: 'access_denied'
|
|
||||||
, error_description: 'None of the permissions were accepted'
|
|
||||||
, error_uri: 'https://oauth3.org/docs/errors#access_denied'
|
|
||||||
, state: scope.appQuery.state
|
|
||||||
});
|
|
||||||
window.location.href = redirectUri;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO move to Oauth3? or not?
|
|
||||||
// this could be implementation-specific,
|
|
||||||
// but it may still be nice to provide it as de-facto
|
|
||||||
var url = DaplieApiConfig.apiBaseUri + '/api/org.oauth3.provider/grants/:client_id/:account_id'
|
|
||||||
.replace(/:client_id/g, scope.appQuery.client_id || scope.appQuery.client_uri)
|
|
||||||
.replace(/:account_id/g, scope.selectedAccountId)
|
|
||||||
;
|
|
||||||
|
|
||||||
var account = scope.sessionAccount;
|
|
||||||
var session = { accessToken: account.token, refreshToken: account.refreshToken };
|
|
||||||
var preq = {
|
|
||||||
url: url
|
|
||||||
, method: 'POST'
|
|
||||||
, data: {
|
|
||||||
scope: updateAccepted()
|
|
||||||
, response_type: scope.appQuery.response_type
|
|
||||||
, referrer: document.referrer || document.referer || ''
|
|
||||||
, referer: document.referrer || document.referer || ''
|
|
||||||
, tenant_id: scope.appQuery.tenant_id
|
|
||||||
, client_id: scope.appQuery.client_id
|
|
||||||
, client_uri: scope.appQuery.client_uri
|
|
||||||
}
|
|
||||||
, session: session
|
|
||||||
};
|
|
||||||
preq.clientId = preq.appId = DaplieApiConfig.appId || DaplieApiConfig.clientId;
|
|
||||||
preq.clientUri = preq.appUri = DaplieApiConfig.appUri || DaplieApiConfig.clientUri;
|
|
||||||
// TODO need a way to have middleware in Oauth3.request for TherapySession et al
|
|
||||||
|
|
||||||
return Oauth3.request(preq).then(function (resp) {
|
|
||||||
var err;
|
|
||||||
var data = resp.data || {};
|
|
||||||
|
|
||||||
if (data.error) {
|
|
||||||
err = new Error(data.error.message || data.errorDescription);
|
|
||||||
err.message = data.error.message || data.errorDescription;
|
|
||||||
err.code = resp.data.error.code || resp.data.error;
|
|
||||||
err.uri = 'https://oauth3.org/docs/errors#' + (resp.data.error.code || resp.data.error);
|
|
||||||
return $q.reject(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(data.code || data.accessToken)) {
|
|
||||||
err = new Error("No grant code");
|
|
||||||
return $q.reject(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}).then(function (data) {
|
|
||||||
redirectUri += separator + Oauth3.querystringify({
|
|
||||||
state: scope.appQuery.state
|
|
||||||
|
|
||||||
, code: data.code
|
|
||||||
|
|
||||||
, access_token: data.access_token
|
|
||||||
, expires_at: data.expires_at
|
|
||||||
, expires_in: data.expires_in
|
|
||||||
, scope: data.scope
|
|
||||||
|
|
||||||
, refresh_token: data.refresh_token
|
|
||||||
, refresh_expires_at: data.refresh_expires_at
|
|
||||||
, refresh_expires_in: data.refresh_expires_in
|
|
||||||
});
|
|
||||||
|
|
||||||
if ('token' === scope.appQuery.response_type) {
|
|
||||||
window.location.href = redirectUri;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if ('code' === scope.appQuery.response_type) {
|
|
||||||
scope.hackFormSubmitHelper(redirectUri);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.warn("Grant Code NOT IMPLEMENTED for '" + scope.appQuery.response_type + "'");
|
|
||||||
console.warn(redirectUri);
|
|
||||||
throw new Error("Grant Code NOT IMPLEMENTED for '" + scope.appQuery.response_type + "'");
|
|
||||||
}
|
|
||||||
}, function (err) {
|
|
||||||
redirectUri += separator + Oauth3.querystringify({
|
|
||||||
error: err.code || 'server_error'
|
|
||||||
, error_description: err.message || "Server Error: It's not your fault"
|
|
||||||
, error_uri: err.uri || 'https://oauth3.org/docs/errors#server_error'
|
|
||||||
, state: scope.appQuery.state
|
|
||||||
});
|
|
||||||
|
|
||||||
console.error('Grant Code Error NOT IMPLEMENTED');
|
|
||||||
console.error(err);
|
|
||||||
console.error(redirectUri);
|
|
||||||
//window.location.href = redirectUri;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
, hackFormSubmitHelper: function (uri) {
|
|
||||||
// TODO de-jQuerify
|
|
||||||
//window.location.href = redirectUri;
|
|
||||||
//return;
|
|
||||||
|
|
||||||
// the only way to do a POST that redirects the current window
|
|
||||||
window.jQuery('form.js-hack-hidden-form').attr('action', uri);
|
|
||||||
|
|
||||||
// give time for the apply to take place
|
|
||||||
window.setTimeout(function () {
|
|
||||||
window.jQuery('form.js-hack-hidden-form').submit();
|
|
||||||
}, 50);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
browser.requests = browser.authn;
|
|
||||||
|
|
||||||
Object.keys(browser).forEach(function (key) {
|
|
||||||
if ('requests' === key) {
|
|
||||||
Object.keys(browser.requests).forEach(function (key) {
|
|
||||||
OAUTH3.requests[key] = browser.requests[key];
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
OAUTH3[key] = browser[key];
|
|
||||||
});
|
|
||||||
|
|
||||||
}('undefined' !== typeof exports ? exports : window));
|
|
|
@ -1,42 +0,0 @@
|
||||||
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.normalizeUrl(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);
|
|
||||||
|
|
||||||
return promise || promise2;
|
|
||||||
};
|
|
|
@ -1,473 +0,0 @@
|
||||||
;(function (exports) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
// NOTE: we assume that directive.provider_uri exists
|
|
||||||
|
|
||||||
var core = {};
|
|
||||||
core.urls = core;
|
|
||||||
|
|
||||||
function getDefaultAppApiBase() {
|
|
||||||
console.warn('[deprecated] using window.location.host when opts.appApiBase should be used');
|
|
||||||
return 'https://' + window.location.host;
|
|
||||||
}
|
|
||||||
|
|
||||||
core.parsescope = function (scope) {
|
|
||||||
return (scope||'').split(/[+, ]/g);
|
|
||||||
};
|
|
||||||
core.stringifyscope = function (scope) {
|
|
||||||
if (Array.isArray(scope)) {
|
|
||||||
scope = scope.join(' ');
|
|
||||||
}
|
|
||||||
return scope;
|
|
||||||
};
|
|
||||||
|
|
||||||
core.querystringify = function (params) {
|
|
||||||
var qs = [];
|
|
||||||
|
|
||||||
Object.keys(params).forEach(function (key) {
|
|
||||||
// TODO nullify instead?
|
|
||||||
if ('undefined' === typeof params[key]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('scope' === key) {
|
|
||||||
params[key] = core.stringifyscope(params[key]);
|
|
||||||
}
|
|
||||||
|
|
||||||
qs.push(encodeURIComponent(key) + '=' + encodeURIComponent(params[key]));
|
|
||||||
});
|
|
||||||
|
|
||||||
return qs.join('&');
|
|
||||||
};
|
|
||||||
|
|
||||||
// Modified from http://stackoverflow.com/a/7826782
|
|
||||||
core.queryparse = function (search) {
|
|
||||||
// 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;
|
|
||||||
};
|
|
||||||
|
|
||||||
core.formatError = function (providerUri, params) {
|
|
||||||
var err = new Error(params.error_description || params.error.message || "Unknown error when discoving provider '" + providerUri + "'");
|
|
||||||
err.uri = params.error_uri || params.error.uri;
|
|
||||||
err.code = params.error.code || params.error;
|
|
||||||
return err;
|
|
||||||
};
|
|
||||||
core.normalizePath = function (path) {
|
|
||||||
return path.replace(/^\//, '').replace(/\/$/, '');
|
|
||||||
};
|
|
||||||
core.normalizeUri = function (providerUri) {
|
|
||||||
// tested with
|
|
||||||
// example.com
|
|
||||||
// example.com/
|
|
||||||
// http://example.com
|
|
||||||
// https://example.com/
|
|
||||||
return providerUri
|
|
||||||
.replace(/^(https?:\/\/)?/i, '')
|
|
||||||
.replace(/\/?$/, '')
|
|
||||||
;
|
|
||||||
};
|
|
||||||
core.normalizeUrl = function (providerUri) {
|
|
||||||
// tested with
|
|
||||||
// example.com
|
|
||||||
// example.com/
|
|
||||||
// http://example.com
|
|
||||||
// https://example.com/
|
|
||||||
return providerUri
|
|
||||||
.replace(/^(https?:\/\/)?/i, 'https://')
|
|
||||||
.replace(/\/?$/, '')
|
|
||||||
;
|
|
||||||
};
|
|
||||||
|
|
||||||
// these might not really belong in core... not sure
|
|
||||||
// there should be node.js- and browser-specific versions probably
|
|
||||||
core.utils = {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
, base64ToUrlSafeBase64: function (b64) {
|
|
||||||
// Base64 to URL-safe Base64
|
|
||||||
b64 = b64.replace(/\+/g, '-').replace(/\//g, '_');
|
|
||||||
b64 = b64.replace(/=+/g, '');
|
|
||||||
return b64;
|
|
||||||
}
|
|
||||||
, randomState: function () {
|
|
||||||
var i;
|
|
||||||
var ch;
|
|
||||||
var str;
|
|
||||||
|
|
||||||
// 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) {
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
core.jwt = {
|
|
||||||
// decode only (no verification)
|
|
||||||
decode: function (str) {
|
|
||||||
|
|
||||||
// 'abc.qrs.xyz'
|
|
||||||
// [ 'abc', 'qrs', 'xyz' ]
|
|
||||||
// [ {}, {}, 'foo' ]
|
|
||||||
// { header: {}, payload: {}, signature: }
|
|
||||||
var parts = str.split(/\./g);
|
|
||||||
var jsons = parts.slice(0, 2).map(function (urlsafe64) {
|
|
||||||
var atob = exports.atob || require('atob');
|
|
||||||
var b64 = core.utils.urlSafeBase64ToBase64(urlsafe64);
|
|
||||||
return atob(b64);
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
header: JSON.parse(jsons[0])
|
|
||||||
, payload: JSON.parse(jsons[1])
|
|
||||||
, signature: parts[2] // should remain url-safe base64
|
|
||||||
};
|
|
||||||
}
|
|
||||||
, getFreshness: function (tokenMeta, staletime, now) {
|
|
||||||
staletime = staletime || (15 * 60);
|
|
||||||
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';
|
|
||||||
}
|
|
||||||
// encode-only (no signature)
|
|
||||||
, encode: function (parts) {
|
|
||||||
parts.header = parts.header || { alg: 'none', typ: 'jwt' };
|
|
||||||
parts.signature = parts.signature || '';
|
|
||||||
|
|
||||||
var btoa = exports.btoa || require('btoa');
|
|
||||||
var result = [
|
|
||||||
core.utils.base64ToUrlSafeBase64(btoa(JSON.stringify(parts.header, null)))
|
|
||||||
, core.utils.base64ToUrlSafeBase64(btoa(JSON.stringify(parts.payload, null)))
|
|
||||||
, parts.signature // should already be url-safe base64
|
|
||||||
].join('.');
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
core.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 = core.normalizeUrl(opts.client_id || opts.client_uri);
|
|
||||||
providerUri = core.normalizeUrl(providerUri);
|
|
||||||
|
|
||||||
var params = {
|
|
||||||
action: 'directives'
|
|
||||||
, state: core.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/#/?' + core.querystringify(params)
|
|
||||||
, state: params.state
|
|
||||||
, method: 'GET'
|
|
||||||
, query: params
|
|
||||||
};
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
core.urls.authorizationCode = function (/*directive, 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=`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
|
|
||||||
//
|
|
||||||
// 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");
|
|
||||||
};
|
|
||||||
|
|
||||||
core.urls.authorizationRedirect = function (directive, 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=`cryptoutil.random().toString('hex')`
|
|
||||||
// &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 || {};
|
|
||||||
|
|
||||||
var scope = opts.scope || directive.authn_scope;
|
|
||||||
var providerUri = directive.provider_uri;
|
|
||||||
var params = {
|
|
||||||
state: core.utils.randomState()
|
|
||||||
, debug: opts.debug || undefined
|
|
||||||
};
|
|
||||||
var slimProviderUri = encodeURIComponent(providerUri.replace(/^(https?|spdy):\/\//, ''));
|
|
||||||
var authorizationRedirect = opts.authorizationRedirect;
|
|
||||||
|
|
||||||
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 = (opts.appApiBase || getDefaultAppApiBase())
|
|
||||||
+ '/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 {
|
|
||||||
url: authorizationRedirect + '?' + core.querystringify(params)
|
|
||||||
, method: 'GET'
|
|
||||||
, state: params.state // this becomes browser_state
|
|
||||||
, params: params // includes scope, final redirect_uri?
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
core.urls.implicitGrant = function (directive, 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=`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 redirectUri = opts.redirect_uri;
|
|
||||||
var scope = opts.scope || directive.authn_scope;
|
|
||||||
var args = directive[type];
|
|
||||||
var uri = args.url;
|
|
||||||
var state = core.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
|
|
||||||
};
|
|
||||||
var result;
|
|
||||||
|
|
||||||
params.state = state;
|
|
||||||
params.response_type = responseType;
|
|
||||||
if (scope) {
|
|
||||||
params.scope = core.stringifyscope(scope);
|
|
||||||
}
|
|
||||||
if (!redirectUri) {
|
|
||||||
// TODO consider making this optional
|
|
||||||
console.error('missing redirect_uri');
|
|
||||||
}
|
|
||||||
params.redirect_uri = redirectUri;
|
|
||||||
|
|
||||||
uri += '?' + core.querystringify(params);
|
|
||||||
|
|
||||||
result = {
|
|
||||||
url: uri
|
|
||||||
, state: state
|
|
||||||
, method: args.method
|
|
||||||
, query: params
|
|
||||||
};
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
core.urls.resolve = function (base, next) {
|
|
||||||
if (/^https:\/\//i.test(next)) {
|
|
||||||
return next;
|
|
||||||
}
|
|
||||||
return core.normalizeUrl(base) + '/' + core.normalizePath(next);
|
|
||||||
};
|
|
||||||
|
|
||||||
core.urls.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.appSecret || opts.clientSecret;
|
|
||||||
var args = directive[type];
|
|
||||||
var params = {
|
|
||||||
"grant_type": grantType
|
|
||||||
, "refresh_token": opts.refresh_token || opts.refreshToken || (opts.session && opts.session.refresh_token)
|
|
||||||
, "response_type": 'token'
|
|
||||||
, "client_id": opts.appId || opts.app_id || opts.client_id || opts.clientId || opts.client_id || opts.clientId
|
|
||||||
, "client_uri": opts.client_uri || opts.clientUri
|
|
||||||
//, "scope": undefined
|
|
||||||
//, "client_secret": undefined
|
|
||||||
, debug: opts.debug || undefined
|
|
||||||
};
|
|
||||||
var uri = args.url;
|
|
||||||
var body;
|
|
||||||
|
|
||||||
// TODO not allowed in the browser
|
|
||||||
if (clientSecret) {
|
|
||||||
params.client_secret = clientSecret;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (scope) {
|
|
||||||
params.scope = core.stringifyscope(scope);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('GET' === args.method.toUpperCase()) {
|
|
||||||
uri += '?' + core.querystringify(params);
|
|
||||||
} else {
|
|
||||||
body = params;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
url: uri
|
|
||||||
, method: args.method
|
|
||||||
, data: body
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
core.urls.logout = function (directive, opts) {
|
|
||||||
opts = opts || {};
|
|
||||||
var type = 'logout';
|
|
||||||
var clientId = opts.appId || opts.clientId || opts.client_id;
|
|
||||||
var args = directive[type];
|
|
||||||
var params = {
|
|
||||||
client_id: opts.clientUri || opts.client_uri
|
|
||||||
, debug: opts.debug || undefined
|
|
||||||
};
|
|
||||||
var uri = args.url;
|
|
||||||
var body;
|
|
||||||
|
|
||||||
if (opts.clientUri) {
|
|
||||||
params.client_uri = opts.clientUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (clientId) {
|
|
||||||
params.client_id = clientId;
|
|
||||||
}
|
|
||||||
|
|
||||||
args.method = (args.method || 'GET').toUpperCase();
|
|
||||||
if ('GET' === args.method) {
|
|
||||||
uri += '?' + core.querystringify(params);
|
|
||||||
} else {
|
|
||||||
body = params;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
url: uri
|
|
||||||
, method: args.method || 'GET'
|
|
||||||
, data: body
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.OAUTH3 = exports.OAUTH3 || { core: core };
|
|
||||||
exports.OAUTH3_CORE = core.OAUTH3_CORE = core;
|
|
||||||
|
|
||||||
if ('undefined' !== typeof module) {
|
|
||||||
module.exports = core;
|
|
||||||
}
|
|
||||||
}('undefined' !== typeof exports ? exports : window));
|
|
|
@ -1,302 +0,0 @@
|
||||||
;(function (exports) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var core = window.OAUTH3_CORE;
|
|
||||||
|
|
||||||
// Provider-Only
|
|
||||||
core.urls.loginCode = function (directive, opts) {
|
|
||||||
//
|
|
||||||
// Example Resource Owner Password Request
|
|
||||||
// (generally for 1st party and direct-partner mobile apps, and webapps)
|
|
||||||
//
|
|
||||||
// POST https://api.example.com/api/org.oauth3.provider/otp
|
|
||||||
// { "request_otp": true, "client_id": "<<id>>", "scope": "<<scope>>"
|
|
||||||
// , "username": "<<username>>" }
|
|
||||||
//
|
|
||||||
opts = opts || {};
|
|
||||||
var clientId = opts.appId || opts.clientId;
|
|
||||||
|
|
||||||
var args = directive.credential_otp;
|
|
||||||
if (!directive.credential_otp) {
|
|
||||||
console.log('[debug] loginCode directive:');
|
|
||||||
console.log(directive);
|
|
||||||
}
|
|
||||||
var params = {
|
|
||||||
"username": opts.id || opts.username
|
|
||||||
, "request_otp": true // opts.requestOtp || undefined
|
|
||||||
//, "jwt": opts.jwt // TODO sign a proof
|
|
||||||
, debug: opts.debug || undefined
|
|
||||||
};
|
|
||||||
var uri = args.url;
|
|
||||||
var body;
|
|
||||||
if (opts.clientUri) {
|
|
||||||
params.client_uri = opts.clientUri;
|
|
||||||
}
|
|
||||||
if (opts.clientAgreeTos) {
|
|
||||||
params.client_agree_tos = opts.clientAgreeTos;
|
|
||||||
}
|
|
||||||
if (clientId) {
|
|
||||||
params.client_id = clientId;
|
|
||||||
}
|
|
||||||
if ('GET' === args.method.toUpperCase()) {
|
|
||||||
uri += '?' + core.querystringify(params);
|
|
||||||
} else {
|
|
||||||
body = params;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
url: uri
|
|
||||||
, method: args.method
|
|
||||||
, data: body
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
core.urls.resourceOwnerPassword = function (directive, 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';
|
|
||||||
|
|
||||||
if (!opts.password) {
|
|
||||||
if (opts.otp) {
|
|
||||||
// for backwards compat
|
|
||||||
opts.password = opts.otp; // 'otp:' + opts.otpUuid + ':' + opts.otp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var scope = opts.scope || directive.authn_scope;
|
|
||||||
var clientId = opts.appId || opts.clientId || opts.client_id;
|
|
||||||
var clientAgreeTos = opts.clientAgreeTos || opts.client_agree_tos;
|
|
||||||
var clientUri = opts.clientUri || opts.client_uri || opts.clientUrl || opts.client_url;
|
|
||||||
var args = directive[type];
|
|
||||||
var otpCode = opts.otp || opts.otpCode || opts.otp_code || opts.otpToken || opts.otp_token || undefined;
|
|
||||||
var params = {
|
|
||||||
"grant_type": grantType
|
|
||||||
, "username": opts.username
|
|
||||||
, "password": opts.password || otpCode || undefined
|
|
||||||
, "totp": opts.totp || opts.totpToken || opts.totp_token || undefined
|
|
||||||
, "otp": otpCode
|
|
||||||
, "password_type": otpCode && 'otp'
|
|
||||||
, "otp_code": otpCode
|
|
||||||
, "otp_uuid": opts.otpUuid || opts.otp_uuid || undefined
|
|
||||||
, "user_agent": opts.userAgent || opts.useragent || opts.user_agent || undefined // AJ's Macbook
|
|
||||||
, "jwk": (opts.rememberDevice || opts.remember_device) && opts.jwk || undefined
|
|
||||||
//, "public_key": opts.rememberDevice && opts.publicKey || undefined
|
|
||||||
//, "public_key_type": opts.rememberDevice && opts.publicKeyType || undefined // RSA/ECDSA
|
|
||||||
//, "jwt": opts.jwt // TODO sign a proof with a previously loaded public_key
|
|
||||||
, debug: opts.debug || undefined
|
|
||||||
};
|
|
||||||
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) {
|
|
||||||
params.scope = core.stringifyscope(scope);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('GET' === args.method.toUpperCase()) {
|
|
||||||
uri += '?' + core.querystringify(params);
|
|
||||||
} else {
|
|
||||||
body = params;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
url: uri
|
|
||||||
, method: args.method
|
|
||||||
, data: body
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
core.urls.grants = function (directive, opts) {
|
|
||||||
// directive = { issuer, authorization_decision }
|
|
||||||
// opts = { response_type, scopes{ granted, requested, pending, accepted } }
|
|
||||||
|
|
||||||
if (!opts) {
|
|
||||||
throw new Error("You must supply a directive and an options object.");
|
|
||||||
}
|
|
||||||
if (!opts.client_id) {
|
|
||||||
throw new Error("You must supply options.client_id.");
|
|
||||||
}
|
|
||||||
if (!opts.session) {
|
|
||||||
throw new Error("You must supply options.session.");
|
|
||||||
}
|
|
||||||
if (!opts.referrer) {
|
|
||||||
console.warn("You should supply options.referrer");
|
|
||||||
}
|
|
||||||
if (!opts.method) {
|
|
||||||
console.warn("You must supply options.method as either 'GET', or 'POST'");
|
|
||||||
}
|
|
||||||
if ('POST' === opts.method) {
|
|
||||||
if ('string' !== typeof opts.scope) {
|
|
||||||
console.warn("You should supply options.scope as a space-delimited string of scopes");
|
|
||||||
}
|
|
||||||
if (-1 === ['token', 'code'].indexOf(opts.response_type)) {
|
|
||||||
throw new Error("You must supply options.response_type as 'token' or 'code'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var url = core.urls.resolve(directive.issuer, directive.grants.url)
|
|
||||||
.replace(/(:azp|:client_id)/g, core.normalizeUri(opts.client_id || opts.client_uri))
|
|
||||||
.replace(/(:sub|:account_id)/g, opts.session.token.sub)
|
|
||||||
;
|
|
||||||
var data = {
|
|
||||||
client_id: opts.client_id
|
|
||||||
, client_uri: opts.client_uri
|
|
||||||
, referrer: opts.referrer
|
|
||||||
, response_type: opts.response_type
|
|
||||||
, scope: opts.scope
|
|
||||||
, tenant_id: opts.tenant_id
|
|
||||||
};
|
|
||||||
var body;
|
|
||||||
|
|
||||||
if ('GET' === opts.method) {
|
|
||||||
url += '?' + core.querystringify(data);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
body = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
method: opts.method
|
|
||||||
, url: url
|
|
||||||
, data: body
|
|
||||||
, session: opts.session
|
|
||||||
};
|
|
||||||
};
|
|
||||||
core.urls.authorizationDecision = function (directive, opts) {
|
|
||||||
var url = core.urls.resolve(directive.issuer, directive.authorization_decision.url);
|
|
||||||
if (!opts) {
|
|
||||||
throw new Error("You must supply a directive and an options object");
|
|
||||||
}
|
|
||||||
console.info(url);
|
|
||||||
throw new Error("NOT IMPLEMENTED authorization_decision");
|
|
||||||
};
|
|
||||||
core.authz = core.authz || {};
|
|
||||||
core.authz.scopes = function (session, clientParams) {
|
|
||||||
// OAuth3.requests.grants(providerUri, {}); // return list of grants
|
|
||||||
// OAuth3.checkGrants(providerUri, {}); //
|
|
||||||
var clientUri = OAUTH3.core.normalizeUri(clientParams.client_uri || window.document.referrer);
|
|
||||||
var scope = clientParams.scope || '';
|
|
||||||
var clientObj = clientParams;
|
|
||||||
|
|
||||||
if (!scope) {
|
|
||||||
scope = 'oauth3_authn';
|
|
||||||
}
|
|
||||||
|
|
||||||
//$('.js-user-avatar').attr('src', userAvatar);
|
|
||||||
|
|
||||||
/*
|
|
||||||
console.log('grants options');
|
|
||||||
console.log(loc.hash);
|
|
||||||
console.log(loc.search);
|
|
||||||
console.log(clientObj);
|
|
||||||
console.log(session.token);
|
|
||||||
console.log(window.document.referrer);
|
|
||||||
*/
|
|
||||||
|
|
||||||
return OAUTH3.requests.grants(CONFIG.host, {
|
|
||||||
method: 'GET'
|
|
||||||
, client_id: clientUri
|
|
||||||
, client_uri: clientUri
|
|
||||||
, session: session
|
|
||||||
}).then(function (grantResults) {
|
|
||||||
var grants;
|
|
||||||
var grantedScopes;
|
|
||||||
var grantedScopesMap;
|
|
||||||
var pendingScopes;
|
|
||||||
var acceptedScopes;
|
|
||||||
var scopes = scope.split(/[+, ]/g);
|
|
||||||
var callbackUrl;
|
|
||||||
|
|
||||||
console.log('previous grants:');
|
|
||||||
console.log(grantResults);
|
|
||||||
|
|
||||||
if (grantResults.data.error) {
|
|
||||||
window.alert('grantResults: ' + grantResults.data.error_description || grantResults.data.error.message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// it doesn't matter who the referrer is as long as the destination
|
|
||||||
// is an authorized destination for the client in question
|
|
||||||
// (though it may not hurt to pass the referrer's info on to the client)
|
|
||||||
if (!OAUTH3.checkRedirect(grantResults.data.client, clientObj)) {
|
|
||||||
callbackUrl = 'https://oauth3.org/docs/errors#E_REDIRECT_ATTACK'
|
|
||||||
+ '?redirect_uri=' + clientObj.redirect_uri
|
|
||||||
+ '&allowed_urls=' + grantResults.data.client.url
|
|
||||||
+ '&client_id=' + clientUri
|
|
||||||
+ '&referrer_uri=' + OAUTH3.core.normalizeUri(window.document.referrer)
|
|
||||||
;
|
|
||||||
location.href = callbackUrl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('oauth3_authn' === scope) {
|
|
||||||
// implicit ppid grant is automatic
|
|
||||||
console.warn('[security] fix scope checking on backend so that we can do automatic grants');
|
|
||||||
// TODO check user preference if implicit ppid grant is allowed
|
|
||||||
//return generateToken(session, clientObj);
|
|
||||||
}
|
|
||||||
|
|
||||||
grants = (grantResults.originalData||grantResults.data).grants.filter(function (grant) {
|
|
||||||
if (clientUri === (grant.azp || grant.oauth_client_id || grant.oauthClientId)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
grantedScopesMap = {};
|
|
||||||
acceptedScopes = [];
|
|
||||||
pendingScopes = scopes.filter(function (requestedScope) {
|
|
||||||
return grants.every(function (grant) {
|
|
||||||
if (!grant.scope) {
|
|
||||||
grant.scope = 'oauth3_authn';
|
|
||||||
}
|
|
||||||
var gscopes = grant.scope.split(/[+, ]/g);
|
|
||||||
gscopes.forEach(function (s) { grantedScopesMap[s] = true; });
|
|
||||||
if (-1 !== gscopes.indexOf(requestedScope)) {
|
|
||||||
// already accepted in the past
|
|
||||||
acceptedScopes.push(requestedScope);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// true, is pending
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
grantedScopes = Object.keys(grantedScopesMap);
|
|
||||||
|
|
||||||
return {
|
|
||||||
pending: pendingScopes // not yet accepted
|
|
||||||
, granted: grantedScopes // all granted, ever
|
|
||||||
, requested: scopes // all requested, now
|
|
||||||
, accepted: acceptedScopes // granted (ever) and requested (now)
|
|
||||||
};
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.OAUTH3_CORE_PROVIDER = core;
|
|
||||||
|
|
||||||
if ('undefined' !== typeof module) {
|
|
||||||
module.exports = core;
|
|
||||||
}
|
|
||||||
}('undefined' !== typeof exports ? exports : window));
|
|
|
@ -1,109 +0,0 @@
|
||||||
(function () {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
// I did try to shim jQuery's deferred, but it's just too clunky.
|
|
||||||
// Here I use es6-promise which lacks asynchrity, but it's the smallest Promise implementation.
|
|
||||||
// Only Opera Mini and MSIE (even on 11) will use this shim, so no biggie;
|
|
||||||
|
|
||||||
var oauth3 = window.OAUTH3;
|
|
||||||
var count = 0;
|
|
||||||
|
|
||||||
function inject() {
|
|
||||||
count += 1;
|
|
||||||
|
|
||||||
if (count >= 100) {
|
|
||||||
throw new Error("you forgot to include rsvp.js, methinks");
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
*
|
|
||||||
[window.Promise, window.ES6Promise, window.RSVP.Promise].forEach(function (PromiseA) {
|
|
||||||
var x = 1; new PromiseA(function (resolve, reject) { console.log('x', 1 === x); resolve(); }); x = 2; void null;
|
|
||||||
var y = 1; PromiseA.resolve().then(function () { console.log('y', 2 === x); }); y = 2; void null;
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
var PromiseA = /*(window.RSVP && window.RSVP.Promise) || window.ES6Promise || */window.Promise;
|
|
||||||
if ('undefined' !== typeof PromiseA) {
|
|
||||||
oauth3.providePromise(PromiseA).then(function () {
|
|
||||||
// ignore
|
|
||||||
window.jqOauth3 = oauth3;
|
|
||||||
}, function (err) {
|
|
||||||
console.error(err);
|
|
||||||
console.error("Bad Promise Implementation!");
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// because MSIE can't tell when a script is loaded
|
|
||||||
setTimeout(inject, 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('undefined' === typeof Promise) {
|
|
||||||
// support Opera Mini and MSIE 11+ (which doesn't support <!-- [if IE]> detection)
|
|
||||||
/* jshint ignore: start */
|
|
||||||
document.write('<script src="bower_components/es6-promise/promise.min.js"></script>');
|
|
||||||
/* jshint ignore: end */
|
|
||||||
|
|
||||||
/*
|
|
||||||
// I would have used this, but it turns out that
|
|
||||||
// MSIE can't tell when a script has loaded
|
|
||||||
var js = document.createElement("script");
|
|
||||||
js.setAttribute("src", "bower_components/es6-promise/promise.js");
|
|
||||||
js.setAttribute("type", "text/javascript");
|
|
||||||
document.getElementsByTagName("head")[0].appendChild(js);
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
inject();
|
|
||||||
|
|
||||||
function Request(opts) {
|
|
||||||
if (!opts.method) {
|
|
||||||
throw new Error("Developer Error: you must set method as one of 'GET', 'POST', 'DELETE', etc");
|
|
||||||
}
|
|
||||||
|
|
||||||
var req = {
|
|
||||||
url: opts.url
|
|
||||||
// Noted: jQuery 1.9 finally added 'method' as an alias of 'type'
|
|
||||||
, method: opts.method
|
|
||||||
// leaving type for backwards compat
|
|
||||||
, type: opts.method
|
|
||||||
, headers: opts.headers || {}
|
|
||||||
};
|
|
||||||
|
|
||||||
// don't allow accidetal querystring via 'data'
|
|
||||||
if (opts.data && !/get|delete/i.test(opts.method)) {
|
|
||||||
req.data = opts.data;
|
|
||||||
if (opts.data && 'object' === typeof opts.data) {
|
|
||||||
req.data = JSON.stringify(req.data);
|
|
||||||
req.headers['Content-Type'] = 'application/json; charset=utf-8';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// I don't trust jQuery promises...
|
|
||||||
return new oauth3.PromiseA(function (resolve, reject) {
|
|
||||||
$.ajax(req).then(function (data, textStatus, jqXhr) {
|
|
||||||
var resp = {};
|
|
||||||
|
|
||||||
Object.keys(jqXhr).forEach(function (key) {
|
|
||||||
// particularly we have to get rid of .then
|
|
||||||
if ('function' !== typeof jqXhr[key]) {
|
|
||||||
resp[key] = jqXhr[key];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
resp.data = data;
|
|
||||||
resp.status = textStatus;
|
|
||||||
resp.request = jqXhr;
|
|
||||||
resolve(resp);
|
|
||||||
}, function (jqXhr, textStatus, errorThrown) {
|
|
||||||
errorThrown.request = jqXhr;
|
|
||||||
errorThrown.response = jqXhr;
|
|
||||||
errorThrown.status = textStatus;
|
|
||||||
reject(errorThrown);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
oauth3.provideRequest(Request);
|
|
||||||
}());
|
|
|
@ -1,445 +0,0 @@
|
||||||
/* 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);
|
|
||||||
// shim for account create which does not return new refresh_token
|
|
||||||
newSession.refresh_token = newSession.refresh_token || oldSession.refresh_token;
|
|
||||||
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 && oldSession.token.acx.id)
|
|
||||||
|| (oldSession.token.axs && oldSession.token.axs.length && oldSession.token.axs[0].appScopedId)
|
|
||||||
;
|
|
||||||
oldSession.token.client_uri = clientUri;
|
|
||||||
oldSession.token.provider_uri = providerUri;
|
|
||||||
|
|
||||||
if (!oldSession.token.sub) {
|
|
||||||
// TODO this is broken hard
|
|
||||||
console.warn('TODO implementation for OAUTH3.hooks.accounts.create (GUI, CLI, or API)');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldSession.refresh_token) {
|
|
||||||
oldSession.refresh = core.jwt.decode(oldSession.refresh_token).payload;
|
|
||||||
oldSession.refresh.sub = oldSession.refresh.sub
|
|
||||||
|| (oldSession.refresh.acx && oldSession.refresh.acx.id)
|
|
||||||
|| (oldSession.refresh.axs && oldSession.refresh.axs.length && oldSession.refresh.axs[0].appScopedId)
|
|
||||||
;
|
|
||||||
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('[ERROR] Please implement OAUTH3.hooks.setSession = function (providerUri, newSession) { return newSession; }');
|
|
||||||
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('[ERROR] Please implement OAUTH3.hooks.getSession = function (providerUri) { return savedSession; }');
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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));
|
|
|
@ -1,158 +0,0 @@
|
||||||
|
|
||||||
// TODO move to a test / lint suite?
|
|
||||||
oauth3._lintPromise = function (PromiseA) {
|
|
||||||
var promise;
|
|
||||||
var x = 1;
|
|
||||||
|
|
||||||
// tests that this promise has all of the necessary api
|
|
||||||
promise = new PromiseA(function (resolve, reject) {
|
|
||||||
//console.log('x [2]', x);
|
|
||||||
if (x !== 1) {
|
|
||||||
throw new Error("bad promise, create not Synchronous [0]");
|
|
||||||
}
|
|
||||||
|
|
||||||
PromiseA.resolve().then(function () {
|
|
||||||
var promise2;
|
|
||||||
|
|
||||||
//console.log('x resolve', x);
|
|
||||||
if (x !== 2) {
|
|
||||||
throw new Error("bad promise, resolve not Asynchronous [1]");
|
|
||||||
}
|
|
||||||
|
|
||||||
promise2 = PromiseA.reject().then(reject, function () {
|
|
||||||
//console.log('x reject', x);
|
|
||||||
if (x !== 4) {
|
|
||||||
throw new Error("bad promise, reject not Asynchronous [2]");
|
|
||||||
}
|
|
||||||
|
|
||||||
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._lintDirectives = function (providerUri, directives) {
|
|
||||||
var params = { directives: directives };
|
|
||||||
console.log('DEBUG oauth3._discoverHelper', directives);
|
|
||||||
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
|
|
||||||
// TODO self-reference in directive for providerUri?
|
|
||||||
directives.provider_uri = providerUri;
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
core.tokenState = function (session) {
|
|
||||||
var fresh;
|
|
||||||
fresh = (Date.now() / 1000) >= (parseInt(session._accessTokenData.exp) || 0);
|
|
||||||
if (!fresh) {
|
|
||||||
console.log("[os] isn't fresh", session._accessTokenData.exp);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
oauth3._lintRequest = function (preq, opts) {
|
|
||||||
var providerUri;
|
|
||||||
|
|
||||||
console.log('[os] request meta opts', opts);
|
|
||||||
|
|
||||||
// check that the JWT is not expired
|
|
||||||
// TODO check that this request applies to the aud and azp
|
|
||||||
if (!(preq.session && preq.session.accessToken)) {
|
|
||||||
console.log('[os] no session/accessTokenData');
|
|
||||||
return oauth3.PromiseA.resolve(preq);
|
|
||||||
}
|
|
||||||
|
|
||||||
preq.headers = preq.headers || {};
|
|
||||||
preq.headers.Authorization = 'Bearer ' + preq.session.accessToken;
|
|
||||||
|
|
||||||
if (!preq.session._accessTokenData) {
|
|
||||||
console.log('[os] no _accessTokenData');
|
|
||||||
preq.session._accessTokenData = core.jwt.decode(preq.session.accessToken).payload;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!preq.url.match(preq.session._accessTokenData.aud)) {
|
|
||||||
console.log("[os] doesn't match audience", preq.session._accessTokenData.aud);
|
|
||||||
return oauth3.PromiseA.resolve(preq);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (core.tokenState(session)) {
|
|
||||||
case 'fresh':
|
|
||||||
return oauth3.PromiseA.resolve(preq);
|
|
||||||
case 'stale':
|
|
||||||
case 'useless':
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!preq.session.refreshToken) {
|
|
||||||
console.log("[os] can't refresh", preq.session);
|
|
||||||
return oauth3.PromiseA.resolve(preq);
|
|
||||||
}
|
|
||||||
|
|
||||||
opts.refreshToken = preq.session.refreshToken;
|
|
||||||
console.log('[oauth3.js] refreshToken attempt');
|
|
||||||
|
|
||||||
// TODO include directive?
|
|
||||||
providerUri = preq.session.providerUri || preq.session._accessTokenData.iss;
|
|
||||||
//opts.
|
|
||||||
return oauth3.refreshToken(providerUri, opts).then(function (res) {
|
|
||||||
console.log('[oauth3.js] refreshToken result:', res);
|
|
||||||
|
|
||||||
if (!res.data.accessToken) {
|
|
||||||
return preq;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO fire session update event
|
|
||||||
res.data.providerUri = preq.session.providerUri;
|
|
||||||
preq.session = res.data;
|
|
||||||
preq.headers.Authorization = 'Bearer ' + preq.session.accessToken;
|
|
||||||
return preq;
|
|
||||||
});
|
|
||||||
};
|
|
|
@ -1,97 +0,0 @@
|
||||||
;(function (exports) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var OAUTH3 = window.OAUTH3 || require('./oauth3.js');
|
|
||||||
|
|
||||||
OAUTH3.authz = OAUTH3.authz || {};
|
|
||||||
OAUTH3.authz.scopes = function (providerUri, session, clientParams) {
|
|
||||||
// OAuth3.requests.grants(providerUri, {}); // return list of grants
|
|
||||||
// OAuth3.checkGrants(providerUri, {}); //
|
|
||||||
var clientUri = OAUTH3.core.normalizeUri(clientParams.client_id || clientParams.client_uri);
|
|
||||||
var scope = clientParams.scope || '';
|
|
||||||
var clientObj = clientParams;
|
|
||||||
|
|
||||||
if (!scope) {
|
|
||||||
scope = 'oauth3_authn';
|
|
||||||
}
|
|
||||||
|
|
||||||
return OAUTH3.requests.grants(providerUri, {
|
|
||||||
method: 'GET'
|
|
||||||
, client_id: clientUri
|
|
||||||
, client_uri: clientUri
|
|
||||||
, session: session
|
|
||||||
}).then(function (grants) {
|
|
||||||
var myGrants;
|
|
||||||
var grantedScopes;
|
|
||||||
var grantedScopesMap;
|
|
||||||
var pendingScopes;
|
|
||||||
var acceptedScopes;
|
|
||||||
var acceptedScopesMap;
|
|
||||||
var scopes = OAUTH3.core.parsescope(scope);
|
|
||||||
var callbackUrl;
|
|
||||||
|
|
||||||
console.log('previous grants:');
|
|
||||||
console.log(grants);
|
|
||||||
|
|
||||||
// it doesn't matter who the referrer is as long as the destination
|
|
||||||
// is an authorized destination for the client in question
|
|
||||||
// (though it may not hurt to pass the referrer's info on to the client)
|
|
||||||
if (!OAUTH3.checkRedirect(grants.client, clientObj)) {
|
|
||||||
callbackUrl = 'https://oauth3.org/docs/errors#E_REDIRECT_ATTACK'
|
|
||||||
+ '?redirect_uri=' + clientObj.redirect_uri
|
|
||||||
+ '&allowed_urls=' + grants.client.url
|
|
||||||
+ '&client_id=' + clientUri
|
|
||||||
+ '&referrer_uri=' + OAUTH3.core.normalizeUri(window.document.referrer)
|
|
||||||
;
|
|
||||||
location.href = callbackUrl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.warn("What are grants? Baby don't hurt me. Don't hurt me. No more.");
|
|
||||||
console.warn(grants);
|
|
||||||
|
|
||||||
myGrants = grants.grants.filter(function (grant) {
|
|
||||||
if (clientUri === (grant.azp || grant.oauth_client_id || grant.oauthClientId)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
grantedScopesMap = {};
|
|
||||||
acceptedScopesMap = {};
|
|
||||||
pendingScopes = scopes.filter(function (requestedScope) {
|
|
||||||
return myGrants.every(function (grant) {
|
|
||||||
if (!grant.scope) {
|
|
||||||
grant.scope = 'oauth3_authn';
|
|
||||||
}
|
|
||||||
var gscopes = grant.scope.split(/[+, ]/g);
|
|
||||||
gscopes.forEach(function (s) { grantedScopesMap[s] = true; });
|
|
||||||
if (-1 !== gscopes.indexOf(requestedScope)) {
|
|
||||||
// already accepted in the past
|
|
||||||
acceptedScopesMap[requestedScope] = true;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// true, is pending
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
grantedScopes = Object.keys(grantedScopesMap);
|
|
||||||
acceptedScopes = Object.keys(acceptedScopesMap);
|
|
||||||
|
|
||||||
return {
|
|
||||||
pending: pendingScopes // not yet accepted
|
|
||||||
, granted: grantedScopes // all granted, ever
|
|
||||||
, requested: scopes // all requested, now
|
|
||||||
, accepted: acceptedScopes // granted (ever) and requested (now)
|
|
||||||
, client: grants.client
|
|
||||||
, grants: grants.grants
|
|
||||||
};
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.OAUTH3_PROVIDER = OAUTH3;
|
|
||||||
|
|
||||||
if ('undefined' !== typeof module) {
|
|
||||||
module.exports = OAUTH3;
|
|
||||||
}
|
|
||||||
}('undefined' !== typeof exports ? exports : window));
|
|
|
@ -1,24 +0,0 @@
|
||||||
var separator;
|
|
||||||
|
|
||||||
// TODO check that we appropriately use '#' for implicit and '?' for code
|
|
||||||
// (server-side) in an OAuth2 backwards-compatible way
|
|
||||||
if ('token' === scope.appQuery.response_type) {
|
|
||||||
separator = '#';
|
|
||||||
}
|
|
||||||
else if ('code' === scope.appQuery.response_type) {
|
|
||||||
separator = '?';
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
separator = '#';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (scope.pendingScope.length && !opts.allow) {
|
|
||||||
redirectUri += separator + Oauth3.querystringify({
|
|
||||||
error: 'access_denied'
|
|
||||||
, error_description: 'None of the permissions were accepted'
|
|
||||||
, error_uri: 'https://oauth3.org/docs/errors#access_denied'
|
|
||||||
, state: scope.appQuery.state
|
|
||||||
});
|
|
||||||
$window.location.href = redirectUri;
|
|
||||||
return;
|
|
||||||
}
|
|
Loading…
Reference in New Issue