296 lines
8.9 KiB
JavaScript
296 lines
8.9 KiB
JavaScript
(function (exports) {
|
|
'use strict';
|
|
|
|
// NOTE: we assume that directive.provider_uri exists
|
|
|
|
var core = {};
|
|
|
|
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) {
|
|
if ('scope' === key) {
|
|
params[key] = core.stringifyscope(params[key]);
|
|
}
|
|
qs.push(encodeURIComponent(key) + '=' + encodeURIComponent(params[key]));
|
|
});
|
|
|
|
return qs.join('&');
|
|
};
|
|
|
|
core.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=`Math.random()`
|
|
// &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.authorizationRedirect = function (directive, authorizationRedirect, 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=`Math.random()`
|
|
// &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 state = Math.random().toString().replace(/^0\./, '');
|
|
var params = {};
|
|
var slimProviderUri = encodeURIComponent(providerUri.replace(/^(https?|spdy):\/\//, ''));
|
|
|
|
params.state = state;
|
|
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 = 'https://' + window.location.host
|
|
+ '/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: state // this becomes browser_state
|
|
, params: params // includes scope, final redirect_uri?
|
|
};
|
|
};
|
|
|
|
core.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=`Math.random()`
|
|
// &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.redirectUri;
|
|
var scope = opts.scope || directive.authn_scope;
|
|
var clientId = opts.appId;
|
|
var args = directive[type];
|
|
var uri = args.url;
|
|
var state = Math.random().toString().replace(/^0\./, '');
|
|
var params = {};
|
|
var loc;
|
|
var result;
|
|
|
|
params.state = state;
|
|
params.response_type = responseType;
|
|
if (scope) {
|
|
if (Array.isArray(scope)) {
|
|
scope = scope.join(' ');
|
|
}
|
|
params.scope = scope;
|
|
}
|
|
if (clientId) {
|
|
// In OAuth3 client_id is optional for implicit grant
|
|
params.client_id = clientId;
|
|
}
|
|
if (!redirectUri) {
|
|
loc = window.location;
|
|
redirectUri = loc.protocol + '//' + loc.host + loc.pathname;
|
|
if ('/' !== redirectUri[redirectUri.length - 1]) {
|
|
redirectUri += '/';
|
|
}
|
|
redirectUri += 'oauth3.html';
|
|
}
|
|
params.redirect_uri = redirectUri;
|
|
|
|
uri += '?' + core.querystringify(params);
|
|
|
|
result = {
|
|
url: uri
|
|
, state: state
|
|
, method: args.method
|
|
, query: params
|
|
};
|
|
|
|
return result;
|
|
};
|
|
|
|
core.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.otp;
|
|
var params = {
|
|
"username": opts.id || opts.username
|
|
, "request_otp": true // opts.requestOtp || undefined
|
|
//, "jwt": opts.jwt // TODO sign a proof
|
|
};
|
|
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.resourceOwnerPassword = function (directive, username, passphrase, 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 (!passphrase) {
|
|
if (opts.otp) {
|
|
// for backwards compat
|
|
passphrase = opts.otp; // 'otp:' + opts.otpUuid + ':' + opts.otp;
|
|
}
|
|
}
|
|
|
|
var scope = opts.scope || directive.authn_scope;
|
|
var clientId = opts.appId;
|
|
var clientAgreeTos = opts.clientAgreeTos;
|
|
var clientUri = opts.clientUri;
|
|
var args = directive[type];
|
|
var params = {
|
|
"grant_type": grantType
|
|
, "username": username
|
|
, "password": passphrase || undefined
|
|
, "totp": opts.totp || opts.totpToken || undefined
|
|
, "otp": opts.otp || opts.otpCode || undefined
|
|
, "otp_uuid": opts.otpUuid || undefined
|
|
, "user_agent": opts.userAgent || undefined // AJ's Macbook
|
|
, "jwk": opts.rememberDevice && 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
|
|
};
|
|
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) {
|
|
if (Array.isArray(scope)) {
|
|
scope = scope.join(' ');
|
|
}
|
|
params.scope = scope;
|
|
}
|
|
|
|
if ('GET' === args.method.toUpperCase()) {
|
|
uri += '?' + core.querystringify(params);
|
|
} else {
|
|
body = params;
|
|
}
|
|
|
|
return {
|
|
url: uri
|
|
, method: args.method
|
|
, 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));
|