forked from coolaj86/walnut.js
		
	implemented verification of token signed elsewhere
This commit is contained in:
		
							parent
							
								
									43a61546d8
								
							
						
					
					
						commit
						5053963874
					
				
							
								
								
									
										25
									
								
								lib/apis.js
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								lib/apis.js
									
									
									
									
									
								
							| @ -291,16 +291,15 @@ module.exports.create = function (xconfx, apiFactories, apiDeps) { | ||||
|               return; | ||||
|             } | ||||
| 
 | ||||
|             // every grant in the array must be present
 | ||||
|             if (!grants.every(function (grant) { | ||||
|               var scopes = grant.split(/\|/g); | ||||
|               return scopes.some(function (scp) { | ||||
|                 return tokenScopes.some(function (s) { | ||||
|                   return scp === s; | ||||
|             // every grant in the array must be present, though some grants can be satisfied
 | ||||
|             // by multiple scopes.
 | ||||
|             var missing = grants.filter(function (grant) { | ||||
|               return !grant.split('|').some(function (scp) { | ||||
|                 return tokenScopes.indexOf(scp) !== -1; | ||||
|               }); | ||||
|             }); | ||||
|             })) { | ||||
|               res.send({ error: { message: "Token does not contain valid grants: '" + grants + "'", code: "E_NO_GRANTS" } }); | ||||
|             if (missing.length) { | ||||
|               res.send({ error: { message: "Token missing required grants: '" + missing.join(',') + "'", code: "E_NO_GRANTS" } }); | ||||
|               return; | ||||
|             } | ||||
| 
 | ||||
| @ -308,11 +307,7 @@ module.exports.create = function (xconfx, apiFactories, apiDeps) { | ||||
|           }; | ||||
|         }; | ||||
| 
 | ||||
|         var _getOauth3Controllers = pkgDeps.getOauth3Controllers = require('oauthcommon/example-oauthmodels').create( | ||||
|           { sqlite3Sock: xconfx.sqlite3Sock, ipcKey: xconfx.ipcKey } | ||||
|         ).getControllers; | ||||
|         //require('oauthcommon').inject(packagedApi._getOauth3Controllers, packagedApi._api, pkgConf, pkgDeps);
 | ||||
|         require('oauthcommon').inject(_getOauth3Controllers, myApp/*, pkgConf, pkgDeps*/); | ||||
|         myApp.use('/', require('./oauth3').attachOauth3); | ||||
| 
 | ||||
|         // TODO delete these caches when config changes
 | ||||
|         var _stripe; | ||||
| @ -725,8 +720,8 @@ module.exports.create = function (xconfx, apiFactories, apiDeps) { | ||||
|       // Canonical client names
 | ||||
|       // example.com should use api.example.com/api for all requests
 | ||||
|       // sub.example.com/api should resolve to sub.example.com
 | ||||
|       // example.com/subpath/api should resolve to example.com#subapp
 | ||||
|       // sub.example.com/subpath/api should resolve to sub.example.com#subapp
 | ||||
|       // example.com/subapp/api should resolve to example.com#subapp
 | ||||
|       // sub.example.com/subapp/api should resolve to sub.example.com#subapp
 | ||||
|       var clientUrih = req.hostname.replace(/^api\./, '') + req.url.replace(/\/api\/.*/, '/').replace(/\/+/g, '#').replace(/#$/, ''); | ||||
|       var clientApiUri = req.hostname + req.url.replace(/\/api\/.*/, '/').replace(/\/$/, ''); | ||||
|       // Canonical package names
 | ||||
|  | ||||
							
								
								
									
										201
									
								
								lib/oauth3.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										201
									
								
								lib/oauth3.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,201 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| var PromiseA = require('bluebird'); | ||||
| 
 | ||||
| function extractAccessToken(req) { | ||||
|   var token; | ||||
|   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 decoded; | ||||
|   try { | ||||
|     decoded = jwt.decode(token, {complete: true}); | ||||
|   } catch (e) {} | ||||
|   if (!decoded) { | ||||
|     return PromiseA.reject({ | ||||
|       message: 'provided token not a JSON Web Token' | ||||
|     , code: 'E_NOT_JWT' | ||||
|     , url: 'https://oauth3.org/docs/errors#E_NOT_JWT' | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   var sub = decoded.payload.sub || decoded.payload.ppid || decoded.payload.appScopedId; | ||||
|   if (!sub) { | ||||
|     return PromiseA.reject({ | ||||
|       message: 'token missing sub' | ||||
|     , code: 'E_MISSING_SUB' | ||||
|     , url: 'https://oauth3.org/docs/errors#E_MISSING_SUB' | ||||
|     }); | ||||
|   } | ||||
|   var kid = decoded.header.kid || decoded.payload.kid; | ||||
|   if (!kid) { | ||||
|     return PromiseA.reject({ | ||||
|       message: 'token missing kid' | ||||
|     , code: 'E_MISSING_KID' | ||||
|     , url: 'https://oauth3.org/docs/errors#E_MISSING_KID' | ||||
|     }); | ||||
|   } | ||||
|   if (!decoded.payload.iss) { | ||||
|     return PromiseA.reject({ | ||||
|       message: 'token missing iss' | ||||
|     , code: 'E_MISSING_ISS' | ||||
|     , url: 'https://oauth3.org/docs/errors#E_MISSING_ISS' | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   var OAUTH3 = require('oauth3.js'); | ||||
|   OAUTH3._hooks = require('oauth3.js/oauth3.node.storage.js'); | ||||
|   return OAUTH3.discover(decoded.payload.iss).then(function (directives) { | ||||
|     var args = (directives || {}).retrieve_jwk; | ||||
|     if (typeof args === 'string') { | ||||
|       args = { url: args, method: 'GET' }; | ||||
|     } | ||||
|     if (typeof (args || {}).url !== 'string') { | ||||
|       return PromiseA.reject({ | ||||
|         message: 'token issuer does not support retrieving JWKs' | ||||
|       , code: 'E_INVALID_ISS' | ||||
|       , url: 'https://oauth3.org/docs/errors#E_INVALID_ISS' | ||||
|       }); | ||||
|     } | ||||
| 
 | ||||
|     var params = { | ||||
|       sub: sub | ||||
|     , kid: kid | ||||
|     }; | ||||
|     var url = args.url; | ||||
|     var body; | ||||
|     Object.keys(params).forEach(function (key) { | ||||
|       if (url.indexOf(':'+key) !== -1) { | ||||
|         url = url.replace(':'+key, params[key]); | ||||
|         delete params[key]; | ||||
|       } | ||||
|     }); | ||||
|     if (Object.keys(params).length > 0) { | ||||
|       if ('GET' === (args.method || 'GET').toUpperCase()) { | ||||
|         url += '?' + OAUTH3.query.stringify(params); | ||||
|       } else { | ||||
|         body = params; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     return OAUTH3.request({ | ||||
|       url: OAUTH3.url.resolve(directives.api, url) | ||||
|     , method: args.method | ||||
|     , data: body | ||||
|     }); | ||||
|   }, function (err) { | ||||
|     return PromiseA.reject({ | ||||
|       message: 'token issuer is not a valid OAuth3 provider' | ||||
|     , code: 'E_INVALID_ISS' | ||||
|     , url: 'https://oauth3.org/docs/errors#E_INVALID_ISS' | ||||
|     , subErr: err.toString() | ||||
|     }); | ||||
|   }).then(function (res) { | ||||
|     if (res.data.error) { | ||||
|       return PromiseA.reject(res.data.error); | ||||
|     } | ||||
|     var opts = {}; | ||||
|     if (Array.isArray(res.data.alg)) { | ||||
|       opts.algorithms = res.data.alg; | ||||
|     } else if (typeof res.data.alg === 'string') { | ||||
|       opts.algorithms = [res.data.alg]; | ||||
|     } | ||||
| 
 | ||||
|     try { | ||||
|       return jwt.verify(token, require('jwk-to-pem')(res.data), opts); | ||||
|     } catch (err) { | ||||
|       return PromiseA.reject({ | ||||
|         message: 'token verification failed' | ||||
|       , code: 'E_INVALID_TOKEN' | ||||
|       , url: 'https://oauth3.org/docs/errors#E_INVALID_TOKEN' | ||||
|       , subErr: err.toString() | ||||
|       }); | ||||
|     } | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| function attachOauth3(req, res, next) { | ||||
|   req.oauth3 = {}; | ||||
| 
 | ||||
|   extractAccessToken(req).then(function (token) { | ||||
|     if (!token) { | ||||
|       return null; | ||||
|     } | ||||
| 
 | ||||
|     var decoded; | ||||
|     try { | ||||
|       decoded = require('jsonwebtoken').decode(token); | ||||
|     } catch (e) {} | ||||
|     if (!decoded) { | ||||
|       return PromiseA.reject({ | ||||
|         message: 'provided token not a JSON Web Token' | ||||
|       , code: 'E_NOT_JWT' | ||||
|       , url: 'https://oauth3.org/docs/errors#E_NOT_JWT' | ||||
|       }); | ||||
|     } | ||||
|     var ppid = decoded.sub || decoded.ppid || decoded.appScopedId; | ||||
| 
 | ||||
|     req.oauth3.encodedToken = token; | ||||
|     req.oauth3.token = decoded; | ||||
|     req.oauth3.ppid = ppid; | ||||
| 
 | ||||
|     req.oauth3.verifyAsync = function () { | ||||
|       return verifyToken(token); | ||||
|     }; | ||||
| 
 | ||||
|     req.oauth3.rescope = function () { | ||||
|       // 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.
 | ||||
|       return PromiseA.resolve(ppid); | ||||
|     }; | ||||
|   }).then(function () { | ||||
|     next(); | ||||
|   }, function (err) { | ||||
|     res.send(err); | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| module.exports.attachOauth3 = attachOauth3; | ||||
| module.exports.verifyToken = verifyToken; | ||||
| @ -55,19 +55,7 @@ function getApi(conf, pkgConf, pkgDeps, packagedApi) { | ||||
|       packagedApi._api = require('express-lazy')(); | ||||
|       packagedApi._api_app = myApp; | ||||
| 
 | ||||
|       //require('./oauth3-auth').inject(conf, packagedApi._api, pkgConf, pkgDeps);
 | ||||
|       pkgDeps.getOauth3Controllers = | ||||
|       packagedApi._getOauth3Controllers = require('oauthcommon/example-oauthmodels').create(conf).getControllers; | ||||
|       require('oauthcommon').inject(packagedApi._getOauth3Controllers, packagedApi._api, pkgConf, pkgDeps); | ||||
| 
 | ||||
|       // DEBUG
 | ||||
|       //
 | ||||
|       /* | ||||
|       packagedApi._api.use('/', function (req, res, next) { | ||||
|         console.log('[DEBUG pkgApiApp]', req.method, req.hostname, req.url); | ||||
|         next(); | ||||
|       }); | ||||
|       //*/
 | ||||
|       packagedApi._api.use('/', require('./oauth3').attachOauth3); | ||||
| 
 | ||||
|       // TODO fix backwards compat
 | ||||
| 
 | ||||
|  | ||||
| @ -52,6 +52,8 @@ | ||||
|     "express": "4.x", | ||||
|     "express-lazy": "^1.1.1", | ||||
|     "express-session": "^1.11.3", | ||||
|     "jsonwebtoken": "^7.4.1", | ||||
|     "jwk-to-pem": "^1.2.6", | ||||
|     "mailchimp-api-v3": "^1.7.0", | ||||
|     "mandrill-api": "^1.0.45", | ||||
|     "masterquest-sqlite3": "git+https://git.daplie.com/node/masterquest-sqlite3.git", | ||||
| @ -59,7 +61,7 @@ | ||||
|     "multiparty": "^4.1.3", | ||||
|     "nodemailer": "^1.4.0", | ||||
|     "nodemailer-mailgun-transport": "1.x", | ||||
|     "oauthcommon": "git+https://git.daplie.com/node/oauthcommon.git", | ||||
|     "oauth3.js": "git+https://git.daplie.com/OAuth3/oauth3.js.git", | ||||
|     "serve-static": "1.x", | ||||
|     "sqlite3-cluster": "git+https://git.daplie.com/coolaj86/sqlite3-cluster.git#v2", | ||||
|     "stripe": "^4.22.0", | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user