433 lines
15 KiB
JavaScript
433 lines
15 KiB
JavaScript
/* global Promise */
|
|
(function (exports) {
|
|
'use strict';
|
|
|
|
var oauth3 = {};
|
|
|
|
var core = exports.OAUTH3_CORE || require('./oauth3.core.js');
|
|
|
|
oauth3.requests = {};
|
|
|
|
if ('undefined' !== typeof Promise) {
|
|
oauth3.PromiseA = Promise;
|
|
} else {
|
|
console.warn("[oauth3.js] Remember to call oauth3.providePromise(Promise) with a proper Promise implementation");
|
|
}
|
|
|
|
oauth3.providePromise = function (PromiseA) {
|
|
oauth3.PromiseA = PromiseA;
|
|
if (oauth3._testPromise) {
|
|
return oauth3._testPromise(PromiseA).then(function () {
|
|
oauth3.PromiseA = PromiseA;
|
|
});
|
|
}
|
|
|
|
oauth3.PromiseA = PromiseA;
|
|
return PromiseA.resolve();
|
|
};
|
|
|
|
// TODO move recase out
|
|
/*
|
|
oauth3._recaseRequest = function (recase, req) {
|
|
// convert JavaScript camelCase to oauth3/ruby snake_case
|
|
if (req.data && 'object' === typeof req.data) {
|
|
req.originalData = req.data;
|
|
req.data = recase.snakeCopy(req.data);
|
|
}
|
|
|
|
return req;
|
|
};
|
|
oauth3._recaseResponse = function (recase, resp) {
|
|
// convert oauth3/ruby snake_case to JavaScript camelCase
|
|
if (resp.data && 'object' === typeof resp.data) {
|
|
resp.originalData = resp.data;
|
|
resp.data = recase.camelCopy(resp.data);
|
|
}
|
|
return resp;
|
|
};
|
|
*/
|
|
|
|
oauth3.hooks = {
|
|
checkSession: function (preq, opts) {
|
|
if (!preq.session) {
|
|
console.warn('[oauth3.hooks.checkSession] no session');
|
|
return oauth3.PromiseA.resolve(null);
|
|
}
|
|
var freshness = oauth3.core.jwt.getFreshness(preq.session.token, opts.staletime);
|
|
console.info('[oauth3.hooks.checkSession] freshness', freshness, preq.session);
|
|
|
|
switch (freshness) {
|
|
case 'stale':
|
|
return oauth3.hooks.sessionStale(preq.session);
|
|
case 'expired':
|
|
return oauth3.hooks.sessionExpired(preq.session).then(function (newSession) {
|
|
preq.session = newSession;
|
|
return newSession;
|
|
});
|
|
//case 'fresh':
|
|
default:
|
|
return oauth3.PromiseA.resolve(preq.session);
|
|
}
|
|
}
|
|
, sessionStale: function (staleSession) {
|
|
console.info('[oauth3.hooks.sessionStale] called');
|
|
if (oauth3.hooks._stalePromise) {
|
|
return oauth3.PromiseA.resolve(staleSession);
|
|
}
|
|
|
|
oauth3.hooks._stalePromise = oauth3.requests.refreshToken(
|
|
staleSession.provider_uri
|
|
, { client_uri: staleSession.client_uri
|
|
, session: staleSession
|
|
, debug: staleSession.debug
|
|
}
|
|
).then(function (newSession) {
|
|
oauth3.hooks._stalePromise = null;
|
|
return newSession; // oauth3.hooks.refreshSession(staleSession, newSession);
|
|
}, function () {
|
|
oauth3.hooks._stalePromise = null;
|
|
});
|
|
|
|
return oauth3.PromiseA.resolve(staleSession);
|
|
}
|
|
, sessionExpired: function (expiredSession) {
|
|
console.info('[oauth3.hooks.sessionExpired] called');
|
|
return oauth3.requests.refreshToken(
|
|
expiredSession.provider_uri
|
|
, { client_uri: expiredSession.client_uri
|
|
, session: expiredSession
|
|
, debug: expiredSession.debug
|
|
}
|
|
).then(function (newSession) {
|
|
return newSession; // oauth3.hooks.refreshSession(expiredSession, newSession);
|
|
});
|
|
}
|
|
, refreshSession: function (oldSession, newSession) {
|
|
var providerUri = oldSession.provider_uri;
|
|
var clientUri = oldSession.client_uri;
|
|
|
|
console.info('[oauth3.hooks.refreshSession] oldSession', JSON.parse(JSON.stringify(oldSession)));
|
|
console.info('[oauth3.hooks.refreshSession] newSession', newSession);
|
|
Object.keys(oldSession).forEach(function (key) {
|
|
oldSession[key] = undefined;
|
|
});
|
|
Object.keys(newSession).forEach(function (key) {
|
|
oldSession[key] = newSession[key];
|
|
});
|
|
|
|
// info about the session of this API call
|
|
oldSession.provider_uri = providerUri; // aud
|
|
oldSession.client_uri = clientUri; // azp
|
|
|
|
// info about the newly-discovered token
|
|
oldSession.token = oldSession.meta = core.jwt.decode(oldSession.access_token).payload;
|
|
|
|
oldSession.token.sub = oldSession.token.sub || oldSession.token.acx.id;
|
|
oldSession.token.client_uri = clientUri;
|
|
oldSession.token.provider_uri = providerUri;
|
|
|
|
if (oldSession.refresh_token || oldSession.refreshToken) {
|
|
oldSession.refresh = core.jwt.decode(oldSession.refresh_token || oldSession.refreshToken).payload;
|
|
oldSession.refresh.sub = oldSession.refresh.sub || oldSession.refresh.acx.id;
|
|
oldSession.refresh.provider_uri = providerUri;
|
|
}
|
|
|
|
console.info('[oauth3.hooks.refreshSession] refreshedSession', oldSession);
|
|
|
|
// set for a set of audiences
|
|
return oauth3.PromiseA.resolve(oauth3.hooks.setSession(providerUri, oldSession));
|
|
}
|
|
, setSession: function (providerUri, newSession) {
|
|
if (!providerUri) {
|
|
console.error(new Error('no providerUri').stack);
|
|
}
|
|
providerUri = oauth3.core.normalizeUri(providerUri);
|
|
console.warn('[oauth3.hooks.setSession] PLEASE IMPLEMENT -- Your Fault');
|
|
console.warn(newSession);
|
|
if (!oauth3.hooks._sessions) { oauth3.hooks._sessions = {}; }
|
|
oauth3.hooks._sessions[providerUri] = newSession;
|
|
return newSession;
|
|
}
|
|
, getSession: function (providerUri) {
|
|
providerUri = oauth3.core.normalizeUri(providerUri);
|
|
console.warn('[oauth3.hooks.getSession] PLEASE IMPLEMENT -- Your Fault');
|
|
if (!oauth3.hooks._sessions) { oauth3.hooks._sessions = {}; }
|
|
return oauth3.hooks._sessions[providerUri];
|
|
}
|
|
, setDirectives: function (providerUri, directives) {
|
|
providerUri = oauth3.core.normalizeUri(providerUri);
|
|
console.warn('[oauth3.hooks.setDirectives] PLEASE IMPLEMENT -- Your Fault');
|
|
console.warn(directives);
|
|
if (!oauth3.hooks._directives) { oauth3.hooks._directives = {}; }
|
|
window.localStorage.setItem('directives-' + providerUri, JSON.stringify(directives));
|
|
oauth3.hooks._directives[providerUri] = directives;
|
|
return directives;
|
|
}
|
|
, getDirectives: function (providerUri) {
|
|
providerUri = oauth3.core.normalizeUri(providerUri);
|
|
console.warn('[oauth3.hooks.getDirectives] PLEASE IMPLEMENT -- Your Fault');
|
|
if (!oauth3.hooks._directives) { oauth3.hooks._directives = {}; }
|
|
return JSON.parse(window.localStorage.getItem('directives-' + providerUri) || '{}');
|
|
//return oauth3.hooks._directives[providerUri];
|
|
}
|
|
|
|
// Provider Only
|
|
, setGrants: function (clientUri, newGrants) {
|
|
clientUri = oauth3.core.normalizeUri(clientUri);
|
|
console.warn('[oauth3.hooks.setGrants] PLEASE IMPLEMENT -- Your Fault');
|
|
console.warn(newGrants);
|
|
if (!oauth3.hooks._grants) { oauth3.hooks._grants = {}; }
|
|
console.log('clientUri, newGrants');
|
|
console.log(clientUri, newGrants);
|
|
oauth3.hooks._grants[clientUri] = newGrants;
|
|
return newGrants;
|
|
}
|
|
, getGrants: function (clientUri) {
|
|
clientUri = oauth3.core.normalizeUri(clientUri);
|
|
console.warn('[oauth3.hooks.getGrants] PLEASE IMPLEMENT -- Your Fault');
|
|
if (!oauth3.hooks._grants) { oauth3.hooks._grants = {}; }
|
|
console.log('clientUri, existingGrants');
|
|
console.log(clientUri, oauth3.hooks._grants[clientUri]);
|
|
return oauth3.hooks._grants[clientUri];
|
|
}
|
|
};
|
|
|
|
// TODO simplify (nix recase)
|
|
oauth3.provideRequest = function (rawRequest, opts) {
|
|
opts = opts || {};
|
|
//var Recase = exports.Recase || require('recase');
|
|
// TODO make insensitive to providing exceptions
|
|
//var recase = Recase.create({ exceptions: {} });
|
|
|
|
function lintAndRequest(preq) {
|
|
function goGetHer() {
|
|
if (preq.session) {
|
|
// TODO check session.token.aud against preq.url to make sure they match
|
|
console.warn("[security] session audience checking has not been implemented yet (it's up to you to check)");
|
|
preq.headers = preq.headers || {};
|
|
preq.headers.Authorization = 'Bearer ' + (preq.session.access_token || preq.session.accessToken);
|
|
}
|
|
|
|
if (!oauth3._lintRequest) {
|
|
return rawRequest(preq);
|
|
}
|
|
return oauth3._lintRequest(preq, opts).then(function (preq) {
|
|
return rawRequest(preq);
|
|
});
|
|
}
|
|
|
|
if (!preq.session) {
|
|
return goGetHer();
|
|
}
|
|
|
|
console.warn('lintAndRequest checkSession', preq);
|
|
return oauth3.hooks.checkSession(preq, opts).then(goGetHer);
|
|
}
|
|
|
|
if (opts.rawCase) {
|
|
oauth3.request = lintAndRequest;
|
|
return;
|
|
}
|
|
|
|
// Wrap oauth3 api calls in snake_case / camelCase conversion
|
|
oauth3.request = function (req, opts) {
|
|
//console.log('[D] [oauth3 req.url]', req.url);
|
|
opts = opts || {};
|
|
|
|
if (opts.rawCase) {
|
|
return lintAndRequest(req, opts);
|
|
}
|
|
|
|
//req = oauth3._recaseRequest(recase, req);
|
|
return lintAndRequest(req, opts).then(function (res) {
|
|
//return oauth3._recaseResponse(recase, res);
|
|
return res;
|
|
});
|
|
};
|
|
|
|
/*
|
|
return oauth3._testRequest(request).then(function () {
|
|
oauth3.request = request;
|
|
});
|
|
*/
|
|
};
|
|
|
|
// TODO merge with regular token access point and new response_type=federated ?
|
|
oauth3.requests.clientToken = function (providerUri, opts) {
|
|
return oauth3.discover(providerUri, opts).then(function (directive) {
|
|
return oauth3.request(core.urls.grants(directive, opts)).then(function (grantsResult) {
|
|
return grantsResult.originalData || grantsResult.data;
|
|
});
|
|
});
|
|
};
|
|
oauth3.requests.grants = function (providerUri, opts) {
|
|
return oauth3.discover(providerUri, {
|
|
client_id: providerUri
|
|
, debug: opts.debug
|
|
}).then(function (directive) {
|
|
return oauth3.request(core.urls.grants(directive, opts)).then(function (grantsResult) {
|
|
if ('POST' === opts.method) {
|
|
// TODO this is clientToken
|
|
return grantsResult.originalData || grantsResult.data;
|
|
}
|
|
|
|
var grants = grantsResult.originalData || grantsResult.data;
|
|
// TODO
|
|
if (grants.error) {
|
|
return oauth3.PromiseA.reject(oauth3.core.formatError(grants.error));
|
|
}
|
|
|
|
console.warn('requests.grants', grants);
|
|
|
|
oauth3.hooks.setGrants(opts.client_id + '-client', grants.client);
|
|
grants.grants.forEach(function (grant) {
|
|
var clientId = grant.client_id || grant.oauth_client_id || grant.oauthClientId;
|
|
// TODO should save as an array
|
|
oauth3.hooks.setGrants(clientId, [ grant ]);
|
|
});
|
|
|
|
return {
|
|
client: oauth3.hooks.getGrants(opts.client_id + '-client')
|
|
, grants: oauth3.hooks.getGrants(opts.client_id) || []
|
|
};
|
|
});
|
|
});
|
|
};
|
|
oauth3.requests.loginCode = function (providerUri, opts) {
|
|
return oauth3.discover(providerUri, opts).then(function (directive) {
|
|
var prequest = core.urls.loginCode(directive, opts);
|
|
|
|
return oauth3.request(prequest).then(function (res) {
|
|
// result = { uuid, expires_at }
|
|
return {
|
|
otpUuid: res.data.uuid
|
|
, otpExpires: res.data.expires_at
|
|
};
|
|
});
|
|
});
|
|
};
|
|
oauth3.loginCode = oauth3.requests.loginCode;
|
|
|
|
oauth3.requests.resourceOwnerPassword = function (providerUri, opts) {
|
|
//var scope = opts.scope;
|
|
//var appId = opts.appId;
|
|
return oauth3.discover(providerUri, opts).then(function (directive) {
|
|
var prequest = core.urls.resourceOwnerPassword(directive, opts);
|
|
|
|
return oauth3.request(prequest).then(function (req) {
|
|
var data = (req.originalData || req.data);
|
|
data.provider_uri = providerUri;
|
|
if (data.error) {
|
|
return oauth3.PromiseA.reject(oauth3.core.formatError(providerUri, data.error));
|
|
}
|
|
return oauth3.hooks.refreshSession(
|
|
opts.session || { provider_uri: providerUri, client_uri: opts.client_uri || opts.clientUri }
|
|
, data
|
|
);
|
|
});
|
|
});
|
|
};
|
|
oauth3.resourceOwnerPassword = oauth3.requests.resourceOwnerPassword;
|
|
|
|
oauth3.requests.refreshToken = function (providerUri, opts) {
|
|
console.info('[oauth3.requests.refreshToken] called', providerUri, opts);
|
|
return oauth3.discover(providerUri, opts).then(function (directive) {
|
|
var prequest = core.urls.refreshToken(directive, opts);
|
|
|
|
return oauth3.request(prequest).then(function (req) {
|
|
var data = (req.originalData || req.data);
|
|
data.provider_uri = providerUri;
|
|
if (data.error) {
|
|
return oauth3.PromiseA.reject(oauth3.core.formatError(providerUri, data));
|
|
}
|
|
return oauth3.hooks.refreshSession(opts, data);
|
|
});
|
|
});
|
|
};
|
|
oauth3.refreshToken = oauth3.requests.refreshToken;
|
|
|
|
// TODO It'll be very interesting to see if we can do some browser popup stuff from the CLI
|
|
oauth3.requests._error_description = 'Not Implemented: Please override by including <script src="oauth3.browser.js"></script>';
|
|
oauth3.requests.authorizationRedirect = function (/*providerUri, opts*/) {
|
|
throw new Error(oauth3.requests._error_description);
|
|
};
|
|
oauth3.requests.implicitGrant = function (/*providerUri, opts*/) {
|
|
throw new Error(oauth3.requests._error_description);
|
|
};
|
|
oauth3.requests.logout = function (/*providerUri, opts*/) {
|
|
throw new Error(oauth3.requests._error_description);
|
|
};
|
|
|
|
oauth3.login = function (providerUri, opts) {
|
|
// Four styles of login:
|
|
// * background (hidden iframe)
|
|
// * iframe (visible iframe, needs border color and width x height params)
|
|
// * popup (needs width x height and positioning? params)
|
|
// * window (params?)
|
|
|
|
// Two strategies
|
|
// * authorization_redirect (to server authorization code)
|
|
// * implicit_grant (default, browser-only)
|
|
// If both are selected, implicit happens first and then the other happens in background
|
|
|
|
var promise;
|
|
|
|
if (opts.username || opts.password) {
|
|
/* jshint ignore:start */
|
|
// ingore "confusing use of !"
|
|
if (!opts.username !== !(opts.password || opts.otp)) {
|
|
throw new Error("you did not specify both username and password");
|
|
}
|
|
/* jshint ignore:end */
|
|
|
|
return oauth3.requests.resourceOwnerPassword(providerUri, opts).then(function (resp) {
|
|
if (!resp || !resp.data) {
|
|
var err = new Error("bad response");
|
|
err.response = resp;
|
|
err.data = resp && resp.data || undefined;
|
|
return oauth3.PromiseA.reject(err);
|
|
}
|
|
return resp.data;
|
|
});
|
|
}
|
|
|
|
// TODO support dual-strategy login
|
|
// by default, always get implicitGrant (for client)
|
|
// and optionally do authorizationCode (for server session)
|
|
if ('background' === opts.type || opts.background) {
|
|
opts.type = 'background';
|
|
opts.background = true;
|
|
}
|
|
else {
|
|
opts.type = 'popup';
|
|
opts.popup = true;
|
|
}
|
|
if (opts.authorizationRedirect) {
|
|
promise = oauth3.requests.authorizationRedirect(providerUri, opts);
|
|
}
|
|
else {
|
|
promise = oauth3.requests.implicitGrant(providerUri, opts);
|
|
}
|
|
|
|
return promise;
|
|
};
|
|
|
|
oauth3.backgroundLogin = function (providerUri, opts) {
|
|
opts = opts || {};
|
|
opts.type = 'background';
|
|
return oauth3.login(providerUri, opts);
|
|
};
|
|
|
|
oauth3.core = core;
|
|
oauth3.querystringify = core.querystringify;
|
|
oauth3.scopestringify = core.stringifyscope;
|
|
oauth3.stringifyscope = core.stringifyscope;
|
|
|
|
exports.OAUTH3 = oauth3.oauth3 = oauth3.OAUTH3 = oauth3;
|
|
exports.oauth3 = exports.OAUTH3;
|
|
|
|
if ('undefined' !== typeof module) {
|
|
module.exports = oauth3;
|
|
}
|
|
}('undefined' !== typeof exports ? exports : window));
|