oauth3.js/prefactor/oauth3.core.provider.js

303 lines
9.5 KiB
JavaScript

;(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));