Merge branch 'v1.2' of ssh://git.oauth3.org/OAuth3/issuer.rest.walnut.js into v1.2
This commit is contained in:
		
						commit
						3fedb3d8ad
					
				
							
								
								
									
										9
									
								
								CHANGELOG
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								CHANGELOG
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,9 @@ | ||||
| v1.2.1 - Allow PPID reuse | ||||
|   * Now allows a single user to use the same PPID for multiple sites | ||||
| 
 | ||||
| v1.2.0 - Issuer Pub/Priv Key and Grant APIs for OAuth3 | ||||
|   * Resource Owner Password token exchange | ||||
|   * Implicit Grant token exchange | ||||
|   * Public / Private Keypair generation | ||||
|   * Public key (remember device) syncing | ||||
|   * BUG: ppid rescoping is not handled correctly for 3rd parties | ||||
							
								
								
									
										41
									
								
								LICENSE
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								LICENSE
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,41 @@ | ||||
| Copyright 2017 Daplie, Inc | ||||
| 
 | ||||
| This is open source software; you can redistribute it and/or modify it under the | ||||
| terms of either: | ||||
| 
 | ||||
|    a) the "MIT License" | ||||
|    b) the "Apache-2.0 License" | ||||
| 
 | ||||
| MIT License | ||||
| 
 | ||||
|    Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|    of this software and associated documentation files (the "Software"), to deal | ||||
|    in the Software without restriction, including without limitation the rights | ||||
|    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|    copies of the Software, and to permit persons to whom the Software is | ||||
|    furnished to do so, subject to the following conditions: | ||||
| 
 | ||||
|    The above copyright notice and this permission notice shall be included in all | ||||
|    copies or substantial portions of the Software. | ||||
| 
 | ||||
|    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|    SOFTWARE. | ||||
| 
 | ||||
| Apache-2.0 License Summary | ||||
| 
 | ||||
|    Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|    you may not use this file except in compliance with the License. | ||||
|    You may obtain a copy of the License at | ||||
| 
 | ||||
|      http://www.apache.org/licenses/LICENSE-2.0 | ||||
| 
 | ||||
|    Unless required by applicable law or agreed to in writing, software | ||||
|    distributed under the License is distributed on an "AS IS" BASIS, | ||||
|    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|    See the License for the specific language governing permissions and | ||||
|    limitations under the License. | ||||
| @ -1,6 +1,12 @@ | ||||
| issuer@oauth3.org (js) | ||||
| ====================== | ||||
| 
 | ||||
| | [oauth3.js](https://git.oauth3.org/OAuth3/oauth3.js) | ||||
| | [issuer.html](https://git.oauth3.org/OAuth3/issuer.html) | ||||
| | *issuer.rest.walnut.js* | ||||
| | [issuer.srv](https://git.oauth3.org/OAuth3/issuer.srv) | ||||
| | Sponsored by [Daplie](https://daplie.com) | ||||
| 
 | ||||
| Implementation of server-side RESTful OAuth3 issuer APIs. | ||||
| 
 | ||||
| These are the OAuth3 APIs that allow for creation and retrieval of public keys | ||||
|  | ||||
							
								
								
									
										23
									
								
								common.js
									
									
									
									
									
								
							
							
						
						
									
										23
									
								
								common.js
									
									
									
									
									
								
							| @ -40,5 +40,28 @@ function checkIssuerToken(req, expectedSub) { | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| async function getPrimarySub(grantStore, secondary) { | ||||
|   var results = await Promise.all([ | ||||
|     grantStore.find({ sub:    secondary }), | ||||
|     grantStore.find({ azpSub: secondary }), | ||||
|   ]); | ||||
| 
 | ||||
|   // Extract only the sub field from each row and reduce the duplicates so that each value
 | ||||
|   // can only appear in the final list once.
 | ||||
|   var subList = [].concat.apply([], results).map(grant => grant.sub); | ||||
|   subList = subList.filter((sub, ind, list) => list.indexOf(sub) === ind); | ||||
| 
 | ||||
|   if (subList.length > 1) { | ||||
|     // This should not ever happen since there is a check for PPID collisions when saving
 | ||||
|     // grants, but it's probably better to have this check anyway just incase something
 | ||||
|     // happens that isn't currently accounted for.
 | ||||
|     console.error('acounts ' + JSON.stringify(subList) + ' are all associated with "'+secondary+'"'); | ||||
|     throw new Error('PPID collision'); | ||||
|   } | ||||
| 
 | ||||
|   return subList[0]; | ||||
| } | ||||
| 
 | ||||
| module.exports.checkIssuerToken = checkIssuerToken; | ||||
| module.exports.getPrimarySub = getPrimarySub; | ||||
| module.exports.makeB64UrlSafe = makeB64UrlSafe; | ||||
|  | ||||
							
								
								
									
										16
									
								
								grants.js
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								grants.js
									
									
									
									
									
								
							| @ -56,14 +56,16 @@ function create(app) { | ||||
|         throw new OpErr("malformed request: 'sub' and 'scope' must be strings"); | ||||
|       } | ||||
| 
 | ||||
|       return req.Store.find({ azpSub: req.body.sub }); | ||||
|     }).then(function (existing) { | ||||
|       if (existing.length) { | ||||
|         if (existing.length > 1) { | ||||
|           throw new OpErr("pre-existing PPID collision detected"); | ||||
|         } else if (existing[0].sub !== req.params.sub || existing[0].azp !== req.params.azp) { | ||||
|           throw new OpErr("PPID collision detected, cannot save authorized party's sub"); | ||||
|       return require('./common').getPrimarySub(req.Store, req.body.sub).catch(function (err) { | ||||
|         if (/collision/.test(err.message)) { | ||||
|           err.message = 'pre-existing PPID collision detected'; | ||||
|         } | ||||
|         throw err; | ||||
|       }); | ||||
|     }).then(function (primSub) { | ||||
|       if (primSub && primSub !== req.params.sub) { | ||||
|         console.log('account "'+req.params.sub+'" cannot use PPID "'+req.body.sub+'" already used by "'+primSub+'"'); | ||||
|         throw new OpErr("PPID collision detected, cannot save authorized party's sub"); | ||||
|       } | ||||
| 
 | ||||
|       var grant = { | ||||
|  | ||||
							
								
								
									
										88
									
								
								jwks.js
									
									
									
									
									
								
							
							
						
						
									
										88
									
								
								jwks.js
									
									
									
									
									
								
							| @ -33,58 +33,58 @@ function thumbprint(jwk) { | ||||
|   return PromiseA.resolve(makeB64UrlSafe(hash)); | ||||
| } | ||||
| 
 | ||||
| function sanitizeJwk(jwk) { | ||||
|   // We need to sanitize the key to make sure we don't deliver any private keys fields if
 | ||||
|   // we were given a key we could use to sign tokens on behalf of the user. We also don't
 | ||||
|   // want to deliver the sub or any other PPIDs.
 | ||||
|   var whitelist = ['kty', 'alg', 'kid', 'use']; | ||||
|   if (jwk.kty === 'EC') { | ||||
|     whitelist = whitelist.concat(['crv', 'x', 'y']); | ||||
|   } else if (jwk.kty === 'RSA') { | ||||
|     whitelist = whitelist.concat(['e', 'n']); | ||||
|   } | ||||
| 
 | ||||
|   var result = {}; | ||||
|   whitelist.forEach(function (key) { | ||||
|     result[key] = jwk[key]; | ||||
|   }); | ||||
|   return result; | ||||
| } | ||||
| 
 | ||||
| function create(app) { | ||||
|   var restful = {}; | ||||
|   restful.get = function (req, res) { | ||||
|     // The sub in params is the 3rd party PPID, but the keys are stored by the issuer PPID, so
 | ||||
|     // we need to look up the issuer PPID using the 3rd party PPID.
 | ||||
|     var promise = req.getSiteStore().then(function (store) { | ||||
|       if (req.params.kid === req.experienceId) { | ||||
|         return store.IssuerOauth3OrgPrivateKeys.get(req.experienceId); | ||||
| 
 | ||||
|   async function getRawKey(req) { | ||||
|     var store = await req.getSiteStore(); | ||||
| 
 | ||||
|     if (req.params.kid === req.experienceId) { | ||||
|       return store.IssuerOauth3OrgPrivateKeys.get(req.experienceId); | ||||
|     } | ||||
| 
 | ||||
|     // The keys are stored by the issuer PPID, but the sub we have might be a different PPID
 | ||||
|     // for a 3rd party.
 | ||||
|     var issuerSub; | ||||
|     try { | ||||
|       issuerSub = await require('./common').getPrimarySub(store.IssuerOauth3OrgGrants, req.params.sub); | ||||
|     } catch (err) { | ||||
|       if (/collision/.test(err.message)) { | ||||
|         err.message = 'PPID collision - unable to safely retrieve keys'; | ||||
|       } | ||||
|       throw err; | ||||
|     } | ||||
| 
 | ||||
|       // First we check to see if the key is being requested by the `sub` that we as the issuer use
 | ||||
|       // to identify the user, and if not then we need to look up the specified `sub` to see if
 | ||||
|       // we can determine which (if any) account it's associated with.
 | ||||
|       return store.IssuerOauth3OrgJwks.get(req.params.sub+'/'+req.params.kid).then(function (jwk) { | ||||
|         if (jwk) { | ||||
|           return jwk; | ||||
|         } | ||||
|     if (!issuerSub) { | ||||
|       throw new OpErr("unknown PPID '" + req.params.sub + "'"); | ||||
|     } | ||||
|     return store.IssuerOauth3OrgJwks.get(issuerSub + '/' + req.params.kid); | ||||
|   } | ||||
| 
 | ||||
|         return store.IssuerOauth3OrgGrants.find({ azpSub: req.params.sub }).then(function (results) { | ||||
|           if (!results.length) { | ||||
|             throw new OpErr("unknown PPID '"+req.params.sub+"'"); | ||||
|           } | ||||
|           if (results.length > 1) { | ||||
|             // This should not ever happen since there is a check for PPID collisions when saving
 | ||||
|             // grants, but it's probably better to have this check anyway just incase something
 | ||||
|             // happens that isn't currently accounted for.
 | ||||
|             throw new OpErr('PPID collision - unable to safely retrieve keys'); | ||||
|           } | ||||
| 
 | ||||
|           return store.IssuerOauth3OrgJwks.get(results[0].sub+'/'+req.params.kid); | ||||
|         }); | ||||
|       }); | ||||
|     }).then(function (jwk) { | ||||
|   restful.get = function (req, res) { | ||||
|     var promise = PromiseA.resolve(getRawKey(req)).then(function (jwk) { | ||||
|       if (!jwk) { | ||||
|         throw new OpErr("no keys stored with kid '"+req.params.kid+"' for PPID "+req.params.sub); | ||||
|       } | ||||
| 
 | ||||
|       // We need to sanitize the key to make sure we don't deliver any private keys fields if
 | ||||
|       // we were given a key we could use to sign tokens on behalf of the user. We also don't
 | ||||
|       // want to deliver the sub or any other PPIDs.
 | ||||
|       var whitelist = [ 'kty', 'alg', 'kid', 'use' ]; | ||||
|       if (jwk.kty === 'EC') { | ||||
|         whitelist = whitelist.concat([ 'crv', 'x', 'y' ]); | ||||
|       } else if (jwk.kty === 'RSA') { | ||||
|         whitelist = whitelist.concat([ 'e', 'n' ]); | ||||
|       } | ||||
| 
 | ||||
|       var result = {}; | ||||
|       whitelist.forEach(function (key) { | ||||
|         result[key] = jwk[key]; | ||||
|       }); | ||||
|       return result; | ||||
|       return sanitizeJwk(jwk); | ||||
|     }); | ||||
| 
 | ||||
|     app.handlePromise(req, res, promise, "[issuer@oauth3.org] retrieve JWK"); | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "issuer_oauth3.org", | ||||
|   "version": "1.0.0-placeholder", | ||||
|   "version": "1.2.1", | ||||
|   "description": "Implementation of server-side RESTful OAuth3 issuer APIs", | ||||
|   "main": "rest.js", | ||||
|   "scripts": { | ||||
| @ -8,7 +8,7 @@ | ||||
|   }, | ||||
|   "repository": { | ||||
|     "type": "git", | ||||
|     "url": "git+https://git.daplie.com/Oauth3/issuer_oauth3.org.git" | ||||
|     "url": "git+https://git.oauth3.org/OAuth3/issuer.rest.walnut.js.git" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "bluebird": "^3.5.0", | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user