Compare commits
	
		
			9 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| e9d530f2a0 | |||
|  | 8c1f39178d | ||
|  | f6fe7acaa3 | ||
|  | 19d153283e | ||
|  | 0e23100233 | ||
|  | 40f8f0877e | ||
|  | f3988a5665 | ||
|  | bdd3cd36f4 | ||
|  | 5c24248230 | 
							
								
								
									
										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) | 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 [ppl](https://ppl.family) | ||||||
|  | 
 | ||||||
| Implementation of server-side RESTful OAuth3 issuer APIs. | Implementation of server-side RESTful OAuth3 issuer APIs. | ||||||
| 
 | 
 | ||||||
| These are the OAuth3 APIs that allow for creation and retrieval of public keys | 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.checkIssuerToken = checkIssuerToken; | ||||||
|  | module.exports.getPrimarySub = getPrimarySub; | ||||||
| module.exports.makeB64UrlSafe = makeB64UrlSafe; | 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"); |         throw new OpErr("malformed request: 'sub' and 'scope' must be strings"); | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       return req.Store.find({ azpSub: req.body.sub }); |       return require('./common').getPrimarySub(req.Store, req.body.sub).catch(function (err) { | ||||||
|     }).then(function (existing) { |         if (/collision/.test(err.message)) { | ||||||
|       if (existing.length) { |           err.message = 'pre-existing PPID collision detected'; | ||||||
|         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"); |  | ||||||
|         } |         } | ||||||
|  |         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 = { |       var grant = { | ||||||
|  | |||||||
							
								
								
									
										88
									
								
								jwks.js
									
									
									
									
									
								
							
							
						
						
									
										88
									
								
								jwks.js
									
									
									
									
									
								
							| @ -33,58 +33,58 @@ function thumbprint(jwk) { | |||||||
|   return PromiseA.resolve(makeB64UrlSafe(hash)); |   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) { | function create(app) { | ||||||
|   var restful = {}; |   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
 |   async function getRawKey(req) { | ||||||
|     // we need to look up the issuer PPID using the 3rd party PPID.
 |     var store = await req.getSiteStore(); | ||||||
|     var promise = req.getSiteStore().then(function (store) { | 
 | ||||||
|       if (req.params.kid === req.experienceId) { |     if (req.params.kid === req.experienceId) { | ||||||
|         return store.IssuerOauth3OrgPrivateKeys.get(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
 |     if (!issuerSub) { | ||||||
|       // to identify the user, and if not then we need to look up the specified `sub` to see if
 |       throw new OpErr("unknown PPID '" + req.params.sub + "'"); | ||||||
|       // we can determine which (if any) account it's associated with.
 |     } | ||||||
|       return store.IssuerOauth3OrgJwks.get(req.params.sub+'/'+req.params.kid).then(function (jwk) { |     return store.IssuerOauth3OrgJwks.get(issuerSub + '/' + req.params.kid); | ||||||
|         if (jwk) { |   } | ||||||
|           return jwk; |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         return store.IssuerOauth3OrgGrants.find({ azpSub: req.params.sub }).then(function (results) { |   restful.get = function (req, res) { | ||||||
|           if (!results.length) { |     var promise = PromiseA.resolve(getRawKey(req)).then(function (jwk) { | ||||||
|             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) { |  | ||||||
|       if (!jwk) { |       if (!jwk) { | ||||||
|         throw new OpErr("no keys stored with kid '"+req.params.kid+"' for PPID "+req.params.sub); |         throw new OpErr("no keys stored with kid '"+req.params.kid+"' for PPID "+req.params.sub); | ||||||
|       } |       } | ||||||
| 
 |       return 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; |  | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     app.handlePromise(req, res, promise, "[issuer@oauth3.org] retrieve JWK"); |     app.handlePromise(req, res, promise, "[issuer@oauth3.org] retrieve JWK"); | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "name": "issuer_oauth3.org", |   "name": "issuer_oauth3.org", | ||||||
|   "version": "1.0.0-placeholder", |   "version": "1.2.1", | ||||||
|   "description": "Implementation of server-side RESTful OAuth3 issuer APIs", |   "description": "Implementation of server-side RESTful OAuth3 issuer APIs", | ||||||
|   "main": "rest.js", |   "main": "rest.js", | ||||||
|   "scripts": { |   "scripts": { | ||||||
| @ -8,7 +8,7 @@ | |||||||
|   }, |   }, | ||||||
|   "repository": { |   "repository": { | ||||||
|     "type": "git", |     "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": { |   "dependencies": { | ||||||
|     "bluebird": "^3.5.0", |     "bluebird": "^3.5.0", | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user