mirror of
				https://github.com/therootcompany/keyfetch.js.git
				synced 2024-11-16 17:29:02 +00:00 
			
		
		
		
	initial commit
This commit is contained in:
		
						commit
						6c071af9cd
					
				
							
								
								
									
										173
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,173 @@ | ||||
| # keyfetch | ||||
| 
 | ||||
| Lightweight support for fetching JWKs. | ||||
| 
 | ||||
| Fetches JSON native JWKs and exposes them as PEMs that can be consumed by the `jsonwebtoken` package | ||||
| (and node's native RSA and ECDSA crypto APIs). | ||||
| 
 | ||||
| ## Features | ||||
| 
 | ||||
| Works great for | ||||
| 
 | ||||
| * [x] `jsonwebtoken` (Auth0) | ||||
| * [x] OIDC (OpenID Connect) | ||||
| * [x] .well-known/jwks.json (Auth0) | ||||
| * [x] Other JWKs URLs | ||||
| 
 | ||||
| Crypto Support | ||||
| 
 | ||||
| * [x] JWT verification | ||||
| * [x] RSA (all variants) | ||||
| * [x] EC / ECDSA (NIST variants P-256, P-384) | ||||
| * [ ] esoteric variants (excluded to keep the code featherweight and secure) | ||||
| 
 | ||||
| # Install | ||||
| 
 | ||||
| ```bash | ||||
| npm install --save keyfetch | ||||
| ``` | ||||
| 
 | ||||
| # Usage | ||||
| 
 | ||||
| Retrieve a key list of keys: | ||||
| 
 | ||||
| ```js | ||||
| var keyfetch = require('keyfetch'); | ||||
| 
 | ||||
| keyfetch.oidcJwks("https://example.com/").then(function (results) { | ||||
|   results.forEach(function (result) { | ||||
|     console.log(result.jwk); | ||||
|     console.log(result.thumprint); | ||||
|     console.log(result.pem); | ||||
|   }); | ||||
| }); | ||||
| ``` | ||||
| 
 | ||||
| Quick JWT verification: | ||||
| 
 | ||||
| ```js | ||||
| var keyfetch = require('keyfetch'); | ||||
| var jwt = '...'; | ||||
| 
 | ||||
| keyfetch.verify({ jwt: jwt }).then(function (decoded) { | ||||
|   console.log(decoded); | ||||
| }); | ||||
| ``` | ||||
| 
 | ||||
| Verify a JWT with `jsonwebtoken`: | ||||
| 
 | ||||
| ```js | ||||
| var keyfetch = require('keyfetch'); | ||||
| var jwt = require('jsonwebtoken'); | ||||
| var auth = "..."; // some JWT | ||||
| var token = jwt.decode(auth, { json: true, complete: true }) | ||||
| 
 | ||||
| if (!isTrustedIssuer(token.payload.iss)) { | ||||
|   throw new Error("untrusted issuer"); | ||||
| } | ||||
| 
 | ||||
| keyfetch.oidcJwk( | ||||
|   token.header.kid | ||||
| , token.payload.iss | ||||
| ).then(function (result) { | ||||
|   console.log(result.jwk); | ||||
|   console.log(result.thumprint); | ||||
|   console.log(result.pem); | ||||
| 
 | ||||
|   jwt.verify(jwt, pem); | ||||
| }); | ||||
| ``` | ||||
| 
 | ||||
| *Note*: You might implement `isTrustedIssuer` one of these: | ||||
| 
 | ||||
| ```js | ||||
| function isTrustedIssuer(iss) { | ||||
|   return -1 !== [ 'https://partner.com/', 'https://auth0.com/'].indexOf(iss); | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| ```js | ||||
| function isTrustedIssuer(iss) { | ||||
|   return /^https:/.test(iss) &&         // must be a secure domain | ||||
|     /(\.|^)example\.com$/.test(iss);    // can be example.com or any subdomain | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| # API | ||||
| 
 | ||||
| All API calls will return the RFC standard JWK SHA256 thumbprint as well as a PEM version of the key. | ||||
| 
 | ||||
| Note: When specifying `id`, it may be either `kid` (as in `token.header.kid`) | ||||
| or `thumbprint` (as in `result.thumbprint`). | ||||
| 
 | ||||
| ### JWKs URLs | ||||
| 
 | ||||
| Retrieves keys from a URL such as `https://example.com/jwks/` with the format `{ keys: [ { kid, kty, exp, ... } ] }` | ||||
| and returns the array of keys (as well as thumbprint and jwk-to-pem). | ||||
| 
 | ||||
| ```js | ||||
| keyfetch.jwks(jwksUrl) | ||||
| // Promises [ { jwk, thumbprint, pem } ] or fails | ||||
| ``` | ||||
| 
 | ||||
| ```js | ||||
| keyfetch.jwk(id, jwksUrl) | ||||
| // Promises { jwk, thumbprint, pem } or fails | ||||
| ``` | ||||
| 
 | ||||
| ### Auth0 | ||||
| 
 | ||||
| If `https://example.com/` is used as `issuerUrl` it will resolve to | ||||
| `https://example.com/.well-known/jwks.json` and return the keys. | ||||
| 
 | ||||
| ```js | ||||
| keyfetch.wellKnownJwks(issuerUrl) | ||||
| // Promises [ { jwk, thumbprint, pem } ] or fails | ||||
| ``` | ||||
| 
 | ||||
| ```js | ||||
| keyfetch.wellKnownJwk(id, issuerUrl) | ||||
| // Promises { jwk, thumbprint, pem } or fails | ||||
| ``` | ||||
| 
 | ||||
| ### OIDC | ||||
| 
 | ||||
| If `https://example.com/` is used as `issuerUrl` then it will first resolve to | ||||
| `https://example.com/.well-known/openid-configuration` and then follow `jwks_uri` to return the keys. | ||||
| 
 | ||||
| ```js | ||||
| keyfetch.oidcJwks(issuerUrl) | ||||
| // Promises [ { jwk, thumbprint, pem } ] or fails | ||||
| ``` | ||||
| 
 | ||||
| ```js | ||||
| keyfetch.oidcJwk(id, issuerUrl) | ||||
| // Promises { jwk, thumbprint, pem } or fails | ||||
| ``` | ||||
| 
 | ||||
| ### Verify JWT | ||||
| 
 | ||||
| ```js | ||||
| keyfetch.verify({ jwt: jwk, strategy: 'oidc' }) | ||||
| // Promises a decoded JWT { headers, payload, signature } or fails | ||||
| ``` | ||||
| 
 | ||||
| * `strategy` may be `oidc` (default) , `auth0`, or a direct JWKs url. | ||||
| 
 | ||||
| ### Cache Settings | ||||
| 
 | ||||
| ```js | ||||
| keyfetch.init({ | ||||
|   // set all keys at least 1 hour (regardless of jwk.exp) | ||||
|   mincache: 1 * 60 * 60 | ||||
| 
 | ||||
|   // expire each key after 3 days (regardless of jwk.exp) | ||||
| , maxcache: 3 * 24 * 60 * 60 | ||||
| 
 | ||||
|   // re-fetch a key up to 15 minutes before it expires (only if used) | ||||
| , staletime: 15 * 60 | ||||
| }) | ||||
| ``` | ||||
| 
 | ||||
| There is no background task to cleanup expired keys as of yet. | ||||
| For now you can limit the number of keys fetched by having a simple whitelist. | ||||
							
								
								
									
										20
									
								
								keyfetch-test.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								keyfetch-test.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| var keyfetch = require('./keyfetch.js'); | ||||
| 
 | ||||
| keyfetch.init({}); | ||||
| keyfetch.oidcJwks("https://bigsquid.auth0.com").then(function (jwks) { | ||||
|   console.log(jwks); | ||||
|   return keyfetch.oidcJwk(jwks[0].thumbprint, "https://bigsquid.auth0.com").then(function (jwk) { | ||||
|     console.log(jwk); | ||||
|   }); | ||||
| }).catch(function (err) { | ||||
|   console.error(err); | ||||
| }); | ||||
| 
 | ||||
| /* | ||||
| var jwt = '...'; | ||||
| keyfetch.verify({ jwt: jwt }).catch(function (err) { | ||||
|   console.log(err); | ||||
| }); | ||||
| */ | ||||
							
								
								
									
										301
									
								
								keyfetch.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										301
									
								
								keyfetch.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,301 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| var keyfetch = module.exports; | ||||
| 
 | ||||
| var promisify = require('util').promisify; | ||||
| var requestAsync = promisify(require('@coolaj86/urequest')); | ||||
| var Rasha = require('rasha'); | ||||
| var Eckles = require('eckles'); | ||||
| var mincache = 1 * 60 * 60; | ||||
| var maxcache = 3 * 24 * 60 * 60; | ||||
| var staletime = 15 * 60; | ||||
| var keyCache = {}; | ||||
| 
 | ||||
| /*global Promise*/ | ||||
| function checkMinDefaultMax(opts, key, n, d, x) { | ||||
|   var i = opts[key]; | ||||
|   if (!i && 0 !== i) { return d; } | ||||
|   if (i >= n && i >= x) { | ||||
|     return parseInt(i, 10); | ||||
|   } else { | ||||
|     throw new Error("opts." + key + " should be at least " + n + " and at most " + x + ", not " + i); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| keyfetch.init = function (opts) { | ||||
|   mincache = checkMinDefaultMax(opts, 'mincache', | ||||
|     1 * 60, | ||||
|     mincache, | ||||
|     31 * 24 * 60 * 60 | ||||
|   ); | ||||
|   maxcache = checkMinDefaultMax(opts, 'maxcache', | ||||
|     1 * 60 * 60, | ||||
|     maxcache, | ||||
|     31 * 24 * 60 * 60 | ||||
|   ); | ||||
|   staletime = checkMinDefaultMax(opts, 'staletime', | ||||
|     1 * 60, | ||||
|     staletime, | ||||
|     31 * 24 * 60 * 60 | ||||
|   ); | ||||
| }; | ||||
| keyfetch._oidc = function (iss) { | ||||
|   return Promise.resolve().then(function () { | ||||
|     return requestAsync({ | ||||
|       url: normalizeIss(iss) + '/.well-known/openid-configuration' | ||||
|     , json: true | ||||
|     }).then(function (resp) { | ||||
|       var oidcConf = resp.body; | ||||
|       if (!oidcConf.jwks_uri) { | ||||
|         throw new Error("Failed to retrieve openid configuration"); | ||||
|       } | ||||
|       return oidcConf; | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
| keyfetch._wellKnownJwks = function (iss) { | ||||
|   return Promise.resolve().then(function () { | ||||
|     return keyfetch._jwks(normalizeIss(iss) + '/.well-known/jwks.json'); | ||||
|   }); | ||||
| }; | ||||
| keyfetch._jwks = function (iss) { | ||||
|   return requestAsync({ url: iss, json: true }).then(function (resp) { | ||||
|     return Promise.all(resp.body.keys.map(function (jwk) { | ||||
|       // EC keys have an x values, whereas RSA keys do not
 | ||||
|       var Keypairs = jwk.x ? Eckles : Rasha; | ||||
|       return Keypairs.thumbprint({ jwk: jwk }).then(function (thumbprint) { | ||||
|         return Keypairs.export({ jwk: jwk }).then(function (pem) { | ||||
|           var cacheable = { | ||||
|             jwk: jwk | ||||
|           , thumbprint: thumbprint | ||||
|           , pem: pem | ||||
|           }; | ||||
|           return cacheable; | ||||
|         }); | ||||
|       }); | ||||
|     })); | ||||
|   }); | ||||
| }; | ||||
| keyfetch.jwks = function (jwkUrl) { | ||||
|   // TODO DRY up a bit
 | ||||
|   return keyfetch._jwks(jwkUrl).then(function (results) { | ||||
|     return Promise.all(results.map(function (result) { | ||||
|       return keyfetch._setCache(result.jwk.iss || jwkUrl, result); | ||||
|     })).then(function () { | ||||
|       // cacheable -> hit (keep original externally immutable)
 | ||||
|       return JSON.parse(JSON.stringify(results)); | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
| keyfetch.wellKnownJwks = function (iss) { | ||||
|   // TODO DRY up a bit
 | ||||
|   return keyfetch._wellKnownJwks(iss).then(function (results) { | ||||
|     return Promise.all(results.map(function (result) { | ||||
|       return keyfetch._setCache(result.jwk.iss || iss, result); | ||||
|     })).then(function () { | ||||
|       // result -> hit (keep original externally immutable)
 | ||||
|       return JSON.parse(JSON.stringify(results)); | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
| keyfetch.oidcJwks = function (iss) { | ||||
|   return keyfetch._oidc(iss).then(function (oidcConf) { | ||||
|     // TODO DRY up a bit
 | ||||
|     return keyfetch._jwks(oidcConf.jwks_uri).then(function (results) { | ||||
|       return Promise.all(results.map(function (result) { | ||||
|         return keyfetch._setCache(result.jwk.iss || iss, result); | ||||
|       })).then(function () { | ||||
|         // result -> hit (keep original externally immutable)
 | ||||
|         return JSON.parse(JSON.stringify(results)); | ||||
|       }); | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
| keyfetch.oidcJwk = function (id, iss) { | ||||
|   // TODO [2] DRY this up a bit
 | ||||
|   return keyfetch._checkCache(id, iss).then(function (hit) { | ||||
|     if (hit) { | ||||
|       return Promise.resolve(hit); | ||||
|     } | ||||
| 
 | ||||
|     return keyfetch.oidcJwks(iss).then(function (results) { | ||||
|       var result = results.some(function (result) { | ||||
|         // we already checked iss above
 | ||||
|         return result.jwk.kid === id || result.thumbprint === id; | ||||
|       })[0]; | ||||
| 
 | ||||
|       if (!result) { | ||||
|         throw new Error("No JWK found by kid or thumbprint '" + id + "'"); | ||||
|       } | ||||
|       return result; | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
| keyfetch.wellKnownJwk = function (id, iss) { | ||||
|   // TODO [2] DRY this up a bit
 | ||||
|   return keyfetch._checkCache(id, iss).then(function (hit) { | ||||
|     if (hit) { | ||||
|       return Promise.resolve(hit); | ||||
|     } | ||||
| 
 | ||||
|     return keyfetch.wellKnownJwks(iss).then(function (results) { | ||||
|       var result = results.some(function (result) { | ||||
|         // we already checked iss above
 | ||||
|         return result.jwk.kid === id || result.thumbprint === id; | ||||
|       })[0]; | ||||
| 
 | ||||
|       if (!result) { | ||||
|         throw new Error("No JWK found by kid or thumbprint '" + id + "'"); | ||||
|       } | ||||
|       return result; | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
| keyfetch.jwk = function (id, jwksUrl) { | ||||
|   // TODO [2] DRY this up a bit
 | ||||
|   return keyfetch._checkCache(id, jwksUrl).then(function (hit) { | ||||
|     if (hit) { | ||||
|       return Promise.resolve(hit); | ||||
|     } | ||||
| 
 | ||||
|     return keyfetch.jwks(jwksUrl).then(function (results) { | ||||
|       var result = results.some(function (result) { | ||||
|         // we already checked iss above
 | ||||
|         return result.jwk.kid === id || result.thumbprint === id; | ||||
|       })[0]; | ||||
| 
 | ||||
|       if (!result) { | ||||
|         throw new Error("No JWK found by kid or thumbprint '" + id + "'"); | ||||
|       } | ||||
|       return result; | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
| keyfetch._checkCache = function (id, iss) { | ||||
|   return Promise.resolve().then(function () { | ||||
|     // We cache by thumbprint and (kid + '@' + iss),
 | ||||
|     // so it's safe to check without appending the issuer
 | ||||
|     var hit = keyCache[id]; | ||||
|     if (!hit) { | ||||
|       hit = keyCache[id + '@' + normalizeIss(iss)]; | ||||
|     } | ||||
|     if (!hit) { | ||||
|       return null; | ||||
|     } | ||||
| 
 | ||||
|     var now = Math.round(Date.now() / 1000); | ||||
|     var left = hit.expiresAt - now; | ||||
|     // not guarding number checks since we know that we
 | ||||
|     // set 'now' and 'expiresAt' correctly elsewhere
 | ||||
|     if (left > staletime) { | ||||
|       return JSON.parse(JSON.stringify(hit)); | ||||
|     } | ||||
|     if (left > 0) { | ||||
|       return JSON.parse(JSON.stringify(hit)); | ||||
|     } | ||||
|     return null; | ||||
|   }); | ||||
| }; | ||||
| keyfetch._setCache = function (iss, cacheable) { | ||||
|   // force into a number
 | ||||
|   var expiresAt = parseInt(cacheable.jwk.exp, 10) || 0; | ||||
|   var now = Date.now() / 1000; | ||||
|   var left = expiresAt - now; | ||||
| 
 | ||||
|   // TODO maybe log out when any of these non-ideal cases happen?
 | ||||
|   if (!left) { | ||||
|     expiresAt = now + maxcache; | ||||
|   } else if (left < mincache) { | ||||
|     expiresAt = now + mincache; | ||||
|   } else if (left > maxcache) { | ||||
|     expiresAt = now + maxcache; | ||||
|   } | ||||
| 
 | ||||
|   // cacheable = { jwk, thumprint, pem }
 | ||||
|   cacheable.createdAt = now; | ||||
|   cacheable.expiresAt = expiresAt; | ||||
|   keyCache[cacheable.thumbprint] = cacheable; | ||||
|   keyCache[cacheable.jwk.kid + '@' + normalizeIss(iss)] = cacheable; | ||||
| }; | ||||
| 
 | ||||
| function normalizeIss(iss) { | ||||
|   // We definitely don't want false negatives stemming
 | ||||
|   // from https://example.com vs https://example.com/
 | ||||
|   // We also don't want to allow insecure issuers
 | ||||
|   if (/^http:/.test(iss) && !process.env.KEYFETCH_ALLOW_INSECURE_HTTP) { | ||||
|     // note, we wrap some things in promises just so we can throw here
 | ||||
|     throw new Error("'" + iss + "' is NOT secure. Set env 'KEYFETCH_ALLOW_INSECURE_HTTP=true' to allow for testing."); | ||||
|   } | ||||
|   return iss.replace(/\/$/, ''); | ||||
| } | ||||
| keyfetch._decode = function (jwt) { | ||||
|   var parts = jwt.split('.'); | ||||
|   return { | ||||
|     header: JSON.parse(Buffer.from(parts[0], 'base64')) | ||||
|   , payload: JSON.parse(Buffer.from(parts[1], 'base64')) | ||||
|   , signature: parts[2] //Buffer.from(parts[2], 'base64')
 | ||||
|   }; | ||||
| }; | ||||
| keyfetch.verify = function (opts) { | ||||
|   var jwt = opts.jwt; | ||||
|   return Promise.resolve().then(function () { | ||||
|     var decoded; | ||||
|     var exp; | ||||
|     var nbf; | ||||
|     var valid; | ||||
|     try { | ||||
|       decoded = keyfetch._decode(jwt); | ||||
|       exp = decoded.payload.exp; | ||||
|       nbf = decoded.payload.nbf; | ||||
|     } catch (e) { | ||||
|       throw new Error("could not parse opts.jwt: '" + jwt + "'"); | ||||
|     } | ||||
|     if (exp) { | ||||
|       valid = (parseInt(exp, 10) - (Date.now()/1000) > 0); | ||||
|       if (!valid) { | ||||
|         throw new Error("token's 'exp' has passed or could not parsed: '" + exp + "'"); | ||||
|       } | ||||
|     } | ||||
|     if (nbf) { | ||||
|       valid = (parseInt(nbf, 10) - (Date.now()/1000) <= 0); | ||||
|       if (!valid) { | ||||
|         throw new Error("token's 'nbf' has not been reached or could not parsed: '" + nbf + "'"); | ||||
|       } | ||||
|     } | ||||
|     var kid = decoded.header.kid; | ||||
|     var iss; | ||||
|     var fetcher; | ||||
|     if (!opts.strategy || 'oidc' === opts.strategy) { | ||||
|       iss = decoded.payload.iss; | ||||
|       fetcher = keyfetch.oidcJwks; | ||||
|     } else if ('auth0' === opts.strategy || 'well-known' === opts.strategy) { | ||||
|       iss = decoded.payload.iss; | ||||
|       fetcher = keyfetch.wellKnownJwks; | ||||
|     } else { | ||||
|       iss = opts.strategy; | ||||
|       fetcher = keyfetch.jwks; | ||||
|     } | ||||
|     function verify(jwk, payload) { | ||||
|       var alg = 'RSA-SHA' + decoded.header.alg.replace(/[^\d]+/i, ''); | ||||
|       return require('crypto') | ||||
|         .createVerify(alg) | ||||
|         .update(jwt.split('.')[0] + '.' + payload) | ||||
|         .verify(jwk.pem, decoded.signature, 'base64'); | ||||
|     } | ||||
|     return fetcher(iss).then(function (jwks) { | ||||
|       var payload = jwt.split('.')[1]; // as string, as it was signed
 | ||||
|       if (jwks.some(function (jwk) { | ||||
|         if (kid) { | ||||
|           if (kid !== jwk.kid && kid !== jwk.thumbprint) { return; } | ||||
|           if (verify(jwk, payload)) { return true; } | ||||
|           throw new Error('token signature verification was unsuccessful'); | ||||
|         } else { | ||||
|           if (verify(jwk, payload)) { return true; } | ||||
|         } | ||||
|       })) { | ||||
|         return decoded; | ||||
|       } | ||||
|       throw new Error("Retrieved a list of keys, but none of them matched the 'kid' (key id) of the token."); | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
							
								
								
									
										23
									
								
								package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @ -0,0 +1,23 @@ | ||||
| { | ||||
|   "name": "keyfetch", | ||||
|   "version": "1.1.0", | ||||
|   "lockfileVersion": 1, | ||||
|   "requires": true, | ||||
|   "dependencies": { | ||||
|     "@coolaj86/urequest": { | ||||
|       "version": "1.3.6", | ||||
|       "resolved": "https://registry.npmjs.org/@coolaj86/urequest/-/urequest-1.3.6.tgz", | ||||
|       "integrity": "sha512-9rBXLFSb5D19opGeXdD/WuiFJsA4Pk2r8VUGEAeUZUxB1a2zB47K85BKAx3Gy9i4nZwg22ejlJA+q9DVrpQlbA==" | ||||
|     }, | ||||
|     "eckles": { | ||||
|       "version": "1.4.0", | ||||
|       "resolved": "https://registry.npmjs.org/eckles/-/eckles-1.4.0.tgz", | ||||
|       "integrity": "sha512-Bm5dpwhsBuoCHvKCY3gAvP8XFyXH7im8uAu3szykpVNbFBdC+lOuV8vLC8fvTYRZBfFqB+k/P6ud/ZPVO2V2tA==" | ||||
|     }, | ||||
|     "rasha": { | ||||
|       "version": "1.2.1", | ||||
|       "resolved": "https://registry.npmjs.org/rasha/-/rasha-1.2.1.tgz", | ||||
|       "integrity": "sha512-cs4Hu/rVF3/Qucq+V7lxSz449VfHNMVXJaeajAHno9H5FC1PWlmS4NM6IAX5jPKFF0IC2rOdHdf7iNxQuIWZag==" | ||||
|     } | ||||
|   } | ||||
| } | ||||
							
								
								
									
										35
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,35 @@ | ||||
| { "author": { | ||||
|     "name": "AJ ONeal", | ||||
|     "email": "solderjs@gmail.com" | ||||
|   }, | ||||
|   "bundleDependencies": false, | ||||
|   "dependencies": { | ||||
|     "@coolaj86/urequest": "^1.3.6", | ||||
|     "eckles": "^1.4.0", | ||||
|     "rasha": "^1.2.1" | ||||
|   }, | ||||
|   "deprecated": false, | ||||
|   "description": "Lightweight support for fetching JWKs.", | ||||
|   "files": [ | ||||
|     "keyfetch-test.js" | ||||
|   ], | ||||
|   "keywords": [ | ||||
|     "jwks", | ||||
|     "jwk", | ||||
|     "jwt", | ||||
|     "auth0", | ||||
|     "pem", | ||||
|     "RSA", | ||||
|     "EC", | ||||
|     "ECDSA", | ||||
|     "OIDC", | ||||
|     "well-known" | ||||
|   ], | ||||
|   "license": "MPL-2.0", | ||||
|   "main": "keyfetch.js", | ||||
|   "name": "keyfetch", | ||||
|   "scripts": { | ||||
|     "test": "echo \"Error: no test specified\" && exit 1" | ||||
|   }, | ||||
|   "version": "1.1.1" | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user