From ef303aa2bc0a4f0801790dedd519f83b7c56118c Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Tue, 17 Jan 2017 22:29:46 -0500 Subject: [PATCH] move core utils and url gens to own file --- oauth3.core.js | 281 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 281 insertions(+) create mode 100644 oauth3.core.js diff --git a/oauth3.core.js b/oauth3.core.js new file mode 100644 index 0000000..56e6a67 --- /dev/null +++ b/oauth3.core.js @@ -0,0 +1,281 @@ +(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://<>.com/api/org.oauth3.consumer/authorization_redirect/<>.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, username, 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": "<>", "scope": "<>" + // , "username": "<>" } + // + opts = opts || {}; + var clientId = opts.appId || opts.clientId; + + var args = directive.otp; + var params = { + "username": 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": "<>", "scope": "<>" + // , "username": "<>", "password": "password" } + // + opts = opts || {}; + var type = 'access_token'; + var grantType = 'password'; + + 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 + //, "totp": opts.totp + }; + 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));