mirror of
				https://github.com/therootcompany/keyfetch.js.git
				synced 2025-11-04 07:12:50 +00:00 
			
		
		
		
	Compare commits
	
		
			18 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| a84e833571 | |||
| 076246e4d0 | |||
| 4c85fc4009 | |||
| d5647ea905 | |||
| 2ea44e3a46 | |||
| 5ef53ecb23 | |||
| 604b42c7ef | |||
| c57a08f0cd | |||
| a3539b0941 | |||
| 523a4f0d1a | |||
| 7d5889b4de | |||
| 79e6758f9f | |||
| 842807f92b | |||
| a648de58d8 | |||
| 8ec2d98645 | |||
| 9141962456 | |||
| aba70bf0ff | |||
| a32b942861 | 
							
								
								
									
										22
									
								
								.jshintrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								.jshintrc
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					    "browser": true,
 | 
				
			||||||
 | 
					    "node": true,
 | 
				
			||||||
 | 
					    "esversion": 11,
 | 
				
			||||||
 | 
					    "curly": true,
 | 
				
			||||||
 | 
					    "sub": true,
 | 
				
			||||||
 | 
					    "bitwise": true,
 | 
				
			||||||
 | 
					    "eqeqeq": true,
 | 
				
			||||||
 | 
					    "forin": true,
 | 
				
			||||||
 | 
					    "freeze": true,
 | 
				
			||||||
 | 
					    "immed": true,
 | 
				
			||||||
 | 
					    "latedef": "nofunc",
 | 
				
			||||||
 | 
					    "nonbsp": true,
 | 
				
			||||||
 | 
					    "nonew": true,
 | 
				
			||||||
 | 
					    "plusplus": true,
 | 
				
			||||||
 | 
					    "undef": true,
 | 
				
			||||||
 | 
					    "unused": "vars",
 | 
				
			||||||
 | 
					    "strict": true,
 | 
				
			||||||
 | 
					    "maxdepth": 3,
 | 
				
			||||||
 | 
					    "maxstatements": 100,
 | 
				
			||||||
 | 
					    "maxcomplexity": 10
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										7
									
								
								.prettierrc.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								.prettierrc.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "bracketSpacing": true,
 | 
				
			||||||
 | 
					  "printWidth": 120,
 | 
				
			||||||
 | 
					  "tabWidth": 4,
 | 
				
			||||||
 | 
					  "trailingComma": "none",
 | 
				
			||||||
 | 
					  "useTabs": false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										11
									
								
								CHANGELOG.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								CHANGELOG.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					# v3.0.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Breaking Change**: Standardize error `message`s (now they're more client-friendly).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# v2.1.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Feature: Add `code`, `status`, and `details` to errors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# v2.0.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Breaking Change**: require `issuers` array (rather than `["*"]` by default).
 | 
				
			||||||
							
								
								
									
										205
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										205
									
								
								README.md
									
									
									
									
									
								
							@ -1,4 +1,4 @@
 | 
				
			|||||||
# keyfetch
 | 
					# [keyfetch](https://git.rootprojects.org/root/keyfetch.js)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Lightweight support for fetching JWKs.
 | 
					Lightweight support for fetching JWKs.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -9,17 +9,28 @@ Fetches JSON native JWKs and exposes them as PEMs that can be consumed by the `j
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
Works great for
 | 
					Works great for
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* [x] `jsonwebtoken` (Auth0)
 | 
					-   [x] `jsonwebtoken` (Auth0)
 | 
				
			||||||
* [x] OIDC (OpenID Connect)
 | 
					-   [x] OIDC (OpenID Connect)
 | 
				
			||||||
* [x] .well-known/jwks.json (Auth0)
 | 
					-   [x] .well-known/jwks.json (Auth0, Okta)
 | 
				
			||||||
* [x] Other JWKs URLs
 | 
					-   [x] Other JWKs URLs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Crypto Support
 | 
					Crypto Support
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* [x] JWT verification
 | 
					-   [x] JWT verification
 | 
				
			||||||
* [x] RSA (all variants)
 | 
					-   [x] RSA (all variants)
 | 
				
			||||||
* [x] EC / ECDSA (NIST variants P-256, P-384)
 | 
					-   [x] EC / ECDSA (NIST variants P-256, P-384)
 | 
				
			||||||
* [ ] esoteric variants (excluded to keep the code featherweight and secure)
 | 
					-   [x] Sane error codes
 | 
				
			||||||
 | 
					-   [ ] esoteric variants (excluded to keep the code featherweight and secure)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Table of Contents
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-   Install
 | 
				
			||||||
 | 
					-   Usage
 | 
				
			||||||
 | 
					-   API
 | 
				
			||||||
 | 
					    -   Auth0 / Okta
 | 
				
			||||||
 | 
					    -   OIDC
 | 
				
			||||||
 | 
					-   Errors
 | 
				
			||||||
 | 
					-   Change Log
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Install
 | 
					# Install
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -32,73 +43,71 @@ npm install --save keyfetch
 | 
				
			|||||||
Retrieve a key list of keys:
 | 
					Retrieve a key list of keys:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```js
 | 
					```js
 | 
				
			||||||
var keyfetch = require('keyfetch');
 | 
					var keyfetch = require("keyfetch");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
keyfetch.oidcJwks("https://example.com/").then(function (results) {
 | 
					keyfetch.oidcJwks("https://example.com/").then(function (results) {
 | 
				
			||||||
  results.forEach(function (result) {
 | 
					    results.forEach(function (result) {
 | 
				
			||||||
    console.log(result.jwk);
 | 
					        console.log(result.jwk);
 | 
				
			||||||
    console.log(result.thumprint);
 | 
					        console.log(result.thumprint);
 | 
				
			||||||
    console.log(result.pem);
 | 
					        console.log(result.pem);
 | 
				
			||||||
  });
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Quick JWT verification (for authentication):
 | 
					Quick JWT verification (for authentication):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```js
 | 
					```js
 | 
				
			||||||
var keyfetch = require('keyfetch');
 | 
					var keyfetch = require("keyfetch");
 | 
				
			||||||
var jwt = '...';
 | 
					var jwt = "...";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
keyfetch.jwt.verify(jwt).then(function (decoded) {
 | 
					keyfetch.jwt.verify(jwt).then(function (decoded) {
 | 
				
			||||||
  console.log(decoded);
 | 
					    console.log(decoded);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
JWT verification (for authorization):
 | 
					JWT verification (for authorization):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```js
 | 
					```js
 | 
				
			||||||
var options = { issuers: ['https://example.com/'], claims: { role: 'admin' } };
 | 
					var options = { issuers: ["https://example.com/"], claims: { role: "admin" } };
 | 
				
			||||||
keyfetch.jwt.verify(jwt, options).then(function (decoded) {
 | 
					keyfetch.jwt.verify(jwt, options).then(function (decoded) {
 | 
				
			||||||
  console.log(decoded);
 | 
					    console.log(decoded);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Verify a JWT with `jsonwebtoken`:
 | 
					Verify a JWT with `jsonwebtoken`:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```js
 | 
					```js
 | 
				
			||||||
var keyfetch = require('keyfetch');
 | 
					var keyfetch = require("keyfetch");
 | 
				
			||||||
var jwt = require('jsonwebtoken');
 | 
					var jwt = require("jsonwebtoken");
 | 
				
			||||||
var auth = "..."; // some JWT
 | 
					var auth = "..."; // some JWT
 | 
				
			||||||
var token = jwt.decode(auth, { json: true, complete: true })
 | 
					var token = jwt.decode(auth, { json: true, complete: true });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if (!isTrustedIssuer(token.payload.iss)) {
 | 
					if (!isTrustedIssuer(token.payload.iss)) {
 | 
				
			||||||
  throw new Error("untrusted issuer");
 | 
					    throw new Error("untrusted issuer");
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
keyfetch.oidcJwk(
 | 
					keyfetch.oidcJwk(token.header.kid, token.payload.iss).then(function (result) {
 | 
				
			||||||
  token.header.kid
 | 
					    console.log(result.jwk);
 | 
				
			||||||
, token.payload.iss
 | 
					    console.log(result.thumprint);
 | 
				
			||||||
).then(function (result) {
 | 
					    console.log(result.pem);
 | 
				
			||||||
  console.log(result.jwk);
 | 
					 | 
				
			||||||
  console.log(result.thumprint);
 | 
					 | 
				
			||||||
  console.log(result.pem);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  jwt.jwt.verify(jwt, { jwk: result.jwk });
 | 
					    jwt.jwt.verify(jwt, { jwk: result.jwk });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
*Note*: You might implement `isTrustedIssuer` one of these:
 | 
					_Note_: You might implement `isTrustedIssuer` one of these:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```js
 | 
					```js
 | 
				
			||||||
function isTrustedIssuer(iss) {
 | 
					function isTrustedIssuer(iss) {
 | 
				
			||||||
  return -1 !== [ 'https://partner.com/', 'https://auth0.com/'].indexOf(iss);
 | 
					    return -1 !== ["https://partner.com/", "https://auth0.com/"].indexOf(iss);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```js
 | 
					```js
 | 
				
			||||||
function isTrustedIssuer(iss) {
 | 
					function isTrustedIssuer(iss) {
 | 
				
			||||||
  return /^https:/.test(iss) &&         // must be a secure domain
 | 
					    return (
 | 
				
			||||||
    /(\.|^)example\.com$/.test(iss);    // can be example.com or any subdomain
 | 
					        /^https:/.test(iss) && /(\.|^)example\.com$/.test(iss) // must be a secure domain
 | 
				
			||||||
 | 
					    ); // can be example.com or any subdomain
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -115,12 +124,12 @@ Retrieves keys from a URL such as `https://example.com/jwks/` with the format `{
 | 
				
			|||||||
and returns the array of keys (as well as thumbprint and jwk-to-pem).
 | 
					and returns the array of keys (as well as thumbprint and jwk-to-pem).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```js
 | 
					```js
 | 
				
			||||||
keyfetch.jwks(jwksUrl)
 | 
					keyfetch.jwks(jwksUrl);
 | 
				
			||||||
// Promises [ { jwk, thumbprint, pem } ] or fails
 | 
					// Promises [ { jwk, thumbprint, pem } ] or fails
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```js
 | 
					```js
 | 
				
			||||||
keyfetch.jwk(id, jwksUrl)
 | 
					keyfetch.jwk(id, jwksUrl);
 | 
				
			||||||
// Promises { jwk, thumbprint, pem } or fails
 | 
					// Promises { jwk, thumbprint, pem } or fails
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -130,12 +139,12 @@ If `https://example.com/` is used as `issuerUrl` it will resolve to
 | 
				
			|||||||
`https://example.com/.well-known/jwks.json` and return the keys.
 | 
					`https://example.com/.well-known/jwks.json` and return the keys.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```js
 | 
					```js
 | 
				
			||||||
keyfetch.wellKnownJwks(issuerUrl)
 | 
					keyfetch.wellKnownJwks(issuerUrl);
 | 
				
			||||||
// Promises [ { jwk, thumbprint, pem } ] or fails
 | 
					// Promises [ { jwk, thumbprint, pem } ] or fails
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```js
 | 
					```js
 | 
				
			||||||
keyfetch.wellKnownJwk(id, issuerUrl)
 | 
					keyfetch.wellKnownJwk(id, issuerUrl);
 | 
				
			||||||
// Promises { jwk, thumbprint, pem } or fails
 | 
					// Promises { jwk, thumbprint, pem } or fails
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -145,12 +154,12 @@ 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.
 | 
					`https://example.com/.well-known/openid-configuration` and then follow `jwks_uri` to return the keys.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```js
 | 
					```js
 | 
				
			||||||
keyfetch.oidcJwks(issuerUrl)
 | 
					keyfetch.oidcJwks(issuerUrl);
 | 
				
			||||||
// Promises [ { jwk, thumbprint, pem } ] or fails
 | 
					// Promises [ { jwk, thumbprint, pem } ] or fails
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```js
 | 
					```js
 | 
				
			||||||
keyfetch.oidcJwk(id, issuerUrl)
 | 
					keyfetch.oidcJwk(id, issuerUrl);
 | 
				
			||||||
// Promises { jwk, thumbprint, pem } or fails
 | 
					// Promises { jwk, thumbprint, pem } or fails
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -161,8 +170,8 @@ This can accept a _JWT string_ (compact JWS) or a _decoded JWT object_ (JWS).
 | 
				
			|||||||
This can be used purely for verifying pure authentication tokens, as well as authorization tokens.
 | 
					This can be used purely for verifying pure authentication tokens, as well as authorization tokens.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```js
 | 
					```js
 | 
				
			||||||
keyfetch.jwt.verify(jwt, { strategy: 'oidc' }).then(function (verified) {
 | 
					keyfetch.jwt.verify(jwt, { strategy: "oidc" }).then(function (verified) {
 | 
				
			||||||
  /*
 | 
					    /*
 | 
				
			||||||
    { protected: '...'  // base64 header
 | 
					    { protected: '...'  // base64 header
 | 
				
			||||||
    , payload: '...'    // base64 payload
 | 
					    , payload: '...'    // base64 payload
 | 
				
			||||||
    , signature: '...'  // base64 signature
 | 
					    , signature: '...'  // base64 signature
 | 
				
			||||||
@ -173,26 +182,27 @@ keyfetch.jwt.verify(jwt, { strategy: 'oidc' }).then(function (verified) {
 | 
				
			|||||||
});
 | 
					});
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
When used for authorization, it's important to specify which `issuers` are allowed
 | 
					When used for authorization, it's important to specify a limited set of trusted `issuers`. \
 | 
				
			||||||
(otherwise anyone can create a valid token with whatever any claims they want).
 | 
					When using for federated authentication you may set `issuers = ["*"]` - but **DO NOT** trust claims such as `email` and `email_verified`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
If your authorization `claims` can be expressed as exact string matches, you can specify those too.
 | 
					If your authorization `claims` can be expressed as exact string matches, you can specify those too.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```js
 | 
					```js
 | 
				
			||||||
keyfetch.jwt.verify(jwt, {
 | 
					keyfetch.jwt.verify(jwt, {
 | 
				
			||||||
  strategy: 'oidc'
 | 
					  strategy: 'oidc',
 | 
				
			||||||
, issuers: [ 'https://example.com/' ]
 | 
					  issuers: [ 'https://example.com/' ],
 | 
				
			||||||
, claims: { role: 'admin', sub: 'abc', group: 'xyz' }
 | 
					  //iss: 'https://example.com/',
 | 
				
			||||||
 | 
					  claims: { role: 'admin', sub: 'abc', group: 'xyz' }
 | 
				
			||||||
}).then(function (verified) {
 | 
					}).then(function (verified) {
 | 
				
			||||||
 | 
					 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* `strategy` may be `oidc` (default) , `auth0`, or a direct JWKs url.
 | 
					-   `strategy` may be `oidc` (default) , `auth0`, or a direct JWKs url.
 | 
				
			||||||
* `issuers` must be a list of https urls (though http is allowed for things like Docker swarm)
 | 
					-   `issuers` must be a list of https urls (though http is allowed for things like Docker swarm), or '\*'
 | 
				
			||||||
* `claims` is an object with arbitrary keys (i.e. everything except for the standard `iat`, `exp`, `jti`, etc)
 | 
					-   `iss` is like `issuers`, but only one
 | 
				
			||||||
* `exp` may be set to `false` if you're validating on your own (i.e. allowing time drift leeway)
 | 
					-   `claims` is an object with arbitrary keys (i.e. everything except for the standard `iat`, `exp`, `jti`, etc)
 | 
				
			||||||
* `jwks` can be used to specify a list of allowed public key rather than fetching them (i.e. for offline unit tests)
 | 
					-   `exp` may be set to `false` if you're validating on your own (i.e. allowing time drift leeway)
 | 
				
			||||||
* `jwk` same as above, but a single key rather than a list
 | 
					-   `jwks` can be used to specify a list of allowed public key rather than fetching them (i.e. for offline unit tests)
 | 
				
			||||||
 | 
					-   `jwk` same as above, but a single key rather than a list
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Decode JWT
 | 
					### Decode JWT
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -216,19 +226,19 @@ It's easier just to show the code than to explain the example.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
```js
 | 
					```js
 | 
				
			||||||
keyfetch.jwt.decode = function (jwt) {
 | 
					keyfetch.jwt.decode = function (jwt) {
 | 
				
			||||||
  // Unpack JWS from "compact" form
 | 
					    // Unpack JWS from "compact" form
 | 
				
			||||||
  var parts = jwt.split('.');
 | 
					    var parts = jwt.split(".");
 | 
				
			||||||
  var obj = {
 | 
					    var obj = {
 | 
				
			||||||
    protected: parts[0]
 | 
					        protected: parts[0],
 | 
				
			||||||
  , payload: parts[1]
 | 
					        payload: parts[1],
 | 
				
			||||||
  , signature: parts[2]
 | 
					        signature: parts[2]
 | 
				
			||||||
  };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Decode JWT properties from JWS as unordered objects
 | 
					    // Decode JWT properties from JWS as unordered objects
 | 
				
			||||||
  obj.header = JSON.parse(Buffer.from(obj.protected, 'base64'));
 | 
					    obj.header = JSON.parse(Buffer.from(obj.protected, "base64"));
 | 
				
			||||||
  obj.claims = JSON.parse(Buffer.from(obj.payload, 'base64'));
 | 
					    obj.claims = JSON.parse(Buffer.from(obj.payload, "base64"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return obj;
 | 
					    return obj;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -236,16 +246,63 @@ keyfetch.jwt.decode = function (jwt) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
```js
 | 
					```js
 | 
				
			||||||
keyfetch.init({
 | 
					keyfetch.init({
 | 
				
			||||||
  // set all keys at least 1 hour (regardless of jwk.exp)
 | 
					    // set all keys at least 1 hour (regardless of jwk.exp)
 | 
				
			||||||
  mincache: 1 * 60 * 60
 | 
					    mincache: 1 * 60 * 60,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // expire each key after 3 days (regardless of jwk.exp)
 | 
					    // expire each key after 3 days (regardless of jwk.exp)
 | 
				
			||||||
, maxcache: 3 * 24 * 60 * 60
 | 
					    maxcache: 3 * 24 * 60 * 60,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // re-fetch a key up to 15 minutes before it expires (only if used)
 | 
					    // re-fetch a key up to 15 minutes before it expires (only if used)
 | 
				
			||||||
, staletime: 15 * 60
 | 
					    staletime: 15 * 60
 | 
				
			||||||
})
 | 
					});
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
There is no background task to cleanup expired keys as of yet.
 | 
					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.
 | 
					For now you can limit the number of keys fetched by having a simple whitelist.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Errors
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					`JSON.stringify()`d errors look like this:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```js
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  code: "INVALID_JWT",
 | 
				
			||||||
 | 
					  status: 401,
 | 
				
			||||||
 | 
					  details: [ "jwt.claims.exp = 1634804500", "DEBUG: helpful message" ]
 | 
				
			||||||
 | 
					  message: "token's 'exp' has passed or could not parsed: 1634804500"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SemVer Compatibility:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-   `code` & `status` will remain the same.
 | 
				
			||||||
 | 
					-   `message` is **NOT** included in the semver compatibility guarantee (we intend to make them more client-friendly), neither is `detail` at this time (but it will be once we decide on what it should be).
 | 
				
			||||||
 | 
					-   `details` may be added to, but not subtracted from
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					| Hint              | Code          | Status | Message (truncated)                                    |
 | 
				
			||||||
 | 
					| ----------------- | ------------- | ------ | ------------------------------------------------------ |
 | 
				
			||||||
 | 
					| bad gateway       | BAD_GATEWAY   | 502    | The auth token could not be verified because our se... |
 | 
				
			||||||
 | 
					| insecure issuer   | MALFORMED_JWT | 400    | The auth token could not be verified because our se... |
 | 
				
			||||||
 | 
					| parse error       | MALFORMED_JWT | 400    | The auth token could not be verified because it is ... |
 | 
				
			||||||
 | 
					| no issuer         | MALFORMED_JWT | 400    | The auth token could not be verified because it doe... |
 | 
				
			||||||
 | 
					| malformed exp     | MALFORMED_JWT | 400    | The auth token could not be verified because it's e... |
 | 
				
			||||||
 | 
					| expired           | INVALID_JWT   | 401    | The auth token is expired. To try again, go to the ... |
 | 
				
			||||||
 | 
					| inactive          | INVALID_JWT   | 401    | The auth token isn't valid yet. It's activation dat... |
 | 
				
			||||||
 | 
					| bad signature     | INVALID_JWT   | 401    | The auth token did not pass verification because it... |
 | 
				
			||||||
 | 
					| jwk not found old | INVALID_JWT   | 401    | The auth token did not pass verification because ou... |
 | 
				
			||||||
 | 
					| jwk not found     | INVALID_JWT   | 401    | The auth token did not pass verification because ou... |
 | 
				
			||||||
 | 
					| no jwkws uri      | INVALID_JWT   | 401    | The auth token did not pass verification because it... |
 | 
				
			||||||
 | 
					| unknown issuer    | INVALID_JWT   | 401    | The auth token did not pass verification because it... |
 | 
				
			||||||
 | 
					| failed claims     | INVALID_JWT   | 401    | The auth token did not pass verification because it... |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Change Log
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Minor Breaking changes (with a major version bump):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-   v3.0.0
 | 
				
			||||||
 | 
					    -   reworked error messages (also available in v2.1.0 as `client_message`)
 | 
				
			||||||
 | 
					    -   started using `let` and template strings (drops _really_ old node compat)
 | 
				
			||||||
 | 
					-   v2.0.0
 | 
				
			||||||
 | 
					    -   changes from the default `issuers = ["*"]` to requiring that an issuer (or public jwk for verification) is specified
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					See other changes in [CHANGELOG.md](./CHANGELOG.md).
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										213
									
								
								keyfetch-test.js
									
									
									
									
									
								
							
							
						
						
									
										213
									
								
								keyfetch-test.js
									
									
									
									
									
								
							@ -1,80 +1,151 @@
 | 
				
			|||||||
'use strict';
 | 
					"use strict";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var keyfetch = require('./keyfetch.js');
 | 
					var keyfetch = require("./keyfetch.js");
 | 
				
			||||||
var testIss = "https://example.auth0.com";
 | 
					var testIss = "https://example.auth0.com";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
keyfetch.init({});
 | 
					keyfetch.init({});
 | 
				
			||||||
keyfetch.oidcJwks(testIss).then(function (hits) {
 | 
					keyfetch
 | 
				
			||||||
  keyfetch._clear();
 | 
					    .oidcJwks(testIss)
 | 
				
			||||||
  //console.log(hits);
 | 
					    .then(function (hits) {
 | 
				
			||||||
  return keyfetch.oidcJwk(hits[0].thumbprint, testIss).then(function () {
 | 
					        keyfetch._clear();
 | 
				
			||||||
    return keyfetch.oidcJwk(hits[0].thumbprint, testIss).then(function (/*jwk*/) {
 | 
					        //console.log(hits);
 | 
				
			||||||
      //console.log(jwk);
 | 
					        return keyfetch.oidcJwk(hits[0].thumbprint, testIss).then(function () {
 | 
				
			||||||
 | 
					            return keyfetch.oidcJwk(hits[0].thumbprint, testIss).then(function (/*jwk*/) {
 | 
				
			||||||
 | 
					                //console.log(jwk);
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .then(function () {
 | 
				
			||||||
 | 
					        console.log("Fetching PASSES");
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .catch(function (err) {
 | 
				
			||||||
 | 
					        console.error("NONE SHALL PASS!");
 | 
				
			||||||
 | 
					        console.error(err);
 | 
				
			||||||
 | 
					        process.exit(1);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
}).then(function () {
 | 
					 | 
				
			||||||
  console.log("Fetching PASSES");
 | 
					 | 
				
			||||||
}).catch(function (err) {
 | 
					 | 
				
			||||||
  console.error("NONE SHALL PASS!");
 | 
					 | 
				
			||||||
  console.error(err);
 | 
					 | 
				
			||||||
  process.exit(1);
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*global Promise*/
 | 
					/*global Promise*/
 | 
				
			||||||
var keypairs = require('keypairs.js');
 | 
					var keypairs = require("keypairs");
 | 
				
			||||||
keypairs.generate().then(function (pair) {
 | 
					keypairs.generate().then(function (pair) {
 | 
				
			||||||
  return Promise.all([
 | 
					    var iss = "https://example.com/";
 | 
				
			||||||
    keypairs.signJwt({
 | 
					    return Promise.all([
 | 
				
			||||||
      jwk: pair.private, iss: 'https://example.com/', sub: 'mikey', exp: '1h'
 | 
					        keypairs
 | 
				
			||||||
    }).then(function (jwt) {
 | 
					            .signJwt({
 | 
				
			||||||
      return Promise.all([
 | 
					                jwk: pair.private,
 | 
				
			||||||
        keyfetch.jwt.verify(jwt, { jwk: pair.public }).then(function (verified) {
 | 
					                iss: iss,
 | 
				
			||||||
          if (!(verified.claims && verified.claims.exp)) {
 | 
					                sub: "mikey",
 | 
				
			||||||
            throw new Error("malformed decoded token");
 | 
					                exp: "1h"
 | 
				
			||||||
          }
 | 
					            })
 | 
				
			||||||
 | 
					            .then(function (jwt) {
 | 
				
			||||||
 | 
					                return Promise.all([
 | 
				
			||||||
 | 
					                    keyfetch.jwt.verify(jwt, { jwk: pair.public, iss: "*" }).then(function (verified) {
 | 
				
			||||||
 | 
					                        if (!(verified.claims && verified.claims.exp)) {
 | 
				
			||||||
 | 
					                            throw new Error("malformed decoded token");
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }),
 | 
				
			||||||
 | 
					                    keyfetch.jwt
 | 
				
			||||||
 | 
					                        .verify(keyfetch.jwt.decode(jwt), { jwk: pair.public, iss: iss })
 | 
				
			||||||
 | 
					                        .then(function (verified) {
 | 
				
			||||||
 | 
					                            if (!(verified.claims && verified.claims.exp)) {
 | 
				
			||||||
 | 
					                                throw new Error("malformed decoded token");
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        }),
 | 
				
			||||||
 | 
					                    keyfetch.jwt.verify(jwt, { jwks: [pair.public], issuers: [iss] }),
 | 
				
			||||||
 | 
					                    keyfetch.jwt.verify(jwt, {
 | 
				
			||||||
 | 
					                        jwk: pair.public,
 | 
				
			||||||
 | 
					                        issuers: ["https://example.com/"]
 | 
				
			||||||
 | 
					                    }),
 | 
				
			||||||
 | 
					                    keyfetch.jwt.verify(jwt, {
 | 
				
			||||||
 | 
					                        jwk: pair.public,
 | 
				
			||||||
 | 
					                        issuers: ["https://example.com"]
 | 
				
			||||||
 | 
					                    }),
 | 
				
			||||||
 | 
					                    keyfetch.jwt.verify(jwt, {
 | 
				
			||||||
 | 
					                        jwk: pair.public,
 | 
				
			||||||
 | 
					                        issuers: ["example.com"]
 | 
				
			||||||
 | 
					                    }),
 | 
				
			||||||
 | 
					                    keyfetch.jwt.verify(jwt, {
 | 
				
			||||||
 | 
					                        jwk: pair.public,
 | 
				
			||||||
 | 
					                        issuers: ["example.com/"]
 | 
				
			||||||
 | 
					                    }),
 | 
				
			||||||
 | 
					                    keyfetch.jwt.verify(jwt, {
 | 
				
			||||||
 | 
					                        jwk: pair.public,
 | 
				
			||||||
 | 
					                        issuers: ["*"]
 | 
				
			||||||
 | 
					                    }),
 | 
				
			||||||
 | 
					                    keyfetch.jwt
 | 
				
			||||||
 | 
					                        .verify(jwt, {
 | 
				
			||||||
 | 
					                            jwk: pair.public,
 | 
				
			||||||
 | 
					                            issuers: ["http://example.com"]
 | 
				
			||||||
 | 
					                        })
 | 
				
			||||||
 | 
					                        .then(e("bad scheme"))
 | 
				
			||||||
 | 
					                        .catch(throwIfNotExpected),
 | 
				
			||||||
 | 
					                    keyfetch.jwt
 | 
				
			||||||
 | 
					                        .verify(jwt, {
 | 
				
			||||||
 | 
					                            jwk: pair.public,
 | 
				
			||||||
 | 
					                            issuers: ["https://www.example.com"]
 | 
				
			||||||
 | 
					                        })
 | 
				
			||||||
 | 
					                        .then(e("bad prefix"))
 | 
				
			||||||
 | 
					                        .catch(throwIfNotExpected),
 | 
				
			||||||
 | 
					                    keyfetch.jwt
 | 
				
			||||||
 | 
					                        .verify(jwt, {
 | 
				
			||||||
 | 
					                            jwk: pair.public,
 | 
				
			||||||
 | 
					                            issuers: ["https://wexample.com"]
 | 
				
			||||||
 | 
					                        })
 | 
				
			||||||
 | 
					                        .then(e("bad sld"))
 | 
				
			||||||
 | 
					                        .catch(throwIfNotExpected),
 | 
				
			||||||
 | 
					                    keyfetch.jwt
 | 
				
			||||||
 | 
					                        .verify(jwt, {
 | 
				
			||||||
 | 
					                            jwk: pair.public,
 | 
				
			||||||
 | 
					                            issuers: ["https://example.comm"]
 | 
				
			||||||
 | 
					                        })
 | 
				
			||||||
 | 
					                        .then(e("bad tld"))
 | 
				
			||||||
 | 
					                        .catch(throwIfNotExpected),
 | 
				
			||||||
 | 
					                    keyfetch.jwt.verify(jwt, {
 | 
				
			||||||
 | 
					                        jwk: pair.public,
 | 
				
			||||||
 | 
					                        claims: { iss: "https://example.com/" }
 | 
				
			||||||
 | 
					                    }),
 | 
				
			||||||
 | 
					                    keyfetch.jwt
 | 
				
			||||||
 | 
					                        .verify(jwt, {
 | 
				
			||||||
 | 
					                            jwk: pair.public,
 | 
				
			||||||
 | 
					                            claims: { iss: "https://example.com" }
 | 
				
			||||||
 | 
					                        })
 | 
				
			||||||
 | 
					                        .then(e("inexact claim"))
 | 
				
			||||||
 | 
					                        .catch(throwIfNotExpected)
 | 
				
			||||||
 | 
					                ]);
 | 
				
			||||||
 | 
					            }),
 | 
				
			||||||
 | 
					        keypairs
 | 
				
			||||||
 | 
					            .signJwt({
 | 
				
			||||||
 | 
					                jwk: pair.private,
 | 
				
			||||||
 | 
					                iss: false,
 | 
				
			||||||
 | 
					                sub: "mikey",
 | 
				
			||||||
 | 
					                exp: "1h"
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .then(function (jwt) {
 | 
				
			||||||
 | 
					                return Promise.all([
 | 
				
			||||||
 | 
					                    // test that the old behavior of defaulting to '*' still works
 | 
				
			||||||
 | 
					                    keyfetch.jwt
 | 
				
			||||||
 | 
					                        .verify(jwt, { jwk: pair.public })
 | 
				
			||||||
 | 
					                        .then(e("should have issued security warning about allow all by default"))
 | 
				
			||||||
 | 
					                        .catch(throwIfNotExpected),
 | 
				
			||||||
 | 
					                    keyfetch.jwt.verify(jwt, { jwk: pair.public, issuers: ["*"] }),
 | 
				
			||||||
 | 
					                    keyfetch.jwt.verify(jwt).then(e("should have an issuer")).catch(throwIfNotExpected),
 | 
				
			||||||
 | 
					                    keyfetch.jwt
 | 
				
			||||||
 | 
					                        .verify(jwt, {
 | 
				
			||||||
 | 
					                            jwk: pair.public,
 | 
				
			||||||
 | 
					                            issuers: ["https://example.com/"]
 | 
				
			||||||
 | 
					                        })
 | 
				
			||||||
 | 
					                        .then(e("fail when issuer specified and doesn't exist"))
 | 
				
			||||||
 | 
					                        .catch(throwIfNotExpected)
 | 
				
			||||||
 | 
					                ]);
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					    ])
 | 
				
			||||||
 | 
					        .then(function () {
 | 
				
			||||||
 | 
					            console.log("JWT PASSES");
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
      , keyfetch.jwt.verify(keyfetch.jwt.decode(jwt), { jwk: pair.public }).then(function (verified) {
 | 
					        .catch(function (err) {
 | 
				
			||||||
          if (!(verified.claims && verified.claims.exp)) {
 | 
					            console.error("NONE SHALL PASS!");
 | 
				
			||||||
            throw new Error("malformed decoded token");
 | 
					            console.error(err);
 | 
				
			||||||
          }
 | 
					            process.exit(1);
 | 
				
			||||||
        })
 | 
					        });
 | 
				
			||||||
      , keyfetch.jwt.verify(jwt, { jwks: [pair.public] })
 | 
					 | 
				
			||||||
      , keyfetch.jwt.verify(jwt, { jwk: pair.public, issuers: ['https://example.com/'] })
 | 
					 | 
				
			||||||
      , keyfetch.jwt.verify(jwt, { jwk: pair.public, issuers: ['https://example.com'] })
 | 
					 | 
				
			||||||
      , keyfetch.jwt.verify(jwt, { jwk: pair.public, issuers: ['example.com'] })
 | 
					 | 
				
			||||||
      , keyfetch.jwt.verify(jwt, { jwk: pair.public, issuers: ['example.com/'] })
 | 
					 | 
				
			||||||
      , keyfetch.jwt.verify(jwt, { jwk: pair.public, issuers: ['*'] })
 | 
					 | 
				
			||||||
      , keyfetch.jwt.verify(jwt, { jwk: pair.public, issuers: ['http://example.com'] })
 | 
					 | 
				
			||||||
          .then(e("bad scheme")).catch(throwIfNotExpected)
 | 
					 | 
				
			||||||
      , keyfetch.jwt.verify(jwt, { jwk: pair.public, issuers: ['https://www.example.com'] })
 | 
					 | 
				
			||||||
          .then(e("bad prefix")).catch(throwIfNotExpected)
 | 
					 | 
				
			||||||
      , keyfetch.jwt.verify(jwt, { jwk: pair.public, issuers: ['https://wexample.com'] })
 | 
					 | 
				
			||||||
          .then(e("bad sld")).catch(throwIfNotExpected)
 | 
					 | 
				
			||||||
      , keyfetch.jwt.verify(jwt, { jwk: pair.public, issuers: ['https://example.comm'] })
 | 
					 | 
				
			||||||
          .then(e("bad tld")).catch(throwIfNotExpected)
 | 
					 | 
				
			||||||
      , keyfetch.jwt.verify(jwt, { jwk: pair.public, claims: { iss: 'https://example.com/' } })
 | 
					 | 
				
			||||||
      , keyfetch.jwt.verify(jwt, { jwk: pair.public, claims: { iss: 'https://example.com' } })
 | 
					 | 
				
			||||||
          .then(e("inexact claim")).catch(throwIfNotExpected)
 | 
					 | 
				
			||||||
      ]);
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
  , keypairs.signJwt({
 | 
					 | 
				
			||||||
      jwk: pair.private, iss: false, sub: 'mikey', exp: '1h'
 | 
					 | 
				
			||||||
    }).then(function (jwt) {
 | 
					 | 
				
			||||||
      return Promise.all([
 | 
					 | 
				
			||||||
        keyfetch.jwt.verify(jwt, { jwk: pair.public })
 | 
					 | 
				
			||||||
      , keyfetch.jwt.verify(jwt)
 | 
					 | 
				
			||||||
          .then(e("should have an issuer")).catch(throwIfNotExpected)
 | 
					 | 
				
			||||||
      , keyfetch.jwt.verify(jwt, { jwk: pair.public, issuers: ['https://example.com/'] })
 | 
					 | 
				
			||||||
          .then(e("fail when issuer specified and doesn't exist")).catch(throwIfNotExpected)
 | 
					 | 
				
			||||||
      ]);
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
  ]).then(function () {
 | 
					 | 
				
			||||||
    console.log("JWT PASSES");
 | 
					 | 
				
			||||||
  }).catch(function (err) {
 | 
					 | 
				
			||||||
    console.error("NONE SHALL PASS!");
 | 
					 | 
				
			||||||
    console.error(err);
 | 
					 | 
				
			||||||
    process.exit(1);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
var jwt = '...';
 | 
					var jwt = '...';
 | 
				
			||||||
@ -84,8 +155,10 @@ keyfetch.verify({ jwt: jwt }).catch(function (err) {
 | 
				
			|||||||
*/
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function e(msg) {
 | 
					function e(msg) {
 | 
				
			||||||
  return new Error("ETEST: " + msg);
 | 
					    return new Error("ETEST: " + msg);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
function throwIfNotExpected(err) {
 | 
					function throwIfNotExpected(err) {
 | 
				
			||||||
  if ("ETEST" === err.message.slice(0, 5)) { throw err; }
 | 
					    if ("ETEST" === err.message.slice(0, 5)) {
 | 
				
			||||||
 | 
					        throw err;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										625
									
								
								keyfetch.js
									
									
									
									
									
								
							
							
						
						
									
										625
									
								
								keyfetch.js
									
									
									
									
									
								
							@ -1,160 +1,163 @@
 | 
				
			|||||||
'use strict';
 | 
					"use strict";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var keyfetch = module.exports;
 | 
					var keyfetch = module.exports;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var promisify = require('util').promisify;
 | 
					var request = require("@root/request").defaults({
 | 
				
			||||||
var requestAsync = promisify(require('@coolaj86/urequest'));
 | 
					    userAgent: "keyfetch/v2.1.0"
 | 
				
			||||||
var Rasha = require('rasha');
 | 
					});
 | 
				
			||||||
var Eckles = require('eckles');
 | 
					var Rasha = require("rasha");
 | 
				
			||||||
 | 
					var Eckles = require("eckles");
 | 
				
			||||||
var mincache = 1 * 60 * 60;
 | 
					var mincache = 1 * 60 * 60;
 | 
				
			||||||
var maxcache = 3 * 24 * 60 * 60;
 | 
					var maxcache = 3 * 24 * 60 * 60;
 | 
				
			||||||
var staletime = 15 * 60;
 | 
					var staletime = 15 * 60;
 | 
				
			||||||
var keyCache = {};
 | 
					var keyCache = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*global Promise*/
 | 
					var Errors = require("./lib/errors.js");
 | 
				
			||||||
function checkMinDefaultMax(opts, key, n, d, x) {
 | 
					
 | 
				
			||||||
  var i = opts[key];
 | 
					async function requestAsync(req) {
 | 
				
			||||||
  if (!i && 0 !== i) { return d; }
 | 
					    var resp = await request(req).catch(Errors.BAD_GATEWAY);
 | 
				
			||||||
  if (i >= n && i >= x) {
 | 
					
 | 
				
			||||||
    return parseInt(i, 10);
 | 
					    // differentiate potentially temporary server errors from 404
 | 
				
			||||||
  } else {
 | 
					    if (!resp.ok && (resp.statusCode >= 500 || resp.statusCode < 200)) {
 | 
				
			||||||
    throw new Error("opts." + key + " should be at least " + n + " and at most " + x + ", not " + i);
 | 
					        throw Errors.BAD_GATEWAY({ response: resp });
 | 
				
			||||||
  }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return resp;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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 Errors.DEVELOPER_ERROR("opts." + key + " should be at least " + n + " and at most " + x + ", not " + i);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					keyfetch._errors = Errors;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
keyfetch._clear = function () {
 | 
					keyfetch._clear = function () {
 | 
				
			||||||
  keyCache = {};
 | 
					    keyCache = {};
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
keyfetch.init = function (opts) {
 | 
					keyfetch.init = function (opts) {
 | 
				
			||||||
  mincache = checkMinDefaultMax(opts, 'mincache',
 | 
					    mincache = checkMinDefaultMax(opts, "mincache", 1 * 60, mincache, 31 * 24 * 60 * 60);
 | 
				
			||||||
    1 * 60,
 | 
					    maxcache = checkMinDefaultMax(opts, "maxcache", 1 * 60 * 60, maxcache, 31 * 24 * 60 * 60);
 | 
				
			||||||
    mincache,
 | 
					    staletime = checkMinDefaultMax(opts, "staletime", 1 * 60, staletime, 31 * 24 * 60 * 60);
 | 
				
			||||||
    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) {
 | 
					keyfetch._oidc = async function (iss) {
 | 
				
			||||||
  return Promise.resolve().then(function () {
 | 
					    var url = normalizeIss(iss) + "/.well-known/openid-configuration";
 | 
				
			||||||
    return requestAsync({
 | 
					    var resp = await requestAsync({
 | 
				
			||||||
      url: normalizeIss(iss) + '/.well-known/openid-configuration'
 | 
					        url: url,
 | 
				
			||||||
    , json: true
 | 
					        json: true
 | 
				
			||||||
    }).then(function (resp) {
 | 
					 | 
				
			||||||
      var oidcConf = resp.body;
 | 
					 | 
				
			||||||
      if (!oidcConf.jwks_uri) {
 | 
					 | 
				
			||||||
        throw new Error("Failed to retrieve openid configuration");
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      return oidcConf;
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  });
 | 
					
 | 
				
			||||||
 | 
					    var oidcConf = resp.body;
 | 
				
			||||||
 | 
					    if (!oidcConf.jwks_uri) {
 | 
				
			||||||
 | 
					        throw Errors.NO_JWKS_URI(url);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return oidcConf;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
keyfetch._wellKnownJwks = function (iss) {
 | 
					keyfetch._wellKnownJwks = async function (iss) {
 | 
				
			||||||
  return Promise.resolve().then(function () {
 | 
					    return keyfetch._jwks(normalizeIss(iss) + "/.well-known/jwks.json");
 | 
				
			||||||
    return keyfetch._jwks(normalizeIss(iss) + '/.well-known/jwks.json');
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
keyfetch._jwks = function (iss) {
 | 
					keyfetch._jwks = async function (iss) {
 | 
				
			||||||
  return requestAsync({ url: iss, json: true }).then(function (resp) {
 | 
					    var resp = await requestAsync({ url: iss, json: true });
 | 
				
			||||||
    return Promise.all(resp.body.keys.map(function (jwk) {
 | 
					
 | 
				
			||||||
      // EC keys have an x values, whereas RSA keys do not
 | 
					    return Promise.all(
 | 
				
			||||||
      var Keypairs = jwk.x ? Eckles : Rasha;
 | 
					        resp.body.keys.map(async function (jwk) {
 | 
				
			||||||
      return Keypairs.thumbprint({ jwk: jwk }).then(function (thumbprint) {
 | 
					            // EC keys have an x values, whereas RSA keys do not
 | 
				
			||||||
        return Keypairs.export({ jwk: jwk }).then(function (pem) {
 | 
					            var Keypairs = jwk.x ? Eckles : Rasha;
 | 
				
			||||||
          var cacheable = {
 | 
					            var thumbprint = await Keypairs.thumbprint({ jwk: jwk });
 | 
				
			||||||
            jwk: jwk
 | 
					            var pem = await Keypairs.export({ jwk: jwk });
 | 
				
			||||||
          , thumbprint: thumbprint
 | 
					            var cacheable = {
 | 
				
			||||||
          , pem: pem
 | 
					                jwk: jwk,
 | 
				
			||||||
          };
 | 
					                thumbprint: thumbprint,
 | 
				
			||||||
          return cacheable;
 | 
					                pem: pem
 | 
				
			||||||
        });
 | 
					            };
 | 
				
			||||||
      });
 | 
					            return cacheable;
 | 
				
			||||||
    }));
 | 
					        })
 | 
				
			||||||
  });
 | 
					    );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
keyfetch.jwks = function (jwkUrl) {
 | 
					keyfetch.jwks = async 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
 | 
					    // TODO DRY up a bit
 | 
				
			||||||
    return keyfetch._jwks(oidcConf.jwks_uri).then(function (results) {
 | 
					    var results = await keyfetch._jwks(jwkUrl);
 | 
				
			||||||
      return Promise.all(results.map(function (result) {
 | 
					    await Promise.all(
 | 
				
			||||||
        return keyfetch._setCache(result.jwk.iss || iss, result);
 | 
					        results.map(async function (result) {
 | 
				
			||||||
      })).then(function () {
 | 
					            return keyfetch._setCache(result.jwk.iss || jwkUrl, result);
 | 
				
			||||||
        // result -> hit (keep original externally immutable)
 | 
					        })
 | 
				
			||||||
        return JSON.parse(JSON.stringify(results));
 | 
					    );
 | 
				
			||||||
      });
 | 
					    // cacheable -> hit (keep original externally immutable)
 | 
				
			||||||
    });
 | 
					    return JSON.parse(JSON.stringify(results));
 | 
				
			||||||
  });
 | 
					};
 | 
				
			||||||
 | 
					keyfetch.wellKnownJwks = async function (iss) {
 | 
				
			||||||
 | 
					    // TODO DRY up a bit
 | 
				
			||||||
 | 
					    var results = await keyfetch._wellKnownJwks(iss);
 | 
				
			||||||
 | 
					    await Promise.all(
 | 
				
			||||||
 | 
					        results.map(async function (result) {
 | 
				
			||||||
 | 
					            return keyfetch._setCache(result.jwk.iss || iss, result);
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    // result -> hit (keep original externally immutable)
 | 
				
			||||||
 | 
					    return JSON.parse(JSON.stringify(results));
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					keyfetch.oidcJwks = async function (iss) {
 | 
				
			||||||
 | 
					    var oidcConf = await keyfetch._oidc(iss);
 | 
				
			||||||
 | 
					    // TODO DRY up a bit
 | 
				
			||||||
 | 
					    var results = await keyfetch._jwks(oidcConf.jwks_uri);
 | 
				
			||||||
 | 
					    await Promise.all(
 | 
				
			||||||
 | 
					        results.map(async function (result) {
 | 
				
			||||||
 | 
					            return keyfetch._setCache(result.jwk.iss || iss, result);
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    // result -> hit (keep original externally immutable)
 | 
				
			||||||
 | 
					    return JSON.parse(JSON.stringify(results));
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
function checkId(id) {
 | 
					function checkId(id) {
 | 
				
			||||||
  return function (results) {
 | 
					    return function (results) {
 | 
				
			||||||
    var result = results.filter(function (result) {
 | 
					        var result = results.filter(function (result) {
 | 
				
			||||||
      // we already checked iss above
 | 
					            // we already checked iss above
 | 
				
			||||||
      return result.jwk.kid === id || result.thumbprint === id;
 | 
					            return result.jwk.kid === id || result.thumbprint === id;
 | 
				
			||||||
    })[0];
 | 
					        })[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!result) {
 | 
					        if (!result) {
 | 
				
			||||||
      throw new Error("No JWK found by kid or thumbprint '" + id + "'");
 | 
					            throw Errors.JWK_NOT_FOUND(id);
 | 
				
			||||||
    }
 | 
					        }
 | 
				
			||||||
    return result;
 | 
					        return result;
 | 
				
			||||||
  };
 | 
					    };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
keyfetch.oidcJwk = function (id, iss) {
 | 
					keyfetch.oidcJwk = async function (id, iss) {
 | 
				
			||||||
  return keyfetch._checkCache(id, iss).then(function (hit) {
 | 
					    var hit = await keyfetch._checkCache(id, iss);
 | 
				
			||||||
    if (hit) { return hit; }
 | 
					    if (hit) {
 | 
				
			||||||
 | 
					        return hit;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    return keyfetch.oidcJwks(iss).then(checkId(id));
 | 
					    return keyfetch.oidcJwks(iss).then(checkId(id));
 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
keyfetch.wellKnownJwk = function (id, iss) {
 | 
					keyfetch.wellKnownJwk = async function (id, iss) {
 | 
				
			||||||
  return keyfetch._checkCache(id, iss).then(function (hit) {
 | 
					    var hit = await keyfetch._checkCache(id, iss);
 | 
				
			||||||
    if (hit) { return hit; }
 | 
					    if (hit) {
 | 
				
			||||||
 | 
					        return hit;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    return keyfetch.wellKnownJwks(iss).then(checkId(id));
 | 
					    return keyfetch.wellKnownJwks(iss).then(checkId(id));
 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
keyfetch.jwk = function (id, jwksUrl) {
 | 
					keyfetch.jwk = async function (id, jwksUrl) {
 | 
				
			||||||
  return keyfetch._checkCache(id, jwksUrl).then(function (hit) {
 | 
					    var hit = await keyfetch._checkCache(id, jwksUrl);
 | 
				
			||||||
    if (hit) { return hit; }
 | 
					    if (hit) {
 | 
				
			||||||
 | 
					        return hit;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    return keyfetch.jwks(jwksUrl).then(checkId(id));
 | 
					    return keyfetch.jwks(jwksUrl).then(checkId(id));
 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
keyfetch._checkCache = function (id, iss) {
 | 
					keyfetch._checkCache = async function (id, iss) {
 | 
				
			||||||
  return Promise.resolve().then(function () {
 | 
					 | 
				
			||||||
    // We cache by thumbprint and (kid + '@' + iss),
 | 
					    // We cache by thumbprint and (kid + '@' + iss),
 | 
				
			||||||
    // so it's safe to check without appending the issuer
 | 
					    // so it's safe to check without appending the issuer
 | 
				
			||||||
    var hit = keyCache[id];
 | 
					    var hit = keyCache[id];
 | 
				
			||||||
    if (!hit) {
 | 
					    if (!hit) {
 | 
				
			||||||
      hit = keyCache[id + '@' + normalizeIss(iss)];
 | 
					        hit = keyCache[id + "@" + normalizeIss(iss)];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (!hit) {
 | 
					    if (!hit) {
 | 
				
			||||||
      return null;
 | 
					        return null;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var now = Math.round(Date.now() / 1000);
 | 
					    var now = Math.round(Date.now() / 1000);
 | 
				
			||||||
@ -162,232 +165,282 @@ keyfetch._checkCache = function (id, iss) {
 | 
				
			|||||||
    // not guarding number checks since we know that we
 | 
					    // not guarding number checks since we know that we
 | 
				
			||||||
    // set 'now' and 'expiresAt' correctly elsewhere
 | 
					    // set 'now' and 'expiresAt' correctly elsewhere
 | 
				
			||||||
    if (left > staletime) {
 | 
					    if (left > staletime) {
 | 
				
			||||||
      return JSON.parse(JSON.stringify(hit));
 | 
					        return JSON.parse(JSON.stringify(hit));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (left > 0) {
 | 
					    if (left > 0) {
 | 
				
			||||||
      return JSON.parse(JSON.stringify(hit));
 | 
					        return JSON.parse(JSON.stringify(hit));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return null;
 | 
					    return null;
 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
keyfetch._setCache = function (iss, cacheable) {
 | 
					keyfetch._setCache = function (iss, cacheable) {
 | 
				
			||||||
  // force into a number
 | 
					    // force into a number
 | 
				
			||||||
  var expiresAt = parseInt(cacheable.jwk.exp, 10) || 0;
 | 
					    var expiresAt = parseInt(cacheable.jwk.exp, 10) || 0;
 | 
				
			||||||
  var now = Date.now() / 1000;
 | 
					    var now = Date.now() / 1000;
 | 
				
			||||||
  var left = expiresAt - now;
 | 
					    var left = expiresAt - now;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // TODO maybe log out when any of these non-ideal cases happen?
 | 
					    // TODO maybe log out when any of these non-ideal cases happen?
 | 
				
			||||||
  if (!left) {
 | 
					    if (!left) {
 | 
				
			||||||
    expiresAt = now + maxcache;
 | 
					        expiresAt = now + maxcache;
 | 
				
			||||||
  } else if (left < mincache) {
 | 
					    } else if (left < mincache) {
 | 
				
			||||||
    expiresAt = now + mincache;
 | 
					        expiresAt = now + mincache;
 | 
				
			||||||
  } else if (left > maxcache) {
 | 
					    } else if (left > maxcache) {
 | 
				
			||||||
    expiresAt = now + maxcache;
 | 
					        expiresAt = now + maxcache;
 | 
				
			||||||
  }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // cacheable = { jwk, thumprint, pem }
 | 
					    // cacheable = { jwk, thumprint, pem }
 | 
				
			||||||
  cacheable.createdAt = now;
 | 
					    cacheable.createdAt = now;
 | 
				
			||||||
  cacheable.expiresAt = expiresAt;
 | 
					    cacheable.expiresAt = expiresAt;
 | 
				
			||||||
  keyCache[cacheable.thumbprint] = cacheable;
 | 
					    keyCache[cacheable.thumbprint] = cacheable;
 | 
				
			||||||
  keyCache[cacheable.jwk.kid + '@' + normalizeIss(iss)] = cacheable;
 | 
					    keyCache[cacheable.jwk.kid + "@" + normalizeIss(iss)] = cacheable;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function normalizeIss(iss) {
 | 
					function normalizeIss(iss) {
 | 
				
			||||||
  // We definitely don't want false negatives stemming
 | 
					    if (!iss) {
 | 
				
			||||||
  // from https://example.com vs https://example.com/
 | 
					        throw Errors.NO_ISSUER();
 | 
				
			||||||
  // 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
 | 
					    // We definitely don't want false negatives stemming
 | 
				
			||||||
    throw new Error("'" + iss + "' is NOT secure. Set env 'KEYFETCH_ALLOW_INSECURE_HTTP=true' to allow for testing.");
 | 
					    // from https://example.com vs https://example.com/
 | 
				
			||||||
  }
 | 
					    // We also don't want to allow insecure issuers
 | 
				
			||||||
  return iss.replace(/\/$/, '');
 | 
					    if (/^http:/.test(iss) && !process.env.KEYFETCH_ALLOW_INSECURE_HTTP) {
 | 
				
			||||||
 | 
					        // note, we wrap some things in promises just so we can throw here
 | 
				
			||||||
 | 
					        throw Errors.INSECURE_ISSUER(iss);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return iss.replace(/\/$/, "");
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
keyfetch.jwt = {};
 | 
					keyfetch.jwt = {};
 | 
				
			||||||
keyfetch.jwt.decode = function (jwt) {
 | 
					keyfetch.jwt.decode = function (jwt) {
 | 
				
			||||||
  var parts = jwt.split('.');
 | 
					    try {
 | 
				
			||||||
  // JWS
 | 
					        var parts = jwt.split(".");
 | 
				
			||||||
  var obj = {
 | 
					        // JWS
 | 
				
			||||||
    protected: parts[0]
 | 
					        var obj = { protected: parts[0], payload: parts[1], signature: parts[2] };
 | 
				
			||||||
  , payload: parts[1]
 | 
					        // JWT
 | 
				
			||||||
  , signature: parts[2]
 | 
					        obj.header = JSON.parse(Buffer.from(obj.protected, "base64"));
 | 
				
			||||||
  };
 | 
					        obj.claims = JSON.parse(Buffer.from(obj.payload, "base64"));
 | 
				
			||||||
  // JWT
 | 
					        return obj;
 | 
				
			||||||
  obj.header = JSON.parse(Buffer.from(obj.protected, 'base64'));
 | 
					    } catch (e) {
 | 
				
			||||||
  obj.claims = JSON.parse(Buffer.from(obj.payload, 'base64'));
 | 
					        var err = Errors.PARSE_ERROR(jwt);
 | 
				
			||||||
  return obj;
 | 
					        err.details = e.message;
 | 
				
			||||||
 | 
					        throw err;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
keyfetch.jwt.verify = function (jwt, opts) {
 | 
					keyfetch.jwt.verify = async function (jwt, opts) {
 | 
				
			||||||
  if (!opts) { opts = {}; }
 | 
					    if (!opts) {
 | 
				
			||||||
  return Promise.resolve().then(function () {
 | 
					        opts = {};
 | 
				
			||||||
    var decoded;
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var jws;
 | 
				
			||||||
    var exp;
 | 
					    var exp;
 | 
				
			||||||
    var nbf;
 | 
					    var nbf;
 | 
				
			||||||
    var active;
 | 
					    var active;
 | 
				
			||||||
    var issuers = opts.issuers || ['*'];
 | 
					    var now;
 | 
				
			||||||
 | 
					    var then;
 | 
				
			||||||
 | 
					    var issuers = opts.issuers || [];
 | 
				
			||||||
 | 
					    if (opts.iss) {
 | 
				
			||||||
 | 
					        issuers.push(opts.iss);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (opts.claims && opts.claims.iss) {
 | 
				
			||||||
 | 
					        issuers.push(opts.claims.iss);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (!issuers.length) {
 | 
				
			||||||
 | 
					        if (!(opts.jwk || opts.jwks)) {
 | 
				
			||||||
 | 
					            throw Errors.DEVELOPER_ERROR(
 | 
				
			||||||
 | 
					                "[keyfetch.js] Security Error: Neither of opts.issuers nor opts.iss were provided. If you would like to bypass issuer verification (i.e. for federated authn) you must explicitly set opts.issuers = ['*']. Otherwise set a value such as https://accounts.google.com/"
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    var claims = opts.claims || {};
 | 
					    var claims = opts.claims || {};
 | 
				
			||||||
    if (!jwt || 'string' === typeof jwt) {
 | 
					    if (!jwt || "string" === typeof jwt) {
 | 
				
			||||||
      try { decoded = keyfetch.jwt.decode(jwt); }
 | 
					        jws = keyfetch.jwt.decode(jwt);
 | 
				
			||||||
      catch (e) { throw new Error("could not parse jwt: '" + jwt + "'"); }
 | 
					 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      decoded = jwt;
 | 
					        jws = jwt;
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    exp = decoded.claims.exp;
 | 
					 | 
				
			||||||
    nbf = decoded.claims.nbf;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!issuers.some(isTrustedIssuer(decoded.claims.iss))) {
 | 
					 | 
				
			||||||
      throw new Error("token was issued by an untrusted issuer: '" + decoded.claims.iss + "'");
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    // TODO verify claims also?
 | 
					 | 
				
			||||||
    if (!Object.keys(claims).every(function (key) {
 | 
					 | 
				
			||||||
      if (claims[key] === decoded.claims[key]) {
 | 
					 | 
				
			||||||
        return true;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    })) {
 | 
					 | 
				
			||||||
      throw new Error("token did not match on one or more authorization claims: '" + Object.keys(claims) + "'");
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    active = ((opts.exp || 0) + parseInt(exp, 10) - (Date.now()/1000) > 0);
 | 
					    if (!jws.claims.iss || !issuers.some(isTrustedIssuer(jws.claims.iss))) {
 | 
				
			||||||
    if (!active) {
 | 
					        if (!(opts.jwk || opts.jwks)) {
 | 
				
			||||||
      // expiration was on the token or, if not, such a token is not allowed
 | 
					            throw Errors.UNKNOWN_ISSUER(jws.claims.iss || "");
 | 
				
			||||||
      if (exp || false !== opts.exp) {
 | 
					        }
 | 
				
			||||||
        throw new Error("token's 'exp' has passed or could not parsed: '" + exp + "'");
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    // Note claims.iss validates more strictly than opts.issuers (requires exact match)
 | 
				
			||||||
 | 
					    var failedClaims = Object.keys(claims)
 | 
				
			||||||
 | 
					        .filter(function (key) {
 | 
				
			||||||
 | 
					            if (claims[key] !== jws.claims[key]) {
 | 
				
			||||||
 | 
					                return true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        .map(function (key) {
 | 
				
			||||||
 | 
					            return "jwt.claims." + key + " = " + JSON.stringify(jws.claims[key]);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    if (failedClaims.length) {
 | 
				
			||||||
 | 
					        throw Errors.FAILED_CLAIMS(failedClaims, Object.keys(claims));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    exp = jws.claims.exp;
 | 
				
			||||||
 | 
					    if (exp && false !== opts.exp) {
 | 
				
			||||||
 | 
					        now = Date.now();
 | 
				
			||||||
 | 
					        // TODO document that opts.exp can be used as leeway? Or introduce opts.leeway?
 | 
				
			||||||
 | 
					        // fair, but not necessary
 | 
				
			||||||
 | 
					        exp = parseInt(exp, 10);
 | 
				
			||||||
 | 
					        if (isNaN(exp)) {
 | 
				
			||||||
 | 
					            throw Errors.MALFORMED_EXP(JSON.stringify(jws.claims.exp));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        then = (opts.exp || 0) + parseInt(exp, 10);
 | 
				
			||||||
 | 
					        active = then - now / 1000 > 0;
 | 
				
			||||||
 | 
					        // expiration was on the token or, if not, such a token is not allowed
 | 
				
			||||||
 | 
					        if (!active) {
 | 
				
			||||||
 | 
					            throw Errors.EXPIRED(exp);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    nbf = jws.claims.nbf;
 | 
				
			||||||
    if (nbf) {
 | 
					    if (nbf) {
 | 
				
			||||||
      active = (parseInt(nbf, 10) - (Date.now()/1000) <= 0);
 | 
					        active = parseInt(nbf, 10) - Date.now() / 1000 <= 0;
 | 
				
			||||||
      if (!active) {
 | 
					        if (!active) {
 | 
				
			||||||
        throw new Error("token's 'nbf' has not been reached or could not parsed: '" + nbf + "'");
 | 
					            throw Errors.INACTIVE(nbf);
 | 
				
			||||||
      }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (opts.jwks || opts.jwk) {
 | 
					    if (opts.jwks || opts.jwk) {
 | 
				
			||||||
      return overrideLookup(opts.jwks || [opts.jwk]);
 | 
					        return overrideLookup(opts.jwks || [opts.jwk]);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var kid = decoded.header.kid;
 | 
					    var kid = jws.header.kid;
 | 
				
			||||||
    var iss;
 | 
					    var iss;
 | 
				
			||||||
    var fetcher;
 | 
					    var fetcher;
 | 
				
			||||||
    var fetchOne;
 | 
					    var fetchOne;
 | 
				
			||||||
    if (!opts.strategy || 'oidc' === opts.strategy) {
 | 
					    if (!opts.strategy || "oidc" === opts.strategy) {
 | 
				
			||||||
      iss = decoded.claims.iss;
 | 
					        iss = jws.claims.iss;
 | 
				
			||||||
      fetcher = keyfetch.oidcJwks;
 | 
					        fetcher = keyfetch.oidcJwks;
 | 
				
			||||||
      fetchOne = keyfetch.oidcJwk;
 | 
					        fetchOne = keyfetch.oidcJwk;
 | 
				
			||||||
    } else if ('auth0' === opts.strategy || 'well-known' === opts.strategy) {
 | 
					    } else if ("auth0" === opts.strategy || "well-known" === opts.strategy) {
 | 
				
			||||||
      iss = decoded.claims.iss;
 | 
					        iss = jws.claims.iss;
 | 
				
			||||||
      fetcher = keyfetch.wellKnownJwks;
 | 
					        fetcher = keyfetch.wellKnownJwks;
 | 
				
			||||||
      fetchOne = keyfetch.wellKnownJwk;
 | 
					        fetchOne = keyfetch.wellKnownJwk;
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      iss = opts.strategy;
 | 
					        iss = opts.strategy;
 | 
				
			||||||
      fetcher = keyfetch.jwks;
 | 
					        fetcher = keyfetch.jwks;
 | 
				
			||||||
      fetchOne = keyfetch.jwk;
 | 
					        fetchOne = keyfetch.jwk;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var p;
 | 
					 | 
				
			||||||
    if (kid) {
 | 
					    if (kid) {
 | 
				
			||||||
      p = fetchOne(kid, iss).then(verifyOne); //.catch(fetchAny);
 | 
					        return fetchOne(kid, iss).then(verifyOne); //.catch(fetchAny);
 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      p = fetcher(iss).then(verifyAny);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return p;
 | 
					    return fetcher(iss).then(verifyAny);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function verifyOne(hit) {
 | 
					    function verifyOne(hit) {
 | 
				
			||||||
      if (true === keyfetch.jws.verify(decoded, hit)) {
 | 
					        if (true === keyfetch.jws.verify(jws, hit)) {
 | 
				
			||||||
        return decoded;
 | 
					            return jws;
 | 
				
			||||||
      }
 | 
					        }
 | 
				
			||||||
      throw new Error('token signature verification was unsuccessful');
 | 
					        throw Errors.BAD_SIGNATURE(jws.protected + "." + jws.payload + "." + jws.signature);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function verifyAny(hits) {
 | 
					    function verifyAny(hits) {
 | 
				
			||||||
      if (hits.some(function (hit) {
 | 
					        if (
 | 
				
			||||||
        if (kid) {
 | 
					            hits.some(function (hit) {
 | 
				
			||||||
          if (kid !== hit.jwk.kid && kid !== hit.thumbprint) { return; }
 | 
					                if (kid) {
 | 
				
			||||||
          if (true === keyfetch.jws.verify(decoded, hit)) { return true; }
 | 
					                    if (kid !== hit.jwk.kid && kid !== hit.thumbprint) {
 | 
				
			||||||
          throw new Error('token signature verification was unsuccessful');
 | 
					                        return;
 | 
				
			||||||
        } else {
 | 
					                    }
 | 
				
			||||||
          if (true === keyfetch.jws.verify(decoded, hit)) { return true; }
 | 
					                    if (true === keyfetch.jws.verify(jws, hit)) {
 | 
				
			||||||
 | 
					                        return true;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    throw Errors.BAD_SIGNATURE();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if (true === keyfetch.jws.verify(jws, hit)) {
 | 
				
			||||||
 | 
					                    return true;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
 | 
					            return jws;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      })) {
 | 
					        throw Errors.JWK_NOT_FOUND_OLD(kid);
 | 
				
			||||||
        return decoded;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      throw new Error("Retrieved a list of keys, but none of them matched the 'kid' (key id) of the token.");
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function overrideLookup(jwks) {
 | 
					    function overrideLookup(jwks) {
 | 
				
			||||||
      return Promise.all(jwks.map(function (jwk) {
 | 
					        return Promise.all(
 | 
				
			||||||
        var Keypairs = jwk.x ? Eckles : Rasha;
 | 
					            jwks.map(async function (jwk) {
 | 
				
			||||||
        return Keypairs.export({ jwk: jwk }).then(function (pem) {
 | 
					                var Keypairs = jwk.x ? Eckles : Rasha;
 | 
				
			||||||
          return Keypairs.thumbprint({ jwk: jwk }).then(function (thumb) {
 | 
					                var pem = await Keypairs.export({ jwk: jwk });
 | 
				
			||||||
            return { jwk: jwk, pem: pem, thumbprint: thumb };
 | 
					                var thumb = await Keypairs.thumbprint({ jwk: jwk });
 | 
				
			||||||
          });
 | 
					                return { jwk: jwk, pem: pem, thumbprint: thumb };
 | 
				
			||||||
        });
 | 
					            })
 | 
				
			||||||
      })).then(verifyAny);
 | 
					        ).then(verifyAny);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
keyfetch.jws = {};
 | 
					keyfetch.jws = {};
 | 
				
			||||||
keyfetch.jws.verify = function (jws, pub) {
 | 
					keyfetch.jws.verify = function (jws, pub) {
 | 
				
			||||||
  var alg = 'SHA' + jws.header.alg.replace(/[^\d]+/i, '');
 | 
					    var alg = "SHA" + jws.header.alg.replace(/[^\d]+/i, "");
 | 
				
			||||||
  var sig = ecdsaAsn1SigToJwtSig(jws.header, jws.signature);
 | 
					    var sig = ecdsaJoseSigToAsn1Sig(jws.header, jws.signature);
 | 
				
			||||||
  return require('crypto')
 | 
					    return require("crypto")
 | 
				
			||||||
    .createVerify(alg)
 | 
					        .createVerify(alg)
 | 
				
			||||||
    .update(jws.protected + '.' + jws.payload)
 | 
					        .update(jws.protected + "." + jws.payload)
 | 
				
			||||||
    .verify(pub.pem, sig, 'base64')
 | 
					        .verify(pub.pem, sig, "base64");
 | 
				
			||||||
  ;
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// old, gotta make sure nothing else uses this
 | 
					// old, gotta make sure nothing else uses this
 | 
				
			||||||
keyfetch._decode = function (jwt) {
 | 
					keyfetch._decode = function (jwt) {
 | 
				
			||||||
  var obj = keyfetch.jwt.decode(jwt);
 | 
					    var obj = keyfetch.jwt.decode(jwt);
 | 
				
			||||||
  return { header: obj.header, payload: obj.claims, signature: obj.signature };
 | 
					    return { header: obj.header, payload: obj.claims, signature: obj.signature };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
keyfetch.verify = function (opts) {
 | 
					keyfetch.verify = async function (opts) {
 | 
				
			||||||
  var jwt = opts.jwt;
 | 
					    var jwt = opts.jwt;
 | 
				
			||||||
  return keyfetch.jwt.verify(jwt, opts).then(function (obj) {
 | 
					    var obj = await keyfetch.jwt.verify(jwt, opts);
 | 
				
			||||||
    return { header: obj.header, payload: obj.claims, signature: obj.signature };
 | 
					    return { header: obj.header, payload: obj.claims, signature: obj.signature };
 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function ecdsaAsn1SigToJwtSig(header, b64sig) {
 | 
					function ecdsaJoseSigToAsn1Sig(header, b64sig) {
 | 
				
			||||||
  // ECDSA JWT signatures differ from "normal" ECDSA signatures
 | 
					    // ECDSA JWT signatures differ from "normal" ECDSA signatures
 | 
				
			||||||
  // https://tools.ietf.org/html/rfc7518#section-3.4
 | 
					    // https://tools.ietf.org/html/rfc7518#section-3.4
 | 
				
			||||||
  if (!/^ES/i.test(header.alg)) { return b64sig; }
 | 
					    if (!/^ES/i.test(header.alg)) {
 | 
				
			||||||
 | 
					        return b64sig;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  var bufsig = Buffer.from(b64sig, 'base64');
 | 
					    var bufsig = Buffer.from(b64sig, "base64");
 | 
				
			||||||
  var hlen = bufsig.byteLength / 2; // should be even
 | 
					    var hlen = bufsig.byteLength / 2; // should be even
 | 
				
			||||||
  var r = bufsig.slice(0, hlen);
 | 
					    var r = bufsig.slice(0, hlen);
 | 
				
			||||||
  var s = bufsig.slice(hlen);
 | 
					    var s = bufsig.slice(hlen);
 | 
				
			||||||
  // unpad positive ints less than 32 bytes wide
 | 
					    // unpad positive ints less than 32 bytes wide
 | 
				
			||||||
  while (!r[0]) { r = r.slice(1); }
 | 
					    while (!r[0]) {
 | 
				
			||||||
  while (!s[0]) { s = s.slice(1); }
 | 
					        r = r.slice(1);
 | 
				
			||||||
  // pad (or re-pad) ambiguously non-negative BigInts to 33 bytes wide
 | 
					    }
 | 
				
			||||||
  if (0x80 & r[0]) { r = Buffer.concat([Buffer.from([0]), r]); }
 | 
					    while (!s[0]) {
 | 
				
			||||||
  if (0x80 & s[0]) { s = Buffer.concat([Buffer.from([0]), s]); }
 | 
					        s = s.slice(1);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    // pad (or re-pad) ambiguously non-negative BigInts to 33 bytes wide
 | 
				
			||||||
 | 
					    if (0x80 & r[0]) {
 | 
				
			||||||
 | 
					        r = Buffer.concat([Buffer.from([0]), r]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (0x80 & s[0]) {
 | 
				
			||||||
 | 
					        s = Buffer.concat([Buffer.from([0]), s]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  var len = 2 + r.byteLength + 2 + s.byteLength;
 | 
					    var len = 2 + r.byteLength + 2 + s.byteLength;
 | 
				
			||||||
  var head = [0x30];
 | 
					    var head = [0x30];
 | 
				
			||||||
  // hard code 0x80 + 1 because it won't be longer than
 | 
					    // hard code 0x80 + 1 because it won't be longer than
 | 
				
			||||||
  // two SHA512 plus two pad bytes (130 bytes <= 256)
 | 
					    // two SHA512 plus two pad bytes (130 bytes <= 256)
 | 
				
			||||||
  if (len >= 0x80) { head.push(0x81); }
 | 
					    if (len >= 0x80) {
 | 
				
			||||||
  head.push(len);
 | 
					        head.push(0x81);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    head.push(len);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  var buf = Buffer.concat([
 | 
					    var buf = Buffer.concat([
 | 
				
			||||||
    Buffer.from(head)
 | 
					        Buffer.from(head),
 | 
				
			||||||
  , Buffer.from([0x02, r.byteLength]), r
 | 
					        Buffer.from([0x02, r.byteLength]),
 | 
				
			||||||
  , Buffer.from([0x02, s.byteLength]), s
 | 
					        r,
 | 
				
			||||||
  ]);
 | 
					        Buffer.from([0x02, s.byteLength]),
 | 
				
			||||||
 | 
					        s
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return buf.toString('base64')
 | 
					    return buf.toString("base64").replace(/-/g, "+").replace(/_/g, "/").replace(/=/g, "");
 | 
				
			||||||
    .replace(/-/g, '+')
 | 
					 | 
				
			||||||
    .replace(/_/g, '/')
 | 
					 | 
				
			||||||
    .replace(/=/g, '')
 | 
					 | 
				
			||||||
  ;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function isTrustedIssuer(issuer) {
 | 
					function isTrustedIssuer(issuer) {
 | 
				
			||||||
  return function (trusted) {
 | 
					    return function (trusted) {
 | 
				
			||||||
    if ('*' === trusted) { return true; }
 | 
					        if ("*" === trusted) {
 | 
				
			||||||
    // TODO account for '*.example.com'
 | 
					            return true;
 | 
				
			||||||
    trusted = (/^http(s?):\/\//.test(trusted) ? trusted : ('https://' + trusted));
 | 
					        }
 | 
				
			||||||
    return issuer.replace(/\/$/, '') === trusted.replace(/\/$/, '') && trusted;
 | 
					        // TODO account for '*.example.com'
 | 
				
			||||||
  };
 | 
					        trusted = /^http(s?):\/\//.test(trusted) ? trusted : "https://" + trusted;
 | 
				
			||||||
 | 
					        return issuer.replace(/\/$/, "") === trusted.replace(/\/$/, "") && trusted;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										270
									
								
								lib/errors.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										270
									
								
								lib/errors.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,270 @@
 | 
				
			|||||||
 | 
					"use strict";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Possible User Errors
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @typedef AuthError
 | 
				
			||||||
 | 
					 * @property {string} message
 | 
				
			||||||
 | 
					 * @property {number} status
 | 
				
			||||||
 | 
					 * @property {string} code
 | 
				
			||||||
 | 
					 * @property {any} [details]
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @param {string} msg
 | 
				
			||||||
 | 
					 * @param {{
 | 
				
			||||||
 | 
					 *   status: number,
 | 
				
			||||||
 | 
					 *   code: string,
 | 
				
			||||||
 | 
					 *   details?: any,
 | 
				
			||||||
 | 
					 * }} opts
 | 
				
			||||||
 | 
					 * @returns {AuthError}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					function create(old, msg, code, status, details) {
 | 
				
			||||||
 | 
					    /** @type AuthError */
 | 
				
			||||||
 | 
					    //@ts-ignore
 | 
				
			||||||
 | 
					    let err = new Error(msg);
 | 
				
			||||||
 | 
					    err.message = msg;
 | 
				
			||||||
 | 
					    err._old_message = old;
 | 
				
			||||||
 | 
					    err.code = code;
 | 
				
			||||||
 | 
					    err.status = status;
 | 
				
			||||||
 | 
					    if (details) {
 | 
				
			||||||
 | 
					        err.details = details;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    err.source = "keyfetch";
 | 
				
			||||||
 | 
					    err.toJSON = toJSON;
 | 
				
			||||||
 | 
					    err.toString = toString;
 | 
				
			||||||
 | 
					    return err;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function toJSON() {
 | 
				
			||||||
 | 
					    /*jshint validthis:true*/
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					        message: this.message,
 | 
				
			||||||
 | 
					        status: this.status,
 | 
				
			||||||
 | 
					        code: this.code,
 | 
				
			||||||
 | 
					        details: this.details
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					function toString() {
 | 
				
			||||||
 | 
					    /*jshint validthis:true*/
 | 
				
			||||||
 | 
					    return this.stack + "\n" + JSON.stringify(this);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DEVELOPER_ERROR - a good token won't make a difference
 | 
				
			||||||
 | 
					var E_DEVELOPER = "DEVELOPER_ERROR";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// BAD_GATEWAY - there may be a temporary error fetching the public or or whatever
 | 
				
			||||||
 | 
					var E_BAD_GATEWAY = "BAD_GATEWAY";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// MALFORMED_JWT - the token could not be verified - not parsable, missing claims, etc
 | 
				
			||||||
 | 
					var E_MALFORMED = "MALFORMED_JWT";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// INVALID_JWT - the token's properties don't meet requirements - iss, claims, sig, exp
 | 
				
			||||||
 | 
					var E_INVALID = "INVALID_JWT";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = {
 | 
				
			||||||
 | 
					    //
 | 
				
			||||||
 | 
					    // DEVELOPER_ERROR (dev / server)
 | 
				
			||||||
 | 
					    //
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @param {string} msg
 | 
				
			||||||
 | 
					     * @returns {AuthError}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    DEVELOPER_ERROR: function (old, msg, details) {
 | 
				
			||||||
 | 
					        return create(old, msg || old, E_DEVELOPER, 500, details);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    BAD_GATEWAY: function (err) {
 | 
				
			||||||
 | 
					        var msg =
 | 
				
			||||||
 | 
					            "The auth token could not be verified because our server encountered a network error (or a bad gateway) when connecting to its issuing server.";
 | 
				
			||||||
 | 
					        var details = [];
 | 
				
			||||||
 | 
					        if (err.message) {
 | 
				
			||||||
 | 
					            details.push("error.message = " + err.message);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (err.response && err.response.statusCode) {
 | 
				
			||||||
 | 
					            details.push("response.statusCode = " + err.response.statusCode);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return create(msg, msg, E_BAD_GATEWAY, 502, details);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    //
 | 
				
			||||||
 | 
					    // MALFORMED_TOKEN (dev / client)
 | 
				
			||||||
 | 
					    //
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @param {string} iss
 | 
				
			||||||
 | 
					     * @returns {AuthError}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    INSECURE_ISSUER: function (iss) {
 | 
				
			||||||
 | 
					        var old =
 | 
				
			||||||
 | 
					            "'" + iss + "' is NOT secure. Set env 'KEYFETCH_ALLOW_INSECURE_HTTP=true' to allow for testing. (iss)";
 | 
				
			||||||
 | 
					        var details = [
 | 
				
			||||||
 | 
					            "jwt.claims.iss = " + JSON.stringify(iss),
 | 
				
			||||||
 | 
					            "DEBUG: Set ENV 'KEYFETCH_ALLOW_INSECURE_HTTP=true' to allow insecure issuers (for testing)."
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					        var msg =
 | 
				
			||||||
 | 
					            'The auth token could not be verified because our server could connect to its issuing server ("iss") securely.';
 | 
				
			||||||
 | 
					        return create(old, msg, E_MALFORMED, 400, details);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @param {string} jwt
 | 
				
			||||||
 | 
					     * @returns {AuthError}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    PARSE_ERROR: function (jwt) {
 | 
				
			||||||
 | 
					        var old = "could not parse jwt: '" + jwt + "'";
 | 
				
			||||||
 | 
					        var msg = "The auth token could not be verified because it is malformed.";
 | 
				
			||||||
 | 
					        var details = ["jwt = " + JSON.stringify(jwt)];
 | 
				
			||||||
 | 
					        return create(old, msg, E_MALFORMED, 400, details);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @param {string} iss
 | 
				
			||||||
 | 
					     * @returns {AuthError}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    NO_ISSUER: function (iss) {
 | 
				
			||||||
 | 
					        var old = "'iss' is not defined";
 | 
				
			||||||
 | 
					        var msg = 'The auth token could not be verified because it doesn\'t specify an issuer ("iss").';
 | 
				
			||||||
 | 
					        var details = ["jwt.claims.iss = " + JSON.stringify(iss)];
 | 
				
			||||||
 | 
					        return create(old, msg, E_MALFORMED, 400, details);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @param {string} iss
 | 
				
			||||||
 | 
					     * @returns {AuthError}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    MALFORMED_EXP: function (exp) {
 | 
				
			||||||
 | 
					        var old = "token's 'exp' has passed or could not parsed: '" + exp + "'";
 | 
				
			||||||
 | 
					        var msg = 'The auth token could not be verified because it\'s expiration date ("exp") could not be read';
 | 
				
			||||||
 | 
					        var details = ["jwt.claims.exp = " + JSON.stringify(exp)];
 | 
				
			||||||
 | 
					        return create(old, msg, E_MALFORMED, 400, details);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    //
 | 
				
			||||||
 | 
					    // INVALID_TOKEN (dev / client)
 | 
				
			||||||
 | 
					    //
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @param {number} exp
 | 
				
			||||||
 | 
					     * @returns {AuthError}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    EXPIRED: function (exp) {
 | 
				
			||||||
 | 
					        var old = "token's 'exp' has passed or could not parsed: '" + exp + "'";
 | 
				
			||||||
 | 
					        // var msg = "The auth token did not pass verification because it is expired.not properly signed.";
 | 
				
			||||||
 | 
					        var msg = "The auth token is expired. To try again, go to the main page and sign in.";
 | 
				
			||||||
 | 
					        var details = ["jwt.claims.exp = " + JSON.stringify(exp)];
 | 
				
			||||||
 | 
					        return create(old, msg, E_INVALID, 401, details);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @param {number} nbf
 | 
				
			||||||
 | 
					     * @returns {AuthError}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    INACTIVE: function (nbf) {
 | 
				
			||||||
 | 
					        var old = "token's 'nbf' has not been reached or could not parsed: '" + nbf + "'";
 | 
				
			||||||
 | 
					        var msg = "The auth token isn't valid yet. It's activation date (\"nbf\") is in the future.";
 | 
				
			||||||
 | 
					        var details = ["jwt.claims.nbf = " + JSON.stringify(nbf)];
 | 
				
			||||||
 | 
					        return create(old, msg, E_INVALID, 401, details);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    /** @returns {AuthError} */
 | 
				
			||||||
 | 
					    BAD_SIGNATURE: function (jwt) {
 | 
				
			||||||
 | 
					        var old = "token signature verification was unsuccessful";
 | 
				
			||||||
 | 
					        var msg = "The auth token did not pass verification because it is not properly signed.";
 | 
				
			||||||
 | 
					        var details = ["jwt = " + JSON.stringify(jwt)];
 | 
				
			||||||
 | 
					        return create(old, msg, E_INVALID, 401, details);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @param {string} kid
 | 
				
			||||||
 | 
					     * @returns {AuthError}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    JWK_NOT_FOUND_OLD: function (kid) {
 | 
				
			||||||
 | 
					        var old = "Retrieved a list of keys, but none of them matched the 'kid' (key id) of the token.";
 | 
				
			||||||
 | 
					        var msg =
 | 
				
			||||||
 | 
					            'The auth token did not pass verification because our server couldn\'t find a mutually trusted verification key ("jwk").';
 | 
				
			||||||
 | 
					        var details = ["jws.header.kid = " + JSON.stringify(kid)];
 | 
				
			||||||
 | 
					        return create(old, msg, E_INVALID, 401, details);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @param {string} id
 | 
				
			||||||
 | 
					     * @returns {AuthError}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    JWK_NOT_FOUND: function (id) {
 | 
				
			||||||
 | 
					        // TODO Distinguish between when it's a kid vs thumbprint.
 | 
				
			||||||
 | 
					        var old = "No JWK found by kid or thumbprint '" + id + "'";
 | 
				
			||||||
 | 
					        var msg =
 | 
				
			||||||
 | 
					            'The auth token did not pass verification because our server couldn\'t find a mutually trusted verification key ("jwk").';
 | 
				
			||||||
 | 
					        var details = ["jws.header.kid = " + JSON.stringify(id)];
 | 
				
			||||||
 | 
					        return create(old, msg, E_INVALID, 401, details);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    /** @returns {AuthError} */
 | 
				
			||||||
 | 
					    NO_JWKWS_URI: function (url) {
 | 
				
			||||||
 | 
					        var old = "Failed to retrieve openid configuration";
 | 
				
			||||||
 | 
					        var msg =
 | 
				
			||||||
 | 
					            'The auth token did not pass verification because its issuing server did not list any verification keys ("jwks").';
 | 
				
			||||||
 | 
					        var details = ["OpenID Provider Configuration: " + JSON.stringify(url)];
 | 
				
			||||||
 | 
					        return create(old, msg, E_INVALID, 401, details);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @param {string} iss
 | 
				
			||||||
 | 
					     * @returns {AuthError}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    UNKNOWN_ISSUER: function (iss) {
 | 
				
			||||||
 | 
					        var old = "token was issued by an untrusted issuer: '" + iss + "'";
 | 
				
			||||||
 | 
					        var msg = "The auth token did not pass verification because it wasn't issued by a server that we trust.";
 | 
				
			||||||
 | 
					        var details = ["jwt.claims.iss = " + JSON.stringify(iss)];
 | 
				
			||||||
 | 
					        return create(old, msg, E_INVALID, 401, details);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @param {Array<string>} details
 | 
				
			||||||
 | 
					     * @returns {AuthError}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    FAILED_CLAIMS: function (details, claimNames) {
 | 
				
			||||||
 | 
					        var old = "token did not match on one or more authorization claims: '" + claimNames + "'";
 | 
				
			||||||
 | 
					        var msg =
 | 
				
			||||||
 | 
					            'The auth token did not pass verification because it failed some of the verification criteria ("claims").';
 | 
				
			||||||
 | 
					        return create(old, msg, E_INVALID, 401, details);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					var Errors = module.exports;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// for README
 | 
				
			||||||
 | 
					if (require.main === module) {
 | 
				
			||||||
 | 
					    let maxWidth = 54;
 | 
				
			||||||
 | 
					    let header = ["Hint", "Code", "Status", "Message (truncated)"];
 | 
				
			||||||
 | 
					    let widths = header.map(function (v) {
 | 
				
			||||||
 | 
					        return Math.min(maxWidth, String(v).length);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    let rows = [];
 | 
				
			||||||
 | 
					    Object.keys(module.exports).forEach(function (k) {
 | 
				
			||||||
 | 
					        //@ts-ignore
 | 
				
			||||||
 | 
					        var E = module.exports[k];
 | 
				
			||||||
 | 
					        var e = E("test");
 | 
				
			||||||
 | 
					        var code = e.code;
 | 
				
			||||||
 | 
					        var msg = e.message;
 | 
				
			||||||
 | 
					        var hint = k.toLowerCase().replace(/_/g, " ");
 | 
				
			||||||
 | 
					        widths[0] = Math.max(widths[0], String(hint).length);
 | 
				
			||||||
 | 
					        widths[1] = Math.max(widths[1], String(code).length);
 | 
				
			||||||
 | 
					        widths[2] = Math.max(widths[2], String(e.status).length);
 | 
				
			||||||
 | 
					        widths[3] = Math.min(maxWidth, Math.max(widths[3], String(msg).length));
 | 
				
			||||||
 | 
					        rows.push([hint, code, e.status, msg]);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    rows.forEach(function (cols, i) {
 | 
				
			||||||
 | 
					        let cells = cols.map(function (col, i) {
 | 
				
			||||||
 | 
					            if (col.length > maxWidth) {
 | 
				
			||||||
 | 
					                col = col.slice(0, maxWidth - 3);
 | 
				
			||||||
 | 
					                col += "...";
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return String(col).padEnd(widths[i], " ");
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        let out = `| ${cells[0]} | ${cells[1]} | ${cells[2]} | ${cells[3].slice(0, widths[3])} |`;
 | 
				
			||||||
 | 
					        //out = out.replace(/\| /g, " ").replace(/\|/g, "");
 | 
				
			||||||
 | 
					        console.info(out);
 | 
				
			||||||
 | 
					        if (i === 0) {
 | 
				
			||||||
 | 
					            cells = cols.map(function (col, i) {
 | 
				
			||||||
 | 
					                return "-".padEnd(widths[i], "-");
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            console.info(`| ${cells[0]} | ${cells[1]} | ${cells[2]} | ${cells[3]} |`);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    console.log();
 | 
				
			||||||
 | 
					    console.log(Errors.MALFORMED_EXP());
 | 
				
			||||||
 | 
					    console.log();
 | 
				
			||||||
 | 
					    console.log(JSON.stringify(Errors.MALFORMED_EXP(), null, 2));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										97
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										97
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@ -1,23 +1,82 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "name": "keyfetch",
 | 
					    "name": "keyfetch",
 | 
				
			||||||
  "version": "1.1.8",
 | 
					    "version": "3.0.2",
 | 
				
			||||||
  "lockfileVersion": 1,
 | 
					    "lockfileVersion": 2,
 | 
				
			||||||
  "requires": true,
 | 
					    "requires": true,
 | 
				
			||||||
  "dependencies": {
 | 
					    "packages": {
 | 
				
			||||||
    "@coolaj86/urequest": {
 | 
					        "": {
 | 
				
			||||||
      "version": "1.3.7",
 | 
					            "name": "keyfetch",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@coolaj86/urequest/-/urequest-1.3.7.tgz",
 | 
					            "version": "3.0.2",
 | 
				
			||||||
      "integrity": "sha512-PPrVYra9aWvZjSCKl/x1pJ9ZpXda1652oJrPBYy5rQumJJMkmTBN3ux+sK2xAUwVvv2wnewDlaQaHLxLwSHnIA=="
 | 
					            "license": "MPL-2.0",
 | 
				
			||||||
 | 
					            "dependencies": {
 | 
				
			||||||
 | 
					                "@root/request": "^1.8.0",
 | 
				
			||||||
 | 
					                "eckles": "^1.4.1",
 | 
				
			||||||
 | 
					                "rasha": "^1.2.4"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "devDependencies": {
 | 
				
			||||||
 | 
					                "keypairs": "^1.2.14"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "node_modules/@root/request": {
 | 
				
			||||||
 | 
					            "version": "1.8.0",
 | 
				
			||||||
 | 
					            "resolved": "https://registry.npmjs.org/@root/request/-/request-1.8.0.tgz",
 | 
				
			||||||
 | 
					            "integrity": "sha512-HufCvoTwqR30OyKSjwg28W5QCUpypSJZpOYcJbC9PME5kI6cOYsccYs/6bXfsuEoarz8+YwBDrsuM1UdBMxMLw=="
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "node_modules/eckles": {
 | 
				
			||||||
 | 
					            "version": "1.4.1",
 | 
				
			||||||
 | 
					            "resolved": "https://registry.npmjs.org/eckles/-/eckles-1.4.1.tgz",
 | 
				
			||||||
 | 
					            "integrity": "sha512-auWyk/k8oSkVHaD4RxkPadKsLUcIwKgr/h8F7UZEueFDBO7BsE4y+H6IMUDbfqKIFPg/9MxV6KcBdJCmVVcxSA==",
 | 
				
			||||||
 | 
					            "bin": {
 | 
				
			||||||
 | 
					                "eckles": "bin/eckles.js"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "node_modules/keypairs": {
 | 
				
			||||||
 | 
					            "version": "1.2.14",
 | 
				
			||||||
 | 
					            "resolved": "https://registry.npmjs.org/keypairs/-/keypairs-1.2.14.tgz",
 | 
				
			||||||
 | 
					            "integrity": "sha512-ZoZfZMygyB0QcjSlz7Rh6wT2CJasYEHBPETtmHZEfxuJd7bnsOG5AdtPZqHZBT+hoHvuWCp/4y8VmvTvH0Y9uA==",
 | 
				
			||||||
 | 
					            "dev": true,
 | 
				
			||||||
 | 
					            "dependencies": {
 | 
				
			||||||
 | 
					                "eckles": "^1.4.1",
 | 
				
			||||||
 | 
					                "rasha": "^1.2.4"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "bin": {
 | 
				
			||||||
 | 
					                "keypairs-install": "bin/keypairs.js"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "node_modules/rasha": {
 | 
				
			||||||
 | 
					            "version": "1.2.4",
 | 
				
			||||||
 | 
					            "resolved": "https://registry.npmjs.org/rasha/-/rasha-1.2.4.tgz",
 | 
				
			||||||
 | 
					            "integrity": "sha512-GsIwKv+hYSumJyK9wkTDaERLwvWaGYh1WuI7JMTBISfYt13TkKFU/HFzlY4n72p8VfXZRUYm0AqaYhkZVxOC3Q==",
 | 
				
			||||||
 | 
					            "bin": {
 | 
				
			||||||
 | 
					                "rasha": "bin/rasha.js"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "eckles": {
 | 
					    "dependencies": {
 | 
				
			||||||
      "version": "1.4.1",
 | 
					        "@root/request": {
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/eckles/-/eckles-1.4.1.tgz",
 | 
					            "version": "1.8.0",
 | 
				
			||||||
      "integrity": "sha512-auWyk/k8oSkVHaD4RxkPadKsLUcIwKgr/h8F7UZEueFDBO7BsE4y+H6IMUDbfqKIFPg/9MxV6KcBdJCmVVcxSA=="
 | 
					            "resolved": "https://registry.npmjs.org/@root/request/-/request-1.8.0.tgz",
 | 
				
			||||||
    },
 | 
					            "integrity": "sha512-HufCvoTwqR30OyKSjwg28W5QCUpypSJZpOYcJbC9PME5kI6cOYsccYs/6bXfsuEoarz8+YwBDrsuM1UdBMxMLw=="
 | 
				
			||||||
    "rasha": {
 | 
					        },
 | 
				
			||||||
      "version": "1.2.4",
 | 
					        "eckles": {
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/rasha/-/rasha-1.2.4.tgz",
 | 
					            "version": "1.4.1",
 | 
				
			||||||
      "integrity": "sha512-GsIwKv+hYSumJyK9wkTDaERLwvWaGYh1WuI7JMTBISfYt13TkKFU/HFzlY4n72p8VfXZRUYm0AqaYhkZVxOC3Q=="
 | 
					            "resolved": "https://registry.npmjs.org/eckles/-/eckles-1.4.1.tgz",
 | 
				
			||||||
 | 
					            "integrity": "sha512-auWyk/k8oSkVHaD4RxkPadKsLUcIwKgr/h8F7UZEueFDBO7BsE4y+H6IMUDbfqKIFPg/9MxV6KcBdJCmVVcxSA=="
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "keypairs": {
 | 
				
			||||||
 | 
					            "version": "1.2.14",
 | 
				
			||||||
 | 
					            "resolved": "https://registry.npmjs.org/keypairs/-/keypairs-1.2.14.tgz",
 | 
				
			||||||
 | 
					            "integrity": "sha512-ZoZfZMygyB0QcjSlz7Rh6wT2CJasYEHBPETtmHZEfxuJd7bnsOG5AdtPZqHZBT+hoHvuWCp/4y8VmvTvH0Y9uA==",
 | 
				
			||||||
 | 
					            "dev": true,
 | 
				
			||||||
 | 
					            "requires": {
 | 
				
			||||||
 | 
					                "eckles": "^1.4.1",
 | 
				
			||||||
 | 
					                "rasha": "^1.2.4"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "rasha": {
 | 
				
			||||||
 | 
					            "version": "1.2.4",
 | 
				
			||||||
 | 
					            "resolved": "https://registry.npmjs.org/rasha/-/rasha-1.2.4.tgz",
 | 
				
			||||||
 | 
					            "integrity": "sha512-GsIwKv+hYSumJyK9wkTDaERLwvWaGYh1WuI7JMTBISfYt13TkKFU/HFzlY4n72p8VfXZRUYm0AqaYhkZVxOC3Q=="
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										71
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										71
									
								
								package.json
									
									
									
									
									
								
							@ -1,36 +1,39 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "name": "keyfetch",
 | 
					    "name": "keyfetch",
 | 
				
			||||||
  "version": "1.2.1",
 | 
					    "version": "3.0.2",
 | 
				
			||||||
  "description": "Lightweight support for fetching JWKs.",
 | 
					    "description": "Lightweight support for fetching JWKs.",
 | 
				
			||||||
  "homepage": "https://git.coolaj86.com/coolaj86/keyfetch.js",
 | 
					    "homepage": "https://git.rootprojects.org/root/keyfetch.js",
 | 
				
			||||||
  "main": "keyfetch.js",
 | 
					    "main": "keyfetch.js",
 | 
				
			||||||
  "files": [],
 | 
					    "files": [
 | 
				
			||||||
  "dependencies": {
 | 
					        "lib"
 | 
				
			||||||
    "@coolaj86/urequest": "^1.3.7",
 | 
					    ],
 | 
				
			||||||
    "eckles": "^1.4.1",
 | 
					    "dependencies": {
 | 
				
			||||||
    "rasha": "^1.2.4"
 | 
					        "@root/request": "^1.8.0",
 | 
				
			||||||
  },
 | 
					        "eckles": "^1.4.1",
 | 
				
			||||||
  "devDependencies": {},
 | 
					        "rasha": "^1.2.4"
 | 
				
			||||||
  "scripts": {
 | 
					    },
 | 
				
			||||||
    "test": "node keyfetch-test.js"
 | 
					    "devDependencies": {
 | 
				
			||||||
  },
 | 
					        "keypairs": "^1.2.14"
 | 
				
			||||||
  "repository": {
 | 
					    },
 | 
				
			||||||
    "type": "git",
 | 
					    "scripts": {
 | 
				
			||||||
    "url": "https://git.coolaj86.com/coolaj86/keyfetch.js.git"
 | 
					        "test": "node keyfetch-test.js"
 | 
				
			||||||
  },
 | 
					    },
 | 
				
			||||||
  "keywords": [
 | 
					    "repository": {
 | 
				
			||||||
    "jwks",
 | 
					        "type": "git",
 | 
				
			||||||
    "jwk",
 | 
					        "url": "https://git.rootprojects.org/root/keyfetch.js.git"
 | 
				
			||||||
    "jwt",
 | 
					    },
 | 
				
			||||||
    "auth0",
 | 
					    "keywords": [
 | 
				
			||||||
    "pem",
 | 
					        "jwks",
 | 
				
			||||||
    "RSA",
 | 
					        "jwk",
 | 
				
			||||||
    "EC",
 | 
					        "jwt",
 | 
				
			||||||
    "ECDSA",
 | 
					        "auth0",
 | 
				
			||||||
    "OIDC",
 | 
					        "pem",
 | 
				
			||||||
    "well-known"
 | 
					        "RSA",
 | 
				
			||||||
  ],
 | 
					        "EC",
 | 
				
			||||||
  "author": "AJ ONeal <solderjs@gmail.com> (https://coolaj86.com/)",
 | 
					        "ECDSA",
 | 
				
			||||||
  "license": "MPL-2.0"
 | 
					        "OIDC",
 | 
				
			||||||
 | 
					        "well-known"
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)",
 | 
				
			||||||
 | 
					    "license": "MPL-2.0"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user