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