303 lines
		
	
	
		
			9.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			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));
 |