refactor browser-only code
This commit is contained in:
parent
efd813a107
commit
7ae4d83cfe
|
@ -0,0 +1,234 @@
|
||||||
|
;(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.appUrl should be used');
|
||||||
|
return window.location.protocol
|
||||||
|
+ '//' + window.location.host
|
||||||
|
+ (window.location.pathname).replace(/\/?$/, '')
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
var browser = exports.OAUTH3_BROWSER = {
|
||||||
|
discover: function (providerUri, opts) {
|
||||||
|
opts = opts || {};
|
||||||
|
opts.debug = true;
|
||||||
|
console.log('discover providerUri', providerUri);
|
||||||
|
providerUri = OAUTH3_CORE.normalizeUrl(providerUri);
|
||||||
|
var discObj = OAUTH3_CORE.discover(providerUri, { appUrl: (opts.appUrl || getDefaultAppUrl()), debug: opts.debug });
|
||||||
|
|
||||||
|
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)));
|
||||||
|
directives.issuer = directives.issuer || OAUTH3_CORE.normalizeUrl(providerUri);
|
||||||
|
return directives;
|
||||||
|
}, function (err) {
|
||||||
|
return OAUTH3.PromiseA.reject(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
, frameRequest: function (url, state, opts) {
|
||||||
|
var promise;
|
||||||
|
|
||||||
|
if ('background' === opts.type) {
|
||||||
|
promise = browser.insertIframe(url, state, opts);
|
||||||
|
} else if ('popup' === opts.type) {
|
||||||
|
promise = browser.openWindow(url, state, opts);
|
||||||
|
} else {
|
||||||
|
throw new Error("login framing method not specified or not type yet implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
return promise.then(function (params) {
|
||||||
|
var err;
|
||||||
|
|
||||||
|
if (params.error || params.error_description) {
|
||||||
|
err = new Error(params.error_description || "Unknown response error");
|
||||||
|
err.code = params.error || "E_UKNOWN_ERROR";
|
||||||
|
err.params = params;
|
||||||
|
return OAUTH3.PromiseA.reject(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
return params;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
, insertIframe: function (url, state, opts) {
|
||||||
|
opts = opts || {};
|
||||||
|
var promise = new OAUTH3.PromiseA(function (resolve, reject) {
|
||||||
|
var tok;
|
||||||
|
var $iframe;
|
||||||
|
|
||||||
|
function cleanup() {
|
||||||
|
delete window['--oauth3-callback-' + state];
|
||||||
|
$iframe.remove();
|
||||||
|
clearTimeout(tok);
|
||||||
|
tok = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
window['--oauth3-callback-' + state] = function (params) {
|
||||||
|
console.info('[iframe] complete', params);
|
||||||
|
resolve(params);
|
||||||
|
cleanup();
|
||||||
|
};
|
||||||
|
|
||||||
|
tok = setTimeout(function () {
|
||||||
|
var err = new Error("the iframe request did not complete within 15 seconds");
|
||||||
|
err.code = "E_TIMEOUT";
|
||||||
|
reject(err);
|
||||||
|
cleanup();
|
||||||
|
}, opts.timeout || 15000);
|
||||||
|
|
||||||
|
// TODO hidden / non-hidden (via directive even)
|
||||||
|
var framesrc = '<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>';
|
||||||
|
$iframe = $(framesrc);
|
||||||
|
|
||||||
|
$('body').append($iframe);
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO periodically garbage collect expired handlers from window object
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
, openWindow: function (url, state, opts) {
|
||||||
|
var promise = new OAUTH3.PromiseA(function (resolve, reject) {
|
||||||
|
var winref;
|
||||||
|
var tok;
|
||||||
|
|
||||||
|
function cleanup() {
|
||||||
|
delete window['--oauth3-callback-' + state];
|
||||||
|
clearTimeout(tok);
|
||||||
|
tok = null;
|
||||||
|
// this is last in case the window self-closes synchronously
|
||||||
|
// (should never happen, but that's a negotiable implementation detail)
|
||||||
|
//winref.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
window['--oauth3-callback-' + state] = function (params) {
|
||||||
|
//console.info('[popup] (or window) complete', params);
|
||||||
|
resolve(params);
|
||||||
|
cleanup();
|
||||||
|
};
|
||||||
|
|
||||||
|
tok = setTimeout(function () {
|
||||||
|
var err = new Error("the windowed request did not complete within 3 minutes");
|
||||||
|
err.code = "E_TIMEOUT";
|
||||||
|
reject(err);
|
||||||
|
cleanup();
|
||||||
|
}, opts.timeout || 3 * 60 * 1000);
|
||||||
|
|
||||||
|
// TODO allow size changes (via directive even)
|
||||||
|
winref = window.open(
|
||||||
|
url
|
||||||
|
, 'oauth3-login-' + state
|
||||||
|
, 'height=' + (opts.height || 720) + ',width=' + (opts.width || 620)
|
||||||
|
);
|
||||||
|
if (!winref) {
|
||||||
|
reject("TODO: open the iframe first and discover oauth3 directives before popup");
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO periodically garbage collect expired handlers from window object
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Logins
|
||||||
|
//
|
||||||
|
, logins: {
|
||||||
|
authorizationRedirect: function (providerUri, opts) {
|
||||||
|
// TODO get own directives
|
||||||
|
|
||||||
|
return OAUTH3.discover(providerUri, opts).then(function (directive) {
|
||||||
|
var prequest = OAUTH3_CORE.authorizationRedirect(
|
||||||
|
directive
|
||||||
|
, opts
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('[DEBUG] [core] authorizationRedirect URL:', prequest);
|
||||||
|
|
||||||
|
if (!prequest.state) {
|
||||||
|
throw new Error("[Devolper Error] [authorization redirect] prequest.state is empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
return browser.frameRequest(prequest.url, prequest.state, opts);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
, implicitGrant: function (providerUri, opts) {
|
||||||
|
// TODO OAuth3 provider should use the redirect URI as the appId?
|
||||||
|
return OAUTH3.discover(providerUri, opts).then(function (directive) {
|
||||||
|
var prequest = OAUTH3_CORE.implicitGrant(
|
||||||
|
directive
|
||||||
|
// TODO OAuth3 provider should referrer / referer / origin as the appId?
|
||||||
|
, opts
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('[DEBUG] [core] implicitGrant URL', prequest);
|
||||||
|
|
||||||
|
if (!prequest.state) {
|
||||||
|
throw new Error("[Devolper Error] [implicit grant] prequest.state is empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
return browser.frameRequest(prequest.url, prequest.state, opts);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
, logout: function (providerUri, opts) {
|
||||||
|
opts = opts || {};
|
||||||
|
|
||||||
|
return OAUTH3.discover(providerUri, opts).then(function (directive) {
|
||||||
|
var prequest = OAUTH3_CORE.logout(
|
||||||
|
directive
|
||||||
|
, opts
|
||||||
|
);
|
||||||
|
// Oauth3.init({ logout: function () {} });
|
||||||
|
//return Oauth3.logout();
|
||||||
|
|
||||||
|
var redirectUri = opts.redirectUri
|
||||||
|
|| (window.location.protocol + '//' + (window.location.host + window.location.pathname) + 'oauth3.html')
|
||||||
|
;
|
||||||
|
var params = {
|
||||||
|
// logout=true for all logins/accounts
|
||||||
|
// logout=app-scoped-login-id for a single login
|
||||||
|
action: 'logout'
|
||||||
|
// TODO specify specific accounts / logins to delete from session
|
||||||
|
, accounts: true
|
||||||
|
, logins: true
|
||||||
|
, redirect_uri: redirectUri
|
||||||
|
, state: prequest.state
|
||||||
|
};
|
||||||
|
|
||||||
|
//console.log('DEBUG oauth3.logout NIX insertIframe');
|
||||||
|
//console.log(url, params.redirect_uri);
|
||||||
|
//console.log(state);
|
||||||
|
//console.log(params); // redirect_uri
|
||||||
|
//console.log(opts);
|
||||||
|
|
||||||
|
if (prequest.url === params.redirect_uri) {
|
||||||
|
return OAUTH3.PromiseA.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
prequest.url += '#' + OAUTH3_CORE.querystringify(params);
|
||||||
|
|
||||||
|
return OAUTH3.insertIframe(prequest.url, prequest.state, opts);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.keys(browser).forEach(function (key) {
|
||||||
|
OAUTH3[key] = browser[key];
|
||||||
|
});
|
||||||
|
|
||||||
|
}('undefined' !== typeof exports ? exports : window));
|
|
@ -0,0 +1,42 @@
|
||||||
|
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,4 +1,4 @@
|
||||||
(function (exports) {
|
;(function (exports) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// NOTE: we assume that directive.provider_uri exists
|
// NOTE: we assume that directive.provider_uri exists
|
||||||
|
@ -75,6 +75,35 @@
|
||||||
return argsParsed;
|
return argsParsed;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
core.formatError = function (providerUri, params) {
|
||||||
|
var err = new Error(params.error_description || "Unknown error when discoving provider '" + providerUri + "'");
|
||||||
|
err.uri = params.error_uri;
|
||||||
|
err.code = params.error;
|
||||||
|
return err;
|
||||||
|
};
|
||||||
|
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(/\/?$/, '')
|
||||||
|
;
|
||||||
|
};
|
||||||
|
|
||||||
core.discover = function (providerUri, opts) {
|
core.discover = function (providerUri, opts) {
|
||||||
if (!providerUri) {
|
if (!providerUri) {
|
||||||
throw new Error("cannot discover without providerUri");
|
throw new Error("cannot discover without providerUri");
|
||||||
|
@ -510,6 +539,40 @@
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
core.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 = exports.OAUTH3 || { core: core };
|
||||||
exports.OAUTH3_CORE = core.OAUTH3_CORE = core;
|
exports.OAUTH3_CORE = core.OAUTH3_CORE = core;
|
||||||
|
|
||||||
|
|
356
oauth3.js
356
oauth3.js
|
@ -7,14 +7,6 @@
|
||||||
|
|
||||||
var core = exports.OAUTH3_CORE || require('./oauth3.core.js');
|
var core = exports.OAUTH3_CORE || require('./oauth3.core.js');
|
||||||
|
|
||||||
function getDefaultAppUrl() {
|
|
||||||
console.warn('[deprecated] using window.location.{protocol, host, pathname} when opts.appUrl should be used');
|
|
||||||
return window.location.protocol
|
|
||||||
+ '//' + window.location.host
|
|
||||||
+ (window.location.pathname).replace(/\/?$/, '')
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
oauth3.requests = logins;
|
oauth3.requests = logins;
|
||||||
|
|
||||||
if ('undefined' !== typeof Promise) {
|
if ('undefined' !== typeof Promise) {
|
||||||
|
@ -149,45 +141,6 @@
|
||||||
*/
|
*/
|
||||||
};
|
};
|
||||||
|
|
||||||
logins.authorizationRedirect = function (providerUri, opts) {
|
|
||||||
// TODO get own directives
|
|
||||||
|
|
||||||
return oauth3.discover(providerUri, opts).then(function (directive) {
|
|
||||||
var prequest = core.authorizationRedirect(
|
|
||||||
directive
|
|
||||||
, opts.authorizationRedirect
|
|
||||||
, opts
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log('[DEBUG] [core] authorizationRedirect URL:', prequest);
|
|
||||||
|
|
||||||
if (!prequest.state) {
|
|
||||||
throw new Error("[Devolper Error] [authorization redirect] prequest.state is empty");
|
|
||||||
}
|
|
||||||
|
|
||||||
return oauth3.frameRequest(prequest.url, prequest.state, opts);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
logins.implicitGrant = function (providerUri, opts) {
|
|
||||||
// TODO OAuth3 provider should use the redirect URI as the appId?
|
|
||||||
return oauth3.discover(providerUri, opts).then(function (directive) {
|
|
||||||
var prequest = core.implicitGrant(
|
|
||||||
directive
|
|
||||||
// TODO OAuth3 provider should referrer / referer / origin as the appId?
|
|
||||||
, opts
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log('[DEBUG] [core] implicitGrant URL', prequest);
|
|
||||||
|
|
||||||
if (!prequest.state) {
|
|
||||||
throw new Error("[Devolper Error] [implicit grant] prequest.state is empty");
|
|
||||||
}
|
|
||||||
|
|
||||||
return oauth3.frameRequest(prequest.url, prequest.state, opts);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
oauth3.loginCode = function (providerUri, opts) {
|
oauth3.loginCode = function (providerUri, opts) {
|
||||||
return oauth3.discover(providerUri, opts).then(function (directive) {
|
return oauth3.discover(providerUri, opts).then(function (directive) {
|
||||||
var prequest = core.loginCode(directive, opts);
|
var prequest = core.loginCode(directive, opts);
|
||||||
|
@ -205,19 +158,12 @@
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
logins.resourceOwnerPassword = function (providerUri, username, passphrase, opts) {
|
oauth3.resourceOwnerPassword = function (providerUri, username, passphrase, opts) {
|
||||||
console.log('DEBUG logins.resourceOwnerPassword opts', opts);
|
console.log('DEBUG oauth3.resourceOwnerPassword opts', opts);
|
||||||
//var scope = opts.scope;
|
//var scope = opts.scope;
|
||||||
//var appId = opts.appId;
|
//var appId = opts.appId;
|
||||||
return oauth3.discover(providerUri, opts).then(function (directive) {
|
return oauth3.discover(providerUri, opts).then(function (directive) {
|
||||||
var prequest = core.resourceOwnerPassword(
|
var prequest = core.resourceOwnerPassword(directive, opts);
|
||||||
directive
|
|
||||||
, username
|
|
||||||
, passphrase
|
|
||||||
, opts // secret: proofstr || otpCode, totp: totpToken, otp: otpCode, otpUuid: otpUuuid
|
|
||||||
//, scope
|
|
||||||
//, appId
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log('[DEBUG] [core] resourceOwnerPassword URL', prequest);
|
console.log('[DEBUG] [core] resourceOwnerPassword URL', prequest);
|
||||||
|
|
||||||
|
@ -243,29 +189,16 @@
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
oauth3.frameRequest = function (url, state, opts) {
|
// TODO It'll be very interesting to see if we can do some browser popup stuff from the CLI
|
||||||
var promise;
|
logins._error_description = 'Not Implemented: Please override by including <script src="oauth3.browser.js"></script>';
|
||||||
|
logins.authorizationRedirect = function (/*providerUri, opts*/) {
|
||||||
if ('background' === opts.type) {
|
throw new Error(logins._error_description);
|
||||||
promise = oauth3.insertIframe(url, state, opts);
|
};
|
||||||
} else if ('popup' === opts.type) {
|
logins.implicitGrant = function (/*providerUri, opts*/) {
|
||||||
promise = oauth3.openWindow(url, state, opts);
|
throw new Error(logins._error_description);
|
||||||
} else {
|
};
|
||||||
throw new Error("login framing method not specified or not type yet implemented");
|
logins.logout = function (/*providerUri, opts*/) {
|
||||||
}
|
throw new Error(logins._error_description);
|
||||||
|
|
||||||
return promise.then(function (params) {
|
|
||||||
var err;
|
|
||||||
|
|
||||||
if (params.error || params.error_description) {
|
|
||||||
err = new Error(params.error_description || "Unknown response error");
|
|
||||||
err.code = params.error || "E_UKNOWN_ERROR";
|
|
||||||
err.params = params;
|
|
||||||
return oauth3.PromiseA.reject(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
return params;
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
oauth3.login = function (providerUri, opts) {
|
oauth3.login = function (providerUri, opts) {
|
||||||
|
@ -298,7 +231,7 @@
|
||||||
delete opts.username;
|
delete opts.username;
|
||||||
delete opts.password;
|
delete opts.password;
|
||||||
|
|
||||||
return logins.resourceOwnerPassword(providerUri, username, password, opts).then(function (resp) {
|
return oauth3.resourceOwnerPassword(providerUri, username, password, opts).then(function (resp) {
|
||||||
if (!resp || !resp.data) {
|
if (!resp || !resp.data) {
|
||||||
var err = new Error("bad response");
|
var err = new Error("bad response");
|
||||||
err.response = resp;
|
err.response = resp;
|
||||||
|
@ -336,267 +269,6 @@
|
||||||
return oauth3.login(providerUri, opts);
|
return oauth3.login(providerUri, opts);
|
||||||
};
|
};
|
||||||
|
|
||||||
oauth3.insertIframe = function (url, state, opts) {
|
|
||||||
opts = opts || {};
|
|
||||||
var promise = new oauth3.PromiseA(function (resolve, reject) {
|
|
||||||
var tok;
|
|
||||||
var $iframe;
|
|
||||||
|
|
||||||
function cleanup() {
|
|
||||||
delete window['--oauth3-callback-' + state];
|
|
||||||
$iframe.remove();
|
|
||||||
clearTimeout(tok);
|
|
||||||
tok = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
window['--oauth3-callback-' + state] = function (params) {
|
|
||||||
//console.info('[iframe] complete', params);
|
|
||||||
resolve(params);
|
|
||||||
cleanup();
|
|
||||||
};
|
|
||||||
|
|
||||||
tok = setTimeout(function () {
|
|
||||||
var err = new Error("the iframe request did not complete within 15 seconds");
|
|
||||||
err.code = "E_TIMEOUT";
|
|
||||||
reject(err);
|
|
||||||
cleanup();
|
|
||||||
}, opts.timeout || 15000);
|
|
||||||
|
|
||||||
// TODO hidden / non-hidden (via directive even)
|
|
||||||
$iframe = $(
|
|
||||||
'<iframe src="' + url
|
|
||||||
//+ '" width="800px" height="800px" style="opacity: 0.8;" frameborder="1"></iframe>'
|
|
||||||
+ '" width="1px" height="1px" frameborder="0"></iframe>'
|
|
||||||
);
|
|
||||||
|
|
||||||
$('body').append($iframe);
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO periodically garbage collect expired handlers from window object
|
|
||||||
return promise;
|
|
||||||
};
|
|
||||||
|
|
||||||
oauth3.openWindow = function (url, state, opts) {
|
|
||||||
var promise = new oauth3.PromiseA(function (resolve, reject) {
|
|
||||||
var winref;
|
|
||||||
var tok;
|
|
||||||
|
|
||||||
function cleanup() {
|
|
||||||
delete window['--oauth3-callback-' + state];
|
|
||||||
clearTimeout(tok);
|
|
||||||
tok = null;
|
|
||||||
// this is last in case the window self-closes synchronously
|
|
||||||
// (should never happen, but that's a negotiable implementation detail)
|
|
||||||
//winref.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
window['--oauth3-callback-' + state] = function (params) {
|
|
||||||
//console.info('[popup] (or window) complete', params);
|
|
||||||
resolve(params);
|
|
||||||
cleanup();
|
|
||||||
};
|
|
||||||
|
|
||||||
tok = setTimeout(function () {
|
|
||||||
var err = new Error("the windowed request did not complete within 3 minutes");
|
|
||||||
err.code = "E_TIMEOUT";
|
|
||||||
reject(err);
|
|
||||||
cleanup();
|
|
||||||
}, opts.timeout || 3 * 60 * 1000);
|
|
||||||
|
|
||||||
// TODO allow size changes (via directive even)
|
|
||||||
winref = window.open(url, 'oauth3-login-' + state, 'height=720,width=620');
|
|
||||||
if (!winref) {
|
|
||||||
reject("TODO: open the iframe first and discover oauth3 directives before popup");
|
|
||||||
cleanup();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO periodically garbage collect expired handlers from window object
|
|
||||||
return promise;
|
|
||||||
};
|
|
||||||
|
|
||||||
oauth3.logout = function (providerUri, opts) {
|
|
||||||
opts = opts || {};
|
|
||||||
|
|
||||||
// Oauth3.init({ logout: function () {} });
|
|
||||||
//return Oauth3.logout();
|
|
||||||
|
|
||||||
var state = parseInt(Math.random().toString().replace('0.', ''), 10).toString('36');
|
|
||||||
var url = providerUri.replace(/\/$/, '') + (opts.providerOauth3Html || '/oauth3.html');
|
|
||||||
var redirectUri = opts.redirectUri
|
|
||||||
|| (window.location.protocol + '//' + (window.location.host + window.location.pathname) + 'oauth3.html')
|
|
||||||
;
|
|
||||||
var params = {
|
|
||||||
// logout=true for all logins/accounts
|
|
||||||
// logout=app-scoped-login-id for a single login
|
|
||||||
action: 'logout'
|
|
||||||
// TODO specify specific accounts / logins to delete from session
|
|
||||||
, accounts: true
|
|
||||||
, logins: true
|
|
||||||
, redirect_uri: redirectUri
|
|
||||||
, state: state
|
|
||||||
};
|
|
||||||
|
|
||||||
//console.log('DEBUG oauth3.logout NIX insertIframe');
|
|
||||||
//console.log(url, params.redirect_uri);
|
|
||||||
//console.log(state);
|
|
||||||
//console.log(params); // redirect_uri
|
|
||||||
//console.log(opts);
|
|
||||||
|
|
||||||
if (url === params.redirect_uri) {
|
|
||||||
return oauth3.PromiseA.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
url += '#' + core.querystringify(params);
|
|
||||||
|
|
||||||
return oauth3.insertIframe(url, state, opts);
|
|
||||||
};
|
|
||||||
|
|
||||||
oauth3.createState = function () {
|
|
||||||
// TODO mo' betta' random function
|
|
||||||
// maybe gather some entropy from mouse / keyboard events?
|
|
||||||
// (probably not, just use webCrypto or be sucky)
|
|
||||||
return parseInt(Math.random().toString().replace('0.', ''), 10).toString('36');
|
|
||||||
};
|
|
||||||
|
|
||||||
oauth3.normalizeProviderUri = function (providerUri) {
|
|
||||||
// tested with
|
|
||||||
// example.com
|
|
||||||
// example.com/
|
|
||||||
// http://example.com
|
|
||||||
// https://example.com/
|
|
||||||
providerUri = providerUri
|
|
||||||
.replace(/^(https?:\/\/)?/, 'https://')
|
|
||||||
.replace(/\/?$/, '')
|
|
||||||
;
|
|
||||||
|
|
||||||
return providerUri;
|
|
||||||
};
|
|
||||||
|
|
||||||
oauth3._discoverHelper = function (providerUri, opts) {
|
|
||||||
return oauth3._discoverHelperNew(providerUri, opts).then(function () {
|
|
||||||
}, function () {
|
|
||||||
console.warn('[directives] fallback to old /oauth3.html');
|
|
||||||
return oauth3._discoverHelperOld(providerUri, opts);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
oauth3._discoverHelperNew = function (providerUri, opts) {
|
|
||||||
opts = opts || {};
|
|
||||||
var state = oauth3.createState();
|
|
||||||
var discObj = oauth3.core.discover(providerUri, { state: state, appUrl: (opts.appUrl || getDefaultAppUrl()) });
|
|
||||||
|
|
||||||
return oauth3.insertIframe(discObj.url, state, opts).then(function (directives) {
|
|
||||||
return directives;
|
|
||||||
}, function (err) {
|
|
||||||
return oauth3.PromiseA.reject(err);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
oauth3._discoverHelperOld = function (providerUri, opts) {
|
|
||||||
opts = opts || {};
|
|
||||||
var state = oauth3.createState();
|
|
||||||
var params;
|
|
||||||
var url;
|
|
||||||
|
|
||||||
params = {
|
|
||||||
action: 'directives'
|
|
||||||
, state: state
|
|
||||||
// TODO this should be configurable (i.e. I want a dev vs production oauth3.html)
|
|
||||||
, redirect_uri: window.location.protocol + '//' + window.location.host
|
|
||||||
+ (window.location.pathname + '/oauth3.html').replace(/\/\//, '/')
|
|
||||||
};
|
|
||||||
|
|
||||||
url = providerUri + '/oauth3.html#' + core.querystringify(params);
|
|
||||||
|
|
||||||
return oauth3.insertIframe(url, state, opts).then(function (directives) {
|
|
||||||
return directives;
|
|
||||||
}, function (err) {
|
|
||||||
return oauth3.PromiseA.reject(err);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
oauth3.discover = function (providerUri, opts) {
|
|
||||||
opts = opts || {};
|
|
||||||
|
|
||||||
console.log('DEBUG oauth3.discover', providerUri);
|
|
||||||
console.log(opts);
|
|
||||||
if (opts.directives) {
|
|
||||||
return oauth3.PromiseA.resolve(opts.directives);
|
|
||||||
}
|
|
||||||
|
|
||||||
var promise;
|
|
||||||
var promise2;
|
|
||||||
var directives;
|
|
||||||
var updatedAt;
|
|
||||||
var fresh;
|
|
||||||
|
|
||||||
providerUri = oauth3.normalizeProviderUri(providerUri);
|
|
||||||
try {
|
|
||||||
directives = JSON.parse(localStorage.getItem('oauth3.' + providerUri + '.directives'));
|
|
||||||
console.log('DEBUG oauth3.discover cache', directives);
|
|
||||||
updatedAt = localStorage.getItem('oauth3.' + providerUri + '.directives.updated_at');
|
|
||||||
console.log('DEBUG oauth3.discover updatedAt', updatedAt);
|
|
||||||
updatedAt = new Date(updatedAt).valueOf();
|
|
||||||
console.log('DEBUG oauth3.discover updatedAt', updatedAt);
|
|
||||||
} catch(e) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
|
|
||||||
fresh = (Date.now() - updatedAt) < (24 * 60 * 60 * 1000);
|
|
||||||
|
|
||||||
if (directives) {
|
|
||||||
promise = oauth3.PromiseA.resolve(directives);
|
|
||||||
|
|
||||||
if (fresh) {
|
|
||||||
//console.log('[local] [fresh directives]', directives);
|
|
||||||
return promise;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
promise2 = oauth3._discoverHelper(providerUri, opts).then(function (params) {
|
|
||||||
console.log('DEBUG oauth3._discoverHelper', params);
|
|
||||||
var err;
|
|
||||||
|
|
||||||
if (!params.directives) {
|
|
||||||
err = new Error(params.error_description || "Unknown error when discoving provider '" + providerUri + "'");
|
|
||||||
err.code = params.error || "E_UNKNOWN_ERROR";
|
|
||||||
return oauth3.PromiseA.reject(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
directives = JSON.parse(atob(params.directives));
|
|
||||||
console.log('DEBUG oauth3._discoverHelper directives', directives);
|
|
||||||
} catch(e) {
|
|
||||||
err = new Error(params.error_description || "could not parse directives for provider '" + providerUri + "'");
|
|
||||||
err.code = params.error || "E_PARSE_DIRECTIVE";
|
|
||||||
return oauth3.PromiseA.reject(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
(directives.authorization_dialog && directives.authorization_dialog.url)
|
|
||||||
|| (directives.access_token && directives.access_token.url)
|
|
||||||
) {
|
|
||||||
// TODO lint directives
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return promise || promise2;
|
|
||||||
};
|
|
||||||
|
|
||||||
oauth3.core = core;
|
oauth3.core = core;
|
||||||
oauth3.querystringify = core.querystringify;
|
oauth3.querystringify = core.querystringify;
|
||||||
oauth3.scopestringify = core.stringifyscope;
|
oauth3.scopestringify = core.stringifyscope;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
|
|
||||||
// TODO move to a test / lint suite?
|
// TODO move to a test / lint suite?
|
||||||
oauth3._testPromise = function (PromiseA) {
|
oauth3._lintPromise = function (PromiseA) {
|
||||||
var promise;
|
var promise;
|
||||||
var x = 1;
|
var x = 1;
|
||||||
|
|
||||||
|
@ -48,3 +49,44 @@
|
||||||
x = 2;
|
x = 2;
|
||||||
return promise;
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in New Issue