mirror of
				https://github.com/therootcompany/keyfetch.js.git
				synced 2024-11-16 17:29:02 +00:00 
			
		
		
		
	Compare commits
	
		
			No commits in common. "main" and "v1.2.2" have entirely different histories.
		
	
	
		
	
		
							
								
								
									
										22
									
								
								.jshintrc
									
									
									
									
									
								
							
							
						
						
									
										22
									
								
								.jshintrc
									
									
									
									
									
								
							| @ -1,22 +0,0 @@ | |||||||
| { |  | ||||||
|     "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 |  | ||||||
| } |  | ||||||
							
								
								
									
										11
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @ -1,11 +0,0 @@ | |||||||
| # 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). |  | ||||||
							
								
								
									
										64
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										64
									
								
								README.md
									
									
									
									
									
								
							| @ -11,7 +11,7 @@ Works great for | |||||||
| 
 | 
 | ||||||
| -   [x] `jsonwebtoken` (Auth0) | -   [x] `jsonwebtoken` (Auth0) | ||||||
| -   [x] OIDC (OpenID Connect) | -   [x] OIDC (OpenID Connect) | ||||||
| -   [x] .well-known/jwks.json (Auth0, Okta) | -   [x] .well-known/jwks.json (Auth0) | ||||||
| -   [x] Other JWKs URLs | -   [x] Other JWKs URLs | ||||||
| 
 | 
 | ||||||
| Crypto Support | Crypto Support | ||||||
| @ -19,19 +19,8 @@ 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) | ||||||
| -   [x] Sane error codes |  | ||||||
| -   [ ] esoteric variants (excluded to keep the code featherweight and secure) | -   [ ] esoteric variants (excluded to keep the code featherweight and secure) | ||||||
| 
 | 
 | ||||||
| # Table of Contents |  | ||||||
| 
 |  | ||||||
| -   Install |  | ||||||
| -   Usage |  | ||||||
| -   API |  | ||||||
|     -   Auth0 / Okta |  | ||||||
|     -   OIDC |  | ||||||
| -   Errors |  | ||||||
| -   Change Log |  | ||||||
| 
 |  | ||||||
| # Install | # Install | ||||||
| 
 | 
 | ||||||
| ```bash | ```bash | ||||||
| @ -182,8 +171,8 @@ keyfetch.jwt.verify(jwt, { strategy: "oidc" }).then(function (verified) { | |||||||
| }); | }); | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| When used for authorization, it's important to specify a limited set of trusted `issuers`. \ | When used for authorization, it's important to specify which `issuers` are allowed | ||||||
| When using for federated authentication you may set `issuers = ["*"]` - but **DO NOT** trust claims such as `email` and `email_verified`. | (otherwise anyone can create a valid token with whatever any claims they want). | ||||||
| 
 | 
 | ||||||
| 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. | ||||||
| 
 | 
 | ||||||
| @ -259,50 +248,3 @@ keyfetch.init({ | |||||||
| 
 | 
 | ||||||
| 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). |  | ||||||
|  | |||||||
| @ -120,12 +120,17 @@ keypairs.generate().then(function (pair) { | |||||||
|                 exp: "1h" |                 exp: "1h" | ||||||
|             }) |             }) | ||||||
|             .then(function (jwt) { |             .then(function (jwt) { | ||||||
|  |                 var warned = false; | ||||||
|  |                 console.warn = function () { | ||||||
|  |                     warned = true; | ||||||
|  |                 }; | ||||||
|                 return Promise.all([ |                 return Promise.all([ | ||||||
|                     // test that the old behavior of defaulting to '*' still works
 |                     // test that the old behavior of defaulting to '*' still works
 | ||||||
|                     keyfetch.jwt |                     keyfetch.jwt.verify(jwt, { jwk: pair.public }).then(function () { | ||||||
|                         .verify(jwt, { jwk: pair.public }) |                         if (!warned) { | ||||||
|                         .then(e("should have issued security warning about allow all by default")) |                             throw 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, { jwk: pair.public, issuers: ["*"] }), | ||||||
|                     keyfetch.jwt.verify(jwt).then(e("should have an issuer")).catch(throwIfNotExpected), |                     keyfetch.jwt.verify(jwt).then(e("should have an issuer")).catch(throwIfNotExpected), | ||||||
|                     keyfetch.jwt |                     keyfetch.jwt | ||||||
|  | |||||||
							
								
								
									
										247
									
								
								keyfetch.js
									
									
									
									
									
								
							
							
						
						
									
										247
									
								
								keyfetch.js
									
									
									
									
									
								
							| @ -2,9 +2,8 @@ | |||||||
| 
 | 
 | ||||||
| var keyfetch = module.exports; | var keyfetch = module.exports; | ||||||
| 
 | 
 | ||||||
| var request = require("@root/request").defaults({ | var promisify = require("util").promisify; | ||||||
|     userAgent: "keyfetch/v2.1.0" | var requestAsync = promisify(require("@root/request")); | ||||||
| }); |  | ||||||
| var Rasha = require("rasha"); | var Rasha = require("rasha"); | ||||||
| var Eckles = require("eckles"); | var Eckles = require("eckles"); | ||||||
| var mincache = 1 * 60 * 60; | var mincache = 1 * 60 * 60; | ||||||
| @ -12,19 +11,7 @@ var maxcache = 3 * 24 * 60 * 60; | |||||||
| var staletime = 15 * 60; | var staletime = 15 * 60; | ||||||
| var keyCache = {}; | var keyCache = {}; | ||||||
| 
 | 
 | ||||||
| var Errors = require("./lib/errors.js"); | /*global Promise*/ | ||||||
| 
 |  | ||||||
| async function requestAsync(req) { |  | ||||||
|     var resp = await request(req).catch(Errors.BAD_GATEWAY); |  | ||||||
| 
 |  | ||||||
|     // differentiate potentially temporary server errors from 404
 |  | ||||||
|     if (!resp.ok && (resp.statusCode >= 500 || resp.statusCode < 200)) { |  | ||||||
|         throw Errors.BAD_GATEWAY({ response: resp }); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return resp; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function checkMinDefaultMax(opts, key, n, d, x) { | function checkMinDefaultMax(opts, key, n, d, x) { | ||||||
|     var i = opts[key]; |     var i = opts[key]; | ||||||
|     if (!i && 0 !== i) { |     if (!i && 0 !== i) { | ||||||
| @ -33,12 +20,10 @@ function checkMinDefaultMax(opts, key, n, d, x) { | |||||||
|     if (i >= n && i >= x) { |     if (i >= n && i >= x) { | ||||||
|         return parseInt(i, 10); |         return parseInt(i, 10); | ||||||
|     } else { |     } else { | ||||||
|         throw Errors.DEVELOPER_ERROR("opts." + key + " should be at least " + n + " and at most " + x + ", not " + i); |         throw new 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 = {}; | ||||||
| }; | }; | ||||||
| @ -47,73 +32,85 @@ keyfetch.init = function (opts) { | |||||||
|     maxcache = checkMinDefaultMax(opts, "maxcache", 1 * 60 * 60, maxcache, 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); |     staletime = checkMinDefaultMax(opts, "staletime", 1 * 60, staletime, 31 * 24 * 60 * 60); | ||||||
| }; | }; | ||||||
| keyfetch._oidc = async function (iss) { | keyfetch._oidc = function (iss) { | ||||||
|     var url = normalizeIss(iss) + "/.well-known/openid-configuration"; |     return Promise.resolve().then(function () { | ||||||
|     var resp = await requestAsync({ |         return requestAsync({ | ||||||
|         url: url, |             url: normalizeIss(iss) + "/.well-known/openid-configuration", | ||||||
|             json: true |             json: true | ||||||
|     }); |         }).then(function (resp) { | ||||||
| 
 |  | ||||||
|             var oidcConf = resp.body; |             var oidcConf = resp.body; | ||||||
|             if (!oidcConf.jwks_uri) { |             if (!oidcConf.jwks_uri) { | ||||||
|         throw Errors.NO_JWKS_URI(url); |                 throw new Error("Failed to retrieve openid configuration"); | ||||||
|             } |             } | ||||||
|             return oidcConf; |             return oidcConf; | ||||||
|  |         }); | ||||||
|  |     }); | ||||||
| }; | }; | ||||||
| keyfetch._wellKnownJwks = async function (iss) { | keyfetch._wellKnownJwks = 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 = async function (iss) { | keyfetch._jwks = function (iss) { | ||||||
|     var resp = await requestAsync({ url: iss, json: true }); |     return requestAsync({ url: iss, json: true }).then(function (resp) { | ||||||
| 
 |  | ||||||
|         return Promise.all( |         return Promise.all( | ||||||
|         resp.body.keys.map(async function (jwk) { |             resp.body.keys.map(function (jwk) { | ||||||
|                 // EC keys have an x values, whereas RSA keys do not
 |                 // EC keys have an x values, whereas RSA keys do not
 | ||||||
|                 var Keypairs = jwk.x ? Eckles : Rasha; |                 var Keypairs = jwk.x ? Eckles : Rasha; | ||||||
|             var thumbprint = await Keypairs.thumbprint({ jwk: jwk }); |                 return Keypairs.thumbprint({ jwk: jwk }).then(function (thumbprint) { | ||||||
|             var pem = await Keypairs.export({ jwk: jwk }); |                     return Keypairs.export({ jwk: jwk }).then(function (pem) { | ||||||
|                         var cacheable = { |                         var cacheable = { | ||||||
|                             jwk: jwk, |                             jwk: jwk, | ||||||
|                             thumbprint: thumbprint, |                             thumbprint: thumbprint, | ||||||
|                             pem: pem |                             pem: pem | ||||||
|                         }; |                         }; | ||||||
|                         return cacheable; |                         return cacheable; | ||||||
|  |                     }); | ||||||
|  |                 }); | ||||||
|             }) |             }) | ||||||
|         ); |         ); | ||||||
|  |     }); | ||||||
| }; | }; | ||||||
| keyfetch.jwks = async function (jwkUrl) { | keyfetch.jwks = function (jwkUrl) { | ||||||
|     // TODO DRY up a bit
 |     // TODO DRY up a bit
 | ||||||
|     var results = await keyfetch._jwks(jwkUrl); |     return keyfetch._jwks(jwkUrl).then(function (results) { | ||||||
|     await Promise.all( |         return Promise.all( | ||||||
|         results.map(async function (result) { |             results.map(function (result) { | ||||||
|                 return keyfetch._setCache(result.jwk.iss || jwkUrl, result); |                 return keyfetch._setCache(result.jwk.iss || jwkUrl, result); | ||||||
|             }) |             }) | ||||||
|     ); |         ).then(function () { | ||||||
|             // cacheable -> hit (keep original externally immutable)
 |             // cacheable -> hit (keep original externally immutable)
 | ||||||
|             return JSON.parse(JSON.stringify(results)); |             return JSON.parse(JSON.stringify(results)); | ||||||
|  |         }); | ||||||
|  |     }); | ||||||
| }; | }; | ||||||
| keyfetch.wellKnownJwks = async function (iss) { | keyfetch.wellKnownJwks = function (iss) { | ||||||
|     // TODO DRY up a bit
 |     // TODO DRY up a bit
 | ||||||
|     var results = await keyfetch._wellKnownJwks(iss); |     return keyfetch._wellKnownJwks(iss).then(function (results) { | ||||||
|     await Promise.all( |         return Promise.all( | ||||||
|         results.map(async function (result) { |             results.map(function (result) { | ||||||
|                 return keyfetch._setCache(result.jwk.iss || iss, result); |                 return keyfetch._setCache(result.jwk.iss || iss, result); | ||||||
|             }) |             }) | ||||||
|     ); |         ).then(function () { | ||||||
|             // result -> hit (keep original externally immutable)
 |             // result -> hit (keep original externally immutable)
 | ||||||
|             return JSON.parse(JSON.stringify(results)); |             return JSON.parse(JSON.stringify(results)); | ||||||
|  |         }); | ||||||
|  |     }); | ||||||
| }; | }; | ||||||
| keyfetch.oidcJwks = async function (iss) { | keyfetch.oidcJwks = function (iss) { | ||||||
|     var oidcConf = await keyfetch._oidc(iss); |     return keyfetch._oidc(iss).then(function (oidcConf) { | ||||||
|         // TODO DRY up a bit
 |         // TODO DRY up a bit
 | ||||||
|     var results = await keyfetch._jwks(oidcConf.jwks_uri); |         return keyfetch._jwks(oidcConf.jwks_uri).then(function (results) { | ||||||
|     await Promise.all( |             return Promise.all( | ||||||
|         results.map(async function (result) { |                 results.map(function (result) { | ||||||
|                     return keyfetch._setCache(result.jwk.iss || iss, result); |                     return keyfetch._setCache(result.jwk.iss || iss, result); | ||||||
|                 }) |                 }) | ||||||
|     ); |             ).then(function () { | ||||||
|                 // result -> hit (keep original externally immutable)
 |                 // result -> hit (keep original externally immutable)
 | ||||||
|                 return JSON.parse(JSON.stringify(results)); |                 return JSON.parse(JSON.stringify(results)); | ||||||
|  |             }); | ||||||
|  |         }); | ||||||
|  |     }); | ||||||
| }; | }; | ||||||
| function checkId(id) { | function checkId(id) { | ||||||
|     return function (results) { |     return function (results) { | ||||||
| @ -123,33 +120,37 @@ function checkId(id) { | |||||||
|         })[0]; |         })[0]; | ||||||
| 
 | 
 | ||||||
|         if (!result) { |         if (!result) { | ||||||
|             throw Errors.JWK_NOT_FOUND(id); |             throw new Error("No JWK found by kid or thumbprint '" + id + "'"); | ||||||
|         } |         } | ||||||
|         return result; |         return result; | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
| keyfetch.oidcJwk = async function (id, iss) { | keyfetch.oidcJwk = function (id, iss) { | ||||||
|     var hit = await keyfetch._checkCache(id, iss); |     return keyfetch._checkCache(id, iss).then(function (hit) { | ||||||
|         if (hit) { |         if (hit) { | ||||||
|             return hit; |             return hit; | ||||||
|         } |         } | ||||||
|         return keyfetch.oidcJwks(iss).then(checkId(id)); |         return keyfetch.oidcJwks(iss).then(checkId(id)); | ||||||
|  |     }); | ||||||
| }; | }; | ||||||
| keyfetch.wellKnownJwk = async function (id, iss) { | keyfetch.wellKnownJwk = function (id, iss) { | ||||||
|     var hit = await keyfetch._checkCache(id, iss); |     return keyfetch._checkCache(id, iss).then(function (hit) { | ||||||
|         if (hit) { |         if (hit) { | ||||||
|             return hit; |             return hit; | ||||||
|         } |         } | ||||||
|         return keyfetch.wellKnownJwks(iss).then(checkId(id)); |         return keyfetch.wellKnownJwks(iss).then(checkId(id)); | ||||||
|  |     }); | ||||||
| }; | }; | ||||||
| keyfetch.jwk = async function (id, jwksUrl) { | keyfetch.jwk = function (id, jwksUrl) { | ||||||
|     var hit = await keyfetch._checkCache(id, jwksUrl); |     return keyfetch._checkCache(id, jwksUrl).then(function (hit) { | ||||||
|         if (hit) { |         if (hit) { | ||||||
|             return hit; |             return hit; | ||||||
|         } |         } | ||||||
|         return keyfetch.jwks(jwksUrl).then(checkId(id)); |         return keyfetch.jwks(jwksUrl).then(checkId(id)); | ||||||
|  |     }); | ||||||
| }; | }; | ||||||
| keyfetch._checkCache = async function (id, iss) { | keyfetch._checkCache = 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]; | ||||||
| @ -171,6 +172,7 @@ keyfetch._checkCache = async function (id, iss) { | |||||||
|             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
 | ||||||
| @ -196,7 +198,7 @@ keyfetch._setCache = function (iss, cacheable) { | |||||||
| 
 | 
 | ||||||
| function normalizeIss(iss) { | function normalizeIss(iss) { | ||||||
|     if (!iss) { |     if (!iss) { | ||||||
|         throw Errors.NO_ISSUER(); |         throw new Error("'iss' is not defined"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // We definitely don't want false negatives stemming
 |     // We definitely don't want false negatives stemming
 | ||||||
| @ -204,39 +206,38 @@ function normalizeIss(iss) { | |||||||
|     // We also don't want to allow insecure issuers
 |     // We also don't want to allow insecure issuers
 | ||||||
|     if (/^http:/.test(iss) && !process.env.KEYFETCH_ALLOW_INSECURE_HTTP) { |     if (/^http:/.test(iss) && !process.env.KEYFETCH_ALLOW_INSECURE_HTTP) { | ||||||
|         // note, we wrap some things in promises just so we can throw here
 |         // note, we wrap some things in promises just so we can throw here
 | ||||||
|         throw Errors.INSECURE_ISSUER(iss); |         throw new Error( | ||||||
|  |             "'" + iss + "' is NOT secure. Set env 'KEYFETCH_ALLOW_INSECURE_HTTP=true' to allow for testing." | ||||||
|  |         ); | ||||||
|     } |     } | ||||||
|     return iss.replace(/\/$/, ""); |     return iss.replace(/\/$/, ""); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| keyfetch.jwt = {}; | keyfetch.jwt = {}; | ||||||
| keyfetch.jwt.decode = function (jwt) { | keyfetch.jwt.decode = function (jwt) { | ||||||
|     try { |  | ||||||
|     var parts = jwt.split("."); |     var parts = jwt.split("."); | ||||||
|     // JWS
 |     // JWS
 | ||||||
|         var obj = { protected: parts[0], payload: parts[1], signature: parts[2] }; |     var obj = { | ||||||
|  |         protected: parts[0], | ||||||
|  |         payload: parts[1], | ||||||
|  |         signature: parts[2] | ||||||
|  |     }; | ||||||
|     // JWT
 |     // JWT
 | ||||||
|     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; | ||||||
|     } catch (e) { |  | ||||||
|         var err = Errors.PARSE_ERROR(jwt); |  | ||||||
|         err.details = e.message; |  | ||||||
|         throw err; |  | ||||||
|     } |  | ||||||
| }; | }; | ||||||
| keyfetch.jwt.verify = async function (jwt, opts) { | keyfetch.jwt.verify = function (jwt, opts) { | ||||||
|     if (!opts) { |     if (!opts) { | ||||||
|         opts = {}; |         opts = {}; | ||||||
|     } |     } | ||||||
| 
 |     return Promise.resolve().then(function () { | ||||||
|     var jws; |         var decoded; | ||||||
|         var exp; |         var exp; | ||||||
|         var nbf; |         var nbf; | ||||||
|         var active; |         var active; | ||||||
|     var now; |  | ||||||
|     var then; |  | ||||||
|         var issuers = opts.issuers || []; |         var issuers = opts.issuers || []; | ||||||
|  |         var err; | ||||||
|         if (opts.iss) { |         if (opts.iss) { | ||||||
|             issuers.push(opts.iss); |             issuers.push(opts.iss); | ||||||
|         } |         } | ||||||
| @ -244,76 +245,66 @@ keyfetch.jwt.verify = async function (jwt, opts) { | |||||||
|             issuers.push(opts.claims.iss); |             issuers.push(opts.claims.iss); | ||||||
|         } |         } | ||||||
|         if (!issuers.length) { |         if (!issuers.length) { | ||||||
|         if (!(opts.jwk || opts.jwks)) { |             err = new Error( | ||||||
|             throw Errors.DEVELOPER_ERROR( |                 "\n[keyfetch.js] Security Warning:\nNeither of opts.issuers nor opts.iss were provided. The default behavior of setting opts.issuers = ['*'] by default - which allows any or no issuers - is deprecated and will throw an error instead in the next major version.\n" | ||||||
|                 "[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/" |  | ||||||
|             ); |             ); | ||||||
|         } |             console.warn(err); | ||||||
|  |             issuers.push("*"); | ||||||
|         } |         } | ||||||
|         var claims = opts.claims || {}; |         var claims = opts.claims || {}; | ||||||
|         if (!jwt || "string" === typeof jwt) { |         if (!jwt || "string" === typeof jwt) { | ||||||
|         jws = keyfetch.jwt.decode(jwt); |             try { | ||||||
|  |                 decoded = keyfetch.jwt.decode(jwt); | ||||||
|  |             } catch (e) { | ||||||
|  |                 throw new Error("could not parse jwt: '" + jwt + "'"); | ||||||
|  |             } | ||||||
|         } else { |         } else { | ||||||
|         jws = jwt; |             decoded = jwt; | ||||||
|         } |         } | ||||||
|  |         exp = decoded.claims.exp; | ||||||
|  |         nbf = decoded.claims.nbf; | ||||||
| 
 | 
 | ||||||
|     if (!jws.claims.iss || !issuers.some(isTrustedIssuer(jws.claims.iss))) { |         if (!issuers.some(isTrustedIssuer(decoded.claims.iss))) { | ||||||
|         if (!(opts.jwk || opts.jwks)) { |             throw new Error("token was issued by an untrusted issuer: '" + decoded.claims.iss + "'"); | ||||||
|             throw Errors.UNKNOWN_ISSUER(jws.claims.iss || ""); |  | ||||||
|         } |  | ||||||
|         } |         } | ||||||
|         // Note claims.iss validates more strictly than opts.issuers (requires exact match)
 |         // Note claims.iss validates more strictly than opts.issuers (requires exact match)
 | ||||||
|     var failedClaims = Object.keys(claims) |         if ( | ||||||
|         .filter(function (key) { |             !Object.keys(claims).every(function (key) { | ||||||
|             if (claims[key] !== jws.claims[key]) { |                 if (claims[key] === decoded.claims[key]) { | ||||||
|                     return true; |                     return true; | ||||||
|                 } |                 } | ||||||
|             }) |             }) | ||||||
|         .map(function (key) { |         ) { | ||||||
|             return "jwt.claims." + key + " = " + JSON.stringify(jws.claims[key]); |             throw new Error("token did not match on one or more authorization claims: '" + Object.keys(claims) + "'"); | ||||||
|         }); |  | ||||||
|     if (failedClaims.length) { |  | ||||||
|         throw Errors.FAILED_CLAIMS(failedClaims, Object.keys(claims)); |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|     exp = jws.claims.exp; |         active = (opts.exp || 0) + parseInt(exp, 10) - Date.now() / 1000 > 0; | ||||||
|     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) { |         if (!active) { | ||||||
|             throw Errors.EXPIRED(exp); |             // expiration was on the token or, if not, such a token is not allowed
 | ||||||
|  |             if (exp || false !== opts.exp) { | ||||||
|  |                 throw new Error("token's 'exp' has passed or could not parsed: '" + 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 Errors.INACTIVE(nbf); |                 throw new Error("token's 'nbf' has not been reached or could not parsed: '" + 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 = jws.header.kid; |         var kid = decoded.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 = jws.claims.iss; |             iss = decoded.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 = jws.claims.iss; |             iss = decoded.claims.iss; | ||||||
|             fetcher = keyfetch.wellKnownJwks; |             fetcher = keyfetch.wellKnownJwks; | ||||||
|             fetchOne = keyfetch.wellKnownJwk; |             fetchOne = keyfetch.wellKnownJwk; | ||||||
|         } else { |         } else { | ||||||
| @ -322,16 +313,19 @@ keyfetch.jwt.verify = async function (jwt, opts) { | |||||||
|             fetchOne = keyfetch.jwk; |             fetchOne = keyfetch.jwk; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         var p; | ||||||
|         if (kid) { |         if (kid) { | ||||||
|         return fetchOne(kid, iss).then(verifyOne); //.catch(fetchAny);
 |             p = fetchOne(kid, iss).then(verifyOne); //.catch(fetchAny);
 | ||||||
|  |         } else { | ||||||
|  |             p = fetcher(iss).then(verifyAny); | ||||||
|         } |         } | ||||||
|     return fetcher(iss).then(verifyAny); |         return p; | ||||||
| 
 | 
 | ||||||
|         function verifyOne(hit) { |         function verifyOne(hit) { | ||||||
|         if (true === keyfetch.jws.verify(jws, hit)) { |             if (true === keyfetch.jws.verify(decoded, hit)) { | ||||||
|             return jws; |                 return decoded; | ||||||
|             } |             } | ||||||
|         throw Errors.BAD_SIGNATURE(jws.protected + "." + jws.payload + "." + jws.signature); |             throw new Error("token signature verification was unsuccessful"); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         function verifyAny(hits) { |         function verifyAny(hits) { | ||||||
| @ -341,31 +335,35 @@ keyfetch.jwt.verify = async function (jwt, opts) { | |||||||
|                         if (kid !== hit.jwk.kid && kid !== hit.thumbprint) { |                         if (kid !== hit.jwk.kid && kid !== hit.thumbprint) { | ||||||
|                             return; |                             return; | ||||||
|                         } |                         } | ||||||
|                     if (true === keyfetch.jws.verify(jws, hit)) { |                         if (true === keyfetch.jws.verify(decoded, hit)) { | ||||||
|                             return true; |                             return true; | ||||||
|                         } |                         } | ||||||
|                     throw Errors.BAD_SIGNATURE(); |                         throw new Error("token signature verification was unsuccessful"); | ||||||
|                 } |                     } else { | ||||||
|                 if (true === keyfetch.jws.verify(jws, hit)) { |                         if (true === keyfetch.jws.verify(decoded, hit)) { | ||||||
|                             return true; |                             return true; | ||||||
|                         } |                         } | ||||||
|  |                     } | ||||||
|                 }) |                 }) | ||||||
|             ) { |             ) { | ||||||
|             return jws; |                 return decoded; | ||||||
|             } |             } | ||||||
|         throw Errors.JWK_NOT_FOUND_OLD(kid); |             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( |             return Promise.all( | ||||||
|             jwks.map(async function (jwk) { |                 jwks.map(function (jwk) { | ||||||
|                     var Keypairs = jwk.x ? Eckles : Rasha; |                     var Keypairs = jwk.x ? Eckles : Rasha; | ||||||
|                 var pem = await Keypairs.export({ jwk: jwk }); |                     return Keypairs.export({ jwk: jwk }).then(function (pem) { | ||||||
|                 var thumb = await Keypairs.thumbprint({ jwk: jwk }); |                         return Keypairs.thumbprint({ jwk: jwk }).then(function (thumb) { | ||||||
|                             return { jwk: jwk, pem: pem, thumbprint: thumb }; |                             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) { | ||||||
| @ -382,10 +380,11 @@ 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 = async function (opts) { | keyfetch.verify = function (opts) { | ||||||
|     var jwt = opts.jwt; |     var jwt = opts.jwt; | ||||||
|     var obj = await keyfetch.jwt.verify(jwt, opts); |     return keyfetch.jwt.verify(jwt, opts).then(function (obj) { | ||||||
|         return { header: obj.header, payload: obj.claims, signature: obj.signature }; |         return { header: obj.header, payload: obj.claims, signature: obj.signature }; | ||||||
|  |     }); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| function ecdsaJoseSigToAsn1Sig(header, b64sig) { | function ecdsaJoseSigToAsn1Sig(header, b64sig) { | ||||||
|  | |||||||
							
								
								
									
										270
									
								
								lib/errors.js
									
									
									
									
									
								
							
							
						
						
									
										270
									
								
								lib/errors.js
									
									
									
									
									
								
							| @ -1,270 +0,0 @@ | |||||||
| "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)); |  | ||||||
| } |  | ||||||
							
								
								
									
										59
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										59
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -1,62 +1,13 @@ | |||||||
| { | { | ||||||
|   "name": "keyfetch", |   "name": "keyfetch", | ||||||
|     "version": "3.0.2", |   "version": "1.2.2", | ||||||
|     "lockfileVersion": 2, |   "lockfileVersion": 1, | ||||||
|   "requires": true, |   "requires": true, | ||||||
|     "packages": { |  | ||||||
|         "": { |  | ||||||
|             "name": "keyfetch", |  | ||||||
|             "version": "3.0.2", |  | ||||||
|             "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" |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "@root/request": { |     "@root/request": { | ||||||
|             "version": "1.8.0", |       "version": "1.7.0", | ||||||
|             "resolved": "https://registry.npmjs.org/@root/request/-/request-1.8.0.tgz", |       "resolved": "https://registry.npmjs.org/@root/request/-/request-1.7.0.tgz", | ||||||
|             "integrity": "sha512-HufCvoTwqR30OyKSjwg28W5QCUpypSJZpOYcJbC9PME5kI6cOYsccYs/6bXfsuEoarz8+YwBDrsuM1UdBMxMLw==" |       "integrity": "sha512-lre7XVeEwszgyrayWWb/kRn5fuJfa+n0Nh+rflM9E+EpC28yIYA+FPm/OL1uhzp3TxhQM0HFN4FE2RDIPGlnmg==" | ||||||
|     }, |     }, | ||||||
|     "eckles": { |     "eckles": { | ||||||
|       "version": "1.4.1", |       "version": "1.4.1", | ||||||
|  | |||||||
| @ -1,14 +1,12 @@ | |||||||
| { | { | ||||||
|     "name": "keyfetch", |     "name": "keyfetch", | ||||||
|     "version": "3.0.2", |     "version": "1.2.2", | ||||||
|     "description": "Lightweight support for fetching JWKs.", |     "description": "Lightweight support for fetching JWKs.", | ||||||
|     "homepage": "https://git.rootprojects.org/root/keyfetch.js", |     "homepage": "https://git.rootprojects.org/root/keyfetch.js", | ||||||
|     "main": "keyfetch.js", |     "main": "keyfetch.js", | ||||||
|     "files": [ |     "files": [], | ||||||
|         "lib" |  | ||||||
|     ], |  | ||||||
|     "dependencies": { |     "dependencies": { | ||||||
|         "@root/request": "^1.8.0", |         "@root/request": "^1.7.0", | ||||||
|         "eckles": "^1.4.1", |         "eckles": "^1.4.1", | ||||||
|         "rasha": "^1.2.4" |         "rasha": "^1.2.4" | ||||||
|     }, |     }, | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user