forked from coolaj86/walnut.js
		
	Compare commits
	
		
			13 Commits
		
	
	
		
			master
			...
			installer-
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 82b0fcf00f | |||
| aa28f00a4b | |||
| fc07a8dd30 | |||
| 1e9618f5ec | |||
| 5f1191a6b9 | |||
| 66850535d3 | |||
| 1eb98edd2b | |||
|  | 567c1cf39f | ||
|  | b803229dac | ||
|  | ed9b05913e | ||
|  | 20eccd5f31 | ||
|  | 4914b28b08 | ||
|  | ff95bfedb8 | 
| @ -53,7 +53,7 @@ Installation | |||||||
| 
 | 
 | ||||||
| We're still in a stage where the installation generally requires many manual steps. | We're still in a stage where the installation generally requires many manual steps. | ||||||
| 
 | 
 | ||||||
| ``bash | ```bash | ||||||
| curl https://git.daplie.com/Daplie/walnut.js/raw/v1.2/installer/get.sh | bash | curl https://git.daplie.com/Daplie/walnut.js/raw/v1.2/installer/get.sh | bash | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| @ -104,6 +104,7 @@ Understanding Walnut | |||||||
| │   ├── rest | │   ├── rest | ||||||
| │   └── services | │   └── services | ||||||
| └── var | └── var | ||||||
|  |     ├── <<pkgname>>/config.json | ||||||
|     └── sites |     └── sites | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| @ -279,7 +280,7 @@ The packages: | |||||||
| The permissions: | The permissions: | ||||||
| 
 | 
 | ||||||
| ``` | ``` | ||||||
| /srv/walnut/packages/ | /srv/walnut/etc/ | ||||||
| └── client-api-grants | └── client-api-grants | ||||||
|     └── cloud.foobar.me |     └── cloud.foobar.me | ||||||
|           ''' |           ''' | ||||||
|  | |||||||
							
								
								
									
										11
									
								
								dist/var/example.com/config.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								dist/var/example.com/config.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | |||||||
|  | { "mailgun.org": { | ||||||
|  |     "apiKey": "key-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" | ||||||
|  |   , "apiPublicKey": "pubkey-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" | ||||||
|  |   , "auth": { | ||||||
|  |       "user": "mailer@example.com" | ||||||
|  |     , "pass": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" | ||||||
|  |     , "api_key": "key-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" | ||||||
|  |     , "domain": "example.com" | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
| @ -9,6 +9,8 @@ my_name=walnut | |||||||
| my_app_pkg_name=com.daplie.walnut.web | my_app_pkg_name=com.daplie.walnut.web | ||||||
| my_app_ver="v1.2" | my_app_ver="v1.2" | ||||||
| my_azp_oauth3_ver="v1.2" | my_azp_oauth3_ver="v1.2" | ||||||
|  | # is the old version still needed in launchpad? | ||||||
|  | #my_azp_oauth3_ver="v1.1.3" | ||||||
| export NODE_VERSION="v8.9.0" | export NODE_VERSION="v8.9.0" | ||||||
| 
 | 
 | ||||||
| if [ -z "${my_tmp-}" ]; then | if [ -z "${my_tmp-}" ]; then | ||||||
| @ -24,9 +26,8 @@ fi | |||||||
| ### IMPORTANT ### | ### IMPORTANT ### | ||||||
| ###  VERSION  ### | ###  VERSION  ### | ||||||
| #my_app_ver="v1.1" | #my_app_ver="v1.1" | ||||||
| my_app_ver="installer-v2" | my_app_ver="v1.2" | ||||||
| my_launchpad_ver="v1.2" | my_launchpad_ver="v1.2" | ||||||
| my_azp_oauth3_ver="v1.1.3" |  | ||||||
| my_iss_oauth3_rest_ver="v1.2.0" | my_iss_oauth3_rest_ver="v1.2.0" | ||||||
| my_iss_oauth3_pages_ver="v1.2.1" | my_iss_oauth3_pages_ver="v1.2.1" | ||||||
| my_www_daplie_ver=v1.0.15 | my_www_daplie_ver=v1.0.15 | ||||||
| @ -106,20 +107,20 @@ pushd $my_tmp/opt/$my_name/core/lib/walnut@daplie.com/setup | |||||||
|   git pull |   git pull | ||||||
|   git checkout $my_launchpad_ver |   git checkout $my_launchpad_ver | ||||||
| 
 | 
 | ||||||
|   git clone https://git.daplie.com/OAuth3/oauth3.js.git ./assets/oauth3.org |   git clone https://git.oauth3.org/OAuth3/oauth3.js.git ./assets/oauth3.org | ||||||
|   pushd assets/oauth3.org |   pushd assets/oauth3.org | ||||||
|     git checkout $my_azp_oauth3_ver |     git checkout $my_azp_oauth3_ver | ||||||
|   popd |   popd | ||||||
| popd | popd | ||||||
| 
 | 
 | ||||||
| pushd $my_tmp/opt/$my_name/packages | pushd $my_tmp/opt/$my_name/packages | ||||||
|   git clone https://git.daplie.com/OAuth3/issuer_oauth3.org.git rest/issuer@oauth3.org |   git clone https://git.oauth3.org/OAuth3/issuer.rest.walnut.js.git rest/issuer@oauth3.org | ||||||
|   pushd rest/issuer@oauth3.org/ |   pushd rest/issuer@oauth3.org/ | ||||||
|       git checkout $my_iss_oauth3_rest_ver |       git checkout $my_iss_oauth3_rest_ver | ||||||
|       $my_npm install |       $my_npm install | ||||||
|   popd |   popd | ||||||
| 
 | 
 | ||||||
|   git clone https://git.daplie.com/OAuth3/org.oauth3.git pages/issuer@oauth3.org |   git clone https://git.oauth3.org/OAuth3/issuer.html.git pages/issuer@oauth3.org | ||||||
|   pushd pages/issuer@oauth3.org |   pushd pages/issuer@oauth3.org | ||||||
|     git checkout $my_iss_oauth3_pages_ver |     git checkout $my_iss_oauth3_pages_ver | ||||||
|     bash ./install.sh |     bash ./install.sh | ||||||
|  | |||||||
							
								
								
									
										63
									
								
								lib/apis.js
									
									
									
									
									
								
							
							
						
						
									
										63
									
								
								lib/apis.js
									
									
									
									
									
								
							| @ -124,6 +124,7 @@ module.exports.create = function (xconfx, apiFactories, apiDeps) { | |||||||
|       var tok = req.oauth3.token; |       var tok = req.oauth3.token; | ||||||
|       var accountId = req.params.accountId || '__NO_ID_GIVEN__'; |       var accountId = req.params.accountId || '__NO_ID_GIVEN__'; | ||||||
|       var ppid; |       var ppid; | ||||||
|  |       var iss = tok.iss; | ||||||
| 
 | 
 | ||||||
|       if (tok.sub && tok.sub.split(/,/g).filter(function (ppid) { |       if (tok.sub && tok.sub.split(/,/g).filter(function (ppid) { | ||||||
|         return ppid === accountId; |         return ppid === accountId; | ||||||
| @ -131,6 +132,7 @@ module.exports.create = function (xconfx, apiFactories, apiDeps) { | |||||||
|         ppid = accountId; |         ppid = accountId; | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|  |       // Deprecated backwards compat. To be removed.
 | ||||||
|       if (tok.axs && tok.axs.filter(function (acc) { |       if (tok.axs && tok.axs.filter(function (acc) { | ||||||
|         return acc.id === accountId || acc.appScopedId === accountId; |         return acc.id === accountId || acc.appScopedId === accountId; | ||||||
|       }).length) { |       }).length) { | ||||||
| @ -145,10 +147,9 @@ module.exports.create = function (xconfx, apiFactories, apiDeps) { | |||||||
|         return PromiseA.reject(new Error("missing accountId '" + accountId + "' in access token")); |         return PromiseA.reject(new Error("missing accountId '" + accountId + "' in access token")); | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       return req.oauth3.rescope(ppid).then(function (accountIdx) { |       return req.oauth3.rescope().then(function (accountIdx) { | ||||||
|         req.oauth3.accountIdx = accountIdx; |         req.oauth3.accountIdx = accountIdx; | ||||||
|         req.oauth3.ppid = ppid; |         req.oauth3.ppid = ppid; | ||||||
|         req.oauth3.accountHash = crypto.createHash('sha1').update(accountIdx).digest('hex'); |  | ||||||
|         //console.log('[walnut@daplie.com] accountIdx:', accountIdx);
 |         //console.log('[walnut@daplie.com] accountIdx:', accountIdx);
 | ||||||
|         //console.log('[walnut@daplie.com] ppid:', ppid);
 |         //console.log('[walnut@daplie.com] ppid:', ppid);
 | ||||||
| 
 | 
 | ||||||
| @ -160,19 +161,31 @@ module.exports.create = function (xconfx, apiFactories, apiDeps) { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   function accountRequired(req, res, next) { |   function accountRequired(req, res, next) { | ||||||
|  |     console.log('[accountRequired] [enter]'); | ||||||
|  | 
 | ||||||
|  |     var myIss = req.experienceId; | ||||||
|  |     var isPpid; | ||||||
|  | 
 | ||||||
|     // if this already has auth, great
 |     // if this already has auth, great
 | ||||||
|     if (req.oauth3.ppid && req.oauth3.accountIdx) { |     if (req.oauth3.ppid && req.oauth3.accountIdx) { | ||||||
|       next(); |       // except that if it's a ppid, we have to internally exchange it for the real token
 | ||||||
|       return; |       isPpid = (myIss === req.oauth3.iss && myIss !== req.oauth3.azp); | ||||||
|     } |       if (!isPpid) { | ||||||
| 
 |         console.log('[accountRequired] has token already'); | ||||||
|     // being public does not disallow authentication
 |         console.log(req.oauth3); | ||||||
|     if (req.isPublic && !req.oauth3.encodedToken) { |         console.log(''); | ||||||
|       next(); |         next(); | ||||||
|       return; |         return; | ||||||
|  |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (!req.oauth3.encodedToken) { |     if (!req.oauth3.encodedToken) { | ||||||
|  |       // being public does not disallow authentication
 | ||||||
|  |       if (req.isPublic) { | ||||||
|  |         next(); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|       rejectableRequest( |       rejectableRequest( | ||||||
|         req |         req | ||||||
|       , res |       , res | ||||||
| @ -187,6 +200,7 @@ module.exports.create = function (xconfx, apiFactories, apiDeps) { | |||||||
|       var tok = req.oauth3.token; |       var tok = req.oauth3.token; | ||||||
|       var ppid; |       var ppid; | ||||||
|       var err; |       var err; | ||||||
|  |       var iss = tok.iss; | ||||||
| 
 | 
 | ||||||
|       if (tok.sub) { |       if (tok.sub) { | ||||||
|         if (tok.sub.split(/,/g).length > 1) { |         if (tok.sub.split(/,/g).length > 1) { | ||||||
| @ -195,25 +209,30 @@ module.exports.create = function (xconfx, apiFactories, apiDeps) { | |||||||
|         } |         } | ||||||
|         ppid = tok.sub; |         ppid = tok.sub; | ||||||
|       } |       } | ||||||
|       else if (tok.axs && tok.axs.length) { |  | ||||||
|         if (tok.axs.length > 1) { |  | ||||||
|           err = new Error("more than one 'axs' specified in token (also, update to using 'sub' instead)"); |  | ||||||
|           return PromiseA.reject(err); |  | ||||||
|         } |  | ||||||
|         ppid = tok.axs[0].appScopedId || tok.axs[0].id; |  | ||||||
|       } |  | ||||||
|       else if (tok.acx) { |  | ||||||
|         ppid = tok.acx.appScopedId || tok.acx.id || tok.acx; |  | ||||||
|       } |  | ||||||
| 
 | 
 | ||||||
|       if (!ppid) { |       if (!ppid) { | ||||||
|         return PromiseA.reject(new Error("could not determine accountId from access token")); |         return PromiseA.reject(new Error("could not determine accountId from access token")); | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       return req.oauth3.rescope(ppid).then(function (accountIdx) { |       return req.oauth3.rescope().then(function (accountIdx) { | ||||||
|  |         console.log('[accountRequired] req.oauth3'); | ||||||
|  |         console.log(accountIdx); | ||||||
|  | 
 | ||||||
|  |         var sub = accountIdx.split('@')[0]; | ||||||
|  |         var iss = accountIdx.split('@')[1]; | ||||||
|  |         var id = sub + '@' + iss; | ||||||
|  | 
 | ||||||
|  |         req.oauth3.profile = { | ||||||
|  |           id: id | ||||||
|  |         , sub: sub | ||||||
|  |         , iss: iss | ||||||
|  |         }; | ||||||
|  |         req.oauth3.id = id; | ||||||
|  |         req.oauth3.sub = sub; | ||||||
|  |         req.oauth3.iss = iss; | ||||||
|  | 
 | ||||||
|         req.oauth3.accountIdx = accountIdx; |         req.oauth3.accountIdx = accountIdx; | ||||||
|         req.oauth3.ppid = ppid; |         req.oauth3.ppid = ppid; | ||||||
|         req.oauth3.accountHash = crypto.createHash('sha1').update(accountIdx).digest('hex'); |  | ||||||
| 
 | 
 | ||||||
|         next(); |         next(); | ||||||
|       }); |       }); | ||||||
|  | |||||||
							
								
								
									
										242
									
								
								lib/oauth3.js
									
									
									
									
									
								
							
							
						
						
									
										242
									
								
								lib/oauth3.js
									
									
									
									
									
								
							| @ -2,15 +2,17 @@ | |||||||
| 
 | 
 | ||||||
| var PromiseA = require('bluebird'); | var PromiseA = require('bluebird'); | ||||||
| 
 | 
 | ||||||
| function generateRescope(req, Models, decoded, fullPpid, ppid) { | function generateRescope(req, Models, decoded) { | ||||||
|  |   var fullPpid = decoded.sub+'@'+decoded.iss; | ||||||
|  |   var ppid = decoded.sub; | ||||||
|   return function (/*sub*/) { |   return function (/*sub*/) { | ||||||
|     // TODO: this function is supposed to convert PPIDs of different parties to some account
 |     // TODO: this function is supposed to convert PPIDs of different parties to some account
 | ||||||
|     // ID that allows application to keep track of permisions and what-not.
 |     // ID that allows application to keep track of permisions and what-not.
 | ||||||
|     console.log('[rescope] Attempting ', fullPpid); |     console.log('[rescope] Attempting ', fullPpid); | ||||||
|     return Models.IssuerOauth3OrgGrants.find({ azpSub: fullPpid }).then(function (results) { |     return Models.IssuerOauth3OrgGrants.find({ azpSub: fullPpid }).then(function (results) { | ||||||
|       if (results[0]) { |       if (results[0]) { | ||||||
|         console.log('[rescope] lukcy duck: got it on the 1st try'); |         console.log('[rescope] lucky duck: got it on the 1st try'); | ||||||
|         return PromiseA.resolve(results); |         return results; | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       // XXX BUG XXX
 |       // XXX BUG XXX
 | ||||||
| @ -19,71 +21,46 @@ function generateRescope(req, Models, decoded, fullPpid, ppid) { | |||||||
|     }).then(function (results) { |     }).then(function (results) { | ||||||
|       var result = results[0]; |       var result = results[0]; | ||||||
| 
 | 
 | ||||||
|  |       if (!result || !result.sub || !decoded.iss) { | ||||||
|  |         console.log('[rescope] Not a 2nd party token...'); | ||||||
|  |         return Models.IssuerOauth3OrgProfiles.get(fullPpid); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       return result; | ||||||
|  |     }).then(function (result) { | ||||||
|  |       var err; | ||||||
|  | 
 | ||||||
|       if (!result || !result.sub || !decoded.iss) { |       if (!result || !result.sub || !decoded.iss) { | ||||||
|         // XXX BUG XXX TODO swap this external ppid for an internal (and ask user to link with existing profile)
 |         // XXX BUG XXX TODO swap this external ppid for an internal (and ask user to link with existing profile)
 | ||||||
|         //req.oauth3.accountIdx = fullPpid;
 |         //req.oauth3.accountIdx = fullPpid;
 | ||||||
|         throw new Error("internal / external ID swapping not yet implemented. TODO: " |         console.log('[DEBUG] decoded:'); | ||||||
|           + "No profile found with that credential. Would you like to create a new profile or link to an existing profile?"); |         console.log(decoded); | ||||||
|  |         console.log('[DEBUG] decoded.iss:', decoded.iss); | ||||||
|  |         console.log('[DEBUG] fullPpid:', fullPpid); | ||||||
|  |         console.log('[DEBUG] ppid:', ppid); | ||||||
|  | 
 | ||||||
|  |         err = new Error( | ||||||
|  |           "TODO: No profile found with that credential. Would you like to create a new profile or link to an existing profile?" | ||||||
|  |         ); | ||||||
|  |         err.code = "E_NO_PROFILE@oauth3.org" | ||||||
|  |         throw err; | ||||||
|  |         //return req.oauth3.token.sub + '@' + req.oauth3.token.iss;
 | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       // XXX BUG XXX need to pass own url in to use as issuer for own tokens
 |       // XXX BUG XXX need to pass own url in to use as issuer for own tokens
 | ||||||
|       req.oauth3.accountIdx = result.sub + '@' + decoded.iss; |       req.oauth3.accountIdx = result.sub + '@' + (result.iss || decoded.iss); | ||||||
| 
 | 
 | ||||||
|       console.log('[rescope] result:'); |       console.log('[rescope] result:'); | ||||||
|       console.log(results); |       console.log(result); | ||||||
|       console.log(req.oauth3.accountIdx); |       console.log('[rescope] req.oauth3.accountIdx:', req.oauth3.accountIdx); | ||||||
| 
 | 
 | ||||||
|       return PromiseA.resolve(req.oauth3.accountIdx); |       return req.oauth3.accountIdx; | ||||||
|     }); |     }); | ||||||
|   }; |   }; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function extractAccessToken(req) { | function verifyToken(token, opts) { | ||||||
|   var token = null; |   opts = opts || { audiences: [], complete: false }; | ||||||
|   var parts; |  | ||||||
|   var scheme; |  | ||||||
|   var credentials; |  | ||||||
| 
 |  | ||||||
|   if (req.headers && req.headers.authorization) { |  | ||||||
|     // Works for all of Authorization: Bearer {{ token }}, Token {{ token }}, JWT {{ token }}
 |  | ||||||
|     parts = req.headers.authorization.split(' '); |  | ||||||
| 
 |  | ||||||
|     if (parts.length !== 2) { |  | ||||||
|       return PromiseA.reject(new Error("malformed Authorization header")); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     scheme = parts[0]; |  | ||||||
|     credentials = parts[1]; |  | ||||||
| 
 |  | ||||||
|     if (-1 !== ['token', 'bearer'].indexOf(scheme.toLowerCase())) { |  | ||||||
|       token = credentials; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   if (req.body && req.body.access_token) { |  | ||||||
|     if (token) { PromiseA.reject(new Error("token exists in header and body")); } |  | ||||||
|     token = req.body.access_token; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   // TODO disallow query with req.method === 'GET'
 |  | ||||||
|   // NOTE: the case of DDNS on routers requires a GET and access_token
 |  | ||||||
|   // (cookies should be used for protected static assets)
 |  | ||||||
|   if (req.query && req.query.access_token) { |  | ||||||
|     if (token) { PromiseA.reject(new Error("token already exists in either header or body and also in query")); } |  | ||||||
|     token = req.query.access_token; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   /* |  | ||||||
|   err = new Error(challenge()); |  | ||||||
|   err.code = 'E_BEARER_REALM'; |  | ||||||
| 
 |  | ||||||
|   if (!token) { return PromiseA.reject(err); } |  | ||||||
|   */ |  | ||||||
| 
 |  | ||||||
|   return PromiseA.resolve(token); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function verifyToken(token) { |  | ||||||
|   var jwt = require('jsonwebtoken'); |   var jwt = require('jsonwebtoken'); | ||||||
|   var decoded; |   var decoded; | ||||||
| 
 | 
 | ||||||
| @ -98,6 +75,7 @@ function verifyToken(token) { | |||||||
|   try { |   try { | ||||||
|     decoded = jwt.decode(token, {complete: true}); |     decoded = jwt.decode(token, {complete: true}); | ||||||
|   } catch (e) {} |   } catch (e) {} | ||||||
|  | 
 | ||||||
|   if (!decoded) { |   if (!decoded) { | ||||||
|     return PromiseA.reject({ |     return PromiseA.reject({ | ||||||
|       message: 'provided token not a JSON Web Token' |       message: 'provided token not a JSON Web Token' | ||||||
| @ -130,6 +108,27 @@ function verifyToken(token) { | |||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   var audMatch = decoded.payload.aud && ('*' === decoded.payload.aud || opts.audiences.some(function (aud) { return -1 !== decoded.payload.aud.split(',').indexOf(aud); })); | ||||||
|  |   var azpMatch = decoded.payload.azp && ('*' === decoded.payload.azp || opts.audiences.some(function (aud) { return -1 !== decoded.payload.azp.split(',').indexOf(aud); })); | ||||||
|  | 
 | ||||||
|  |   if (!audMatch) { | ||||||
|  |     console.log("[verifyToken] 'aud' '" + decoded.payload.aud + "' does not match '" + opts.audiences.join(',') + "'"); | ||||||
|  |   } | ||||||
|  |   // TODO needs an option to verify that the sender of the token was, in fact, the azp (i.e. the Origin and/or Referer Headers)
 | ||||||
|  |   if (!azpMatch) { | ||||||
|  |     console.log("[verifyToken] 'azp' '" + decoded.payload.azp + "' does not match '" + opts.audiences.join(',') + "'"); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if (!audMatch && !azpMatch) { | ||||||
|  |     err = new Error( | ||||||
|  |       "Application '" + req.experienceId + "' refused token because '" + decoded.payload.aud + "' is not an accepted audience (aud)" | ||||||
|  |     + " and '" + decoded.payload.azp + "' is not an authorized party (azp)" | ||||||
|  |     ); | ||||||
|  |     err.code = 'E_TOKEN_AUD'; | ||||||
|  |     err.url = 'https://oauth3.org/docs/errors#E_TOKEN_AUD' | ||||||
|  |     return PromiseA.reject(err); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   var OAUTH3 = require('oauth3.js'); |   var OAUTH3 = require('oauth3.js'); | ||||||
|   OAUTH3._hooks = require('oauth3.js/oauth3.node.storage.js'); |   OAUTH3._hooks = require('oauth3.js/oauth3.node.storage.js'); | ||||||
|   return OAUTH3.discover(decoded.payload.iss).then(function (directives) { |   return OAUTH3.discover(decoded.payload.iss).then(function (directives) { | ||||||
| @ -188,16 +187,27 @@ function verifyToken(token) { | |||||||
|     if (res.data.error) { |     if (res.data.error) { | ||||||
|       return PromiseA.reject(res.data.error); |       return PromiseA.reject(res.data.error); | ||||||
|     } |     } | ||||||
|     var opts = {}; |     var opts2 = {}; | ||||||
|     if (Array.isArray(res.data.alg)) { |     if (Array.isArray(res.data.alg)) { | ||||||
|       opts.algorithms = res.data.alg; |       opts2.algorithms = res.data.alg; | ||||||
|     } else if (typeof res.data.alg === 'string') { |     } else if (typeof res.data.alg === 'string') { | ||||||
|       opts.algorithms = [res.data.alg]; |       opts2.algorithms = [res.data.alg]; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     try { |     try { | ||||||
|       return jwt.verify(token, require('jwk-to-pem')(res.data), opts); |       if (opts.complete) { | ||||||
|  |         opts2.complete = true; | ||||||
|  |       } | ||||||
|  |       return jwt.verify(token, require('jwk-to-pem')(res.data), opts2); | ||||||
|     } catch (err) { |     } catch (err) { | ||||||
|  |       if ('TokenExpiredError' === err.name) { | ||||||
|  |         return PromiseA.reject({ | ||||||
|  |           message: 'TokenExpiredError: jwt expired' | ||||||
|  |         , code: 'E_TOKEN_EXPIRED' | ||||||
|  |         , url: 'https://oauth3.org/docs/errors#E_TOKEN_EXPIRED' | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|       return PromiseA.reject({ |       return PromiseA.reject({ | ||||||
|         message: 'token verification failed' |         message: 'token verification failed' | ||||||
|       , code: 'E_INVALID_TOKEN' |       , code: 'E_INVALID_TOKEN' | ||||||
| @ -217,32 +227,47 @@ function deepFreeze(obj) { | |||||||
|   Object.freeze(obj); |   Object.freeze(obj); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function cookieOauth3(Models, req, res, next) { | function fiddleOauth3(Models, req) { | ||||||
|   req.oauth3 = {}; |   var token = req.oauth3.encodedToken; | ||||||
| 
 | 
 | ||||||
|   var token = req.cookies.jwt; |   req.oauth3.verifyAsync = function (jwt, opts) { | ||||||
| 
 |     return verifyToken(jwt || token, opts || { audiences: [ req.experienceId ] }); | ||||||
|   req.oauth3.encodedToken = token; |  | ||||||
|   req.oauth3.verifyAsync = function (jwt) { |  | ||||||
|     return verifyToken(jwt || token); |  | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   return verifyToken(token).then(function  (decoded) { |   if (!token) { | ||||||
|  |     return PromiseA.resolve(null); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return verifyToken(token, { complete: false, audiences: [ req.experienceId ] }).then(function  (decoded) { | ||||||
|  |     var err; | ||||||
|     req.oauth3.token = decoded; |     req.oauth3.token = decoded; | ||||||
|  | 
 | ||||||
|     if (!decoded) { |     if (!decoded) { | ||||||
|       return null; |       return null; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     var ppid = decoded.sub || decoded.ppid || decoded.appScopedId; |     req.oauth3.ppid = decoded.sub; | ||||||
|     req.oauth3.ppid = ppid; |  | ||||||
|     req.oauth3.accountIdx = ppid+'@'+decoded.iss; |  | ||||||
| 
 | 
 | ||||||
|     var hash = require('crypto').createHash('sha256').update(req.oauth3.accountIdx).digest('base64'); |     req.oauth3.id = decoded.sub + '@' + decoded.iss; | ||||||
|     hash = hash.replace(/\+/g, '-').replace(/\//g, '_').replace(/\=+/g, ''); |     req.oauth3.sub = decoded.sub; | ||||||
|     req.oauth3.accountHash = hash; |     req.oauth3.iss = decoded.iss; | ||||||
|  |     req.oauth3.azp = decoded.azp; | ||||||
|  |     req.oauth3.aud = decoded.aud; | ||||||
| 
 | 
 | ||||||
|     req.oauth3.rescope = generateRescope(req, Models, decoded, fullPpid, ppid); |     req.oauth3.accountIdx = req.oauth3.id; | ||||||
|   }).then(function () { | 
 | ||||||
|  |     req.oauth3.rescope = generateRescope(req, Models, decoded); | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function cookieOauth3(Models, req, res, next) { | ||||||
|  |   req.oauth3 = {}; | ||||||
|  | 
 | ||||||
|  |   var cookieName = 'jwt'; | ||||||
|  |   var token = req.cookies[cookieName]; | ||||||
|  | 
 | ||||||
|  |   req.oauth3.encodedToken = token; | ||||||
|  |   fiddleOauth3(Models, req).then(function () { | ||||||
|     deepFreeze(req.oauth3); |     deepFreeze(req.oauth3); | ||||||
|     //Object.defineProperty(req, 'oauth3', {configurable: false, writable: false});
 |     //Object.defineProperty(req, 'oauth3', {configurable: false, writable: false});
 | ||||||
|     next(); |     next(); | ||||||
| @ -251,6 +276,11 @@ function cookieOauth3(Models, req, res, next) { | |||||||
|       next(); |       next(); | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|  |     if ('E_TOKEN_EXPIRED' === err.code) { | ||||||
|  |       res.clearCookie(cookieName); | ||||||
|  |       next(); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|     console.error('[walnut] cookie lib/oauth3 error:'); |     console.error('[walnut] cookie lib/oauth3 error:'); | ||||||
|     console.error(err); |     console.error(err); | ||||||
|     res.send(err); |     res.send(err); | ||||||
| @ -260,37 +290,49 @@ function cookieOauth3(Models, req, res, next) { | |||||||
| function attachOauth3(Models, req, res, next) { | function attachOauth3(Models, req, res, next) { | ||||||
|   req.oauth3 = {}; |   req.oauth3 = {}; | ||||||
| 
 | 
 | ||||||
|   extractAccessToken(req).then(function (token) { |   var token = null; | ||||||
|     req.oauth3.encodedToken = token; |   var parts; | ||||||
|     req.oauth3.verifyAsync = function (jwt) { |   var scheme; | ||||||
|       return verifyToken(jwt || token); |   var credentials; | ||||||
|     }; |  | ||||||
| 
 | 
 | ||||||
|     if (!token) { |   if (req.headers && req.headers.authorization) { | ||||||
|       return null; |     // Works for all of Authorization: Bearer {{ token }}, Token {{ token }}, JWT {{ token }}
 | ||||||
|     } |     parts = req.headers.authorization.split(' '); | ||||||
|     return verifyToken(token); | 
 | ||||||
|   }).then(function  (decoded) { |     if (parts.length !== 2) { | ||||||
|     req.oauth3.token = decoded; |       return PromiseA.reject(new Error("malformed Authorization header")); | ||||||
|     if (!decoded) { |  | ||||||
|       return null; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     var ppid = decoded.sub || decoded.ppid || decoded.appScopedId; |     scheme = parts[0]; | ||||||
|     var fullPpid = ppid+'@'+decoded.iss; |     credentials = parts[1]; | ||||||
|     req.oauth3.ppid = ppid; |  | ||||||
| 
 | 
 | ||||||
|     // TODO we can anonymize the relationship between our user as the other service's user
 |     if (-1 !== ['token', 'bearer'].indexOf(scheme.toLowerCase())) { | ||||||
|     // in our own database by hashing the remote service's ppid and using that as the lookup
 |       token = credentials; | ||||||
|     var hash = require('crypto').createHash('sha256').update(fullPpid).digest('base64'); |     } | ||||||
|     hash = hash.replace(/\+/g, '-').replace(/\//g, '_').replace(/\=+/g, ''); |   } | ||||||
|     req.oauth3.accountHash = hash; |  | ||||||
| 
 | 
 | ||||||
|     req.oauth3.rescope = generateRescope(req, Models, decoded, fullPpid, ppid); |   if (req.body && req.body.access_token) { | ||||||
|  |     if (token) { PromiseA.reject(new Error("token exists in header and body")); } | ||||||
|  |     token = req.body.access_token; | ||||||
|  |   } | ||||||
| 
 | 
 | ||||||
|     console.log('############### assigned req.oauth3:'); |   // TODO disallow query with req.method === 'GET'
 | ||||||
|     console.log(req.oauth3); |   // NOTE: the case of DDNS on routers requires a GET and access_token
 | ||||||
|   }).then(function () { |   // (cookies should be used for protected static assets)
 | ||||||
|  |   if (req.query && req.query.access_token) { | ||||||
|  |     if (token) { PromiseA.reject(new Error("token already exists in either header or body and also in query")); } | ||||||
|  |     token = req.query.access_token; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /* | ||||||
|  |   err = new Error(challenge()); | ||||||
|  |   err.code = 'E_BEARER_REALM'; | ||||||
|  | 
 | ||||||
|  |   if (!token) { return PromiseA.reject(err); } | ||||||
|  |   */ | ||||||
|  | 
 | ||||||
|  |   req.oauth3.encodedToken = token; | ||||||
|  |   fiddleOauth3(Models, req).then(function () { | ||||||
|     //deepFreeze(req.oauth3);
 |     //deepFreeze(req.oauth3);
 | ||||||
|     //Object.defineProperty(req, 'oauth3', {configurable: false, writable: false});
 |     //Object.defineProperty(req, 'oauth3', {configurable: false, writable: false});
 | ||||||
|     next(); |     next(); | ||||||
|  | |||||||
| @ -62,7 +62,7 @@ | |||||||
|     "multiparty": "^4.1.3", |     "multiparty": "^4.1.3", | ||||||
|     "nodemailer": "^1.4.0", |     "nodemailer": "^1.4.0", | ||||||
|     "nodemailer-mailgun-transport": "1.x", |     "nodemailer-mailgun-transport": "1.x", | ||||||
|     "oauth3.js": "git+https://git.daplie.com/OAuth3/oauth3.js.git", |     "oauth3.js": "git+https://git.oauth3.org/OAuth3/oauth3.js.git#v1.2", | ||||||
|     "recase": "^1.0.4", |     "recase": "^1.0.4", | ||||||
|     "request": "^2.81.0", |     "request": "^2.81.0", | ||||||
|     "scmp": "^2.0.0", |     "scmp": "^2.0.0", | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user