mirror of
				https://github.com/therootcompany/keyfetch.js.git
				synced 2024-11-16 17:29:02 +00:00 
			
		
		
		
	Compare commits
	
		
			9 Commits
		
	
	
		
			523a4f0d1a
			...
			a84e833571
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| a84e833571 | |||
| 076246e4d0 | |||
| 4c85fc4009 | |||
| d5647ea905 | |||
| 2ea44e3a46 | |||
| 5ef53ecb23 | |||
| 604b42c7ef | |||
| c57a08f0cd | |||
| a3539b0941 | 
							
								
								
									
										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). | ||||||
							
								
								
									
										60
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										60
									
								
								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) | -   [x] .well-known/jwks.json (Auth0, Okta) | ||||||
| -   [x] Other JWKs URLs | -   [x] Other JWKs URLs | ||||||
| 
 | 
 | ||||||
| Crypto Support | Crypto Support | ||||||
| @ -19,8 +19,19 @@ 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 | ||||||
| @ -248,3 +259,50 @@ 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). | ||||||
|  | |||||||
							
								
								
									
										74
									
								
								keyfetch.js
									
									
									
									
									
								
							
							
						
						
									
										74
									
								
								keyfetch.js
									
									
									
									
									
								
							| @ -19,7 +19,7 @@ async function requestAsync(req) { | |||||||
| 
 | 
 | ||||||
|     // differentiate potentially temporary server errors from 404
 |     // differentiate potentially temporary server errors from 404
 | ||||||
|     if (!resp.ok && (resp.statusCode >= 500 || resp.statusCode < 200)) { |     if (!resp.ok && (resp.statusCode >= 500 || resp.statusCode < 200)) { | ||||||
|         throw Errors.BAD_GATEWAY(); |         throw Errors.BAD_GATEWAY({ response: resp }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return resp; |     return resp; | ||||||
| @ -37,6 +37,8 @@ function checkMinDefaultMax(opts, key, n, d, x) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | keyfetch._errors = Errors; | ||||||
|  | 
 | ||||||
| keyfetch._clear = function () { | keyfetch._clear = function () { | ||||||
|     keyCache = {}; |     keyCache = {}; | ||||||
| }; | }; | ||||||
| @ -46,14 +48,15 @@ keyfetch.init = function (opts) { | |||||||
|     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 = async function (iss) { | ||||||
|  |     var url = normalizeIss(iss) + "/.well-known/openid-configuration"; | ||||||
|     var resp = await requestAsync({ |     var resp = await requestAsync({ | ||||||
|         url: normalizeIss(iss) + "/.well-known/openid-configuration", |         url: url, | ||||||
|         json: true |         json: true | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     var oidcConf = resp.body; |     var oidcConf = resp.body; | ||||||
|     if (!oidcConf.jwks_uri) { |     if (!oidcConf.jwks_uri) { | ||||||
|         throw Errors.OIDC_CONFIG_NOT_FOUND(); |         throw Errors.NO_JWKS_URI(url); | ||||||
|     } |     } | ||||||
|     return oidcConf; |     return oidcConf; | ||||||
| }; | }; | ||||||
| @ -193,7 +196,7 @@ keyfetch._setCache = function (iss, cacheable) { | |||||||
| 
 | 
 | ||||||
| function normalizeIss(iss) { | function normalizeIss(iss) { | ||||||
|     if (!iss) { |     if (!iss) { | ||||||
|         throw Errors.TOKEN_NO_ISSUER(); |         throw Errors.NO_ISSUER(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // We definitely don't want false negatives stemming
 |     // We definitely don't want false negatives stemming
 | ||||||
| @ -217,7 +220,7 @@ keyfetch.jwt.decode = function (jwt) { | |||||||
|         obj.claims = JSON.parse(Buffer.from(obj.payload, "base64")); |         obj.claims = JSON.parse(Buffer.from(obj.payload, "base64")); | ||||||
|         return obj; |         return obj; | ||||||
|     } catch (e) { |     } catch (e) { | ||||||
|         var err = Errors.TOKEN_PARSE_ERROR(jwt); |         var err = Errors.PARSE_ERROR(jwt); | ||||||
|         err.details = e.message; |         err.details = e.message; | ||||||
|         throw err; |         throw err; | ||||||
|     } |     } | ||||||
| @ -227,7 +230,7 @@ keyfetch.jwt.verify = async function (jwt, opts) { | |||||||
|         opts = {}; |         opts = {}; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     var decoded; |     var jws; | ||||||
|     var exp; |     var exp; | ||||||
|     var nbf; |     var nbf; | ||||||
|     var active; |     var active; | ||||||
| @ -249,60 +252,68 @@ keyfetch.jwt.verify = async function (jwt, opts) { | |||||||
|     } |     } | ||||||
|     var claims = opts.claims || {}; |     var claims = opts.claims || {}; | ||||||
|     if (!jwt || "string" === typeof jwt) { |     if (!jwt || "string" === typeof jwt) { | ||||||
|         decoded = keyfetch.jwt.decode(jwt); |         jws = keyfetch.jwt.decode(jwt); | ||||||
|     } else { |     } else { | ||||||
|         decoded = jwt; |         jws = jwt; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (!decoded.claims.iss || !issuers.some(isTrustedIssuer(decoded.claims.iss))) { |     if (!jws.claims.iss || !issuers.some(isTrustedIssuer(jws.claims.iss))) { | ||||||
|         if (!(opts.jwk || opts.jwks)) { |         if (!(opts.jwk || opts.jwks)) { | ||||||
|             throw Errors.ISSUER_NOT_TRUSTED(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)
 | ||||||
|     if ( |     var failedClaims = Object.keys(claims) | ||||||
|         !Object.keys(claims).every(function (key) { |         .filter(function (key) { | ||||||
|             if (claims[key] === decoded.claims[key]) { |             if (claims[key] !== jws.claims[key]) { | ||||||
|                 return true; |                 return true; | ||||||
|             } |             } | ||||||
|         }) |         }) | ||||||
|     ) { |         .map(function (key) { | ||||||
|         throw Errors.CLAIMS_MISMATCH(Object.keys(claims)); |             return "jwt.claims." + key + " = " + JSON.stringify(jws.claims[key]); | ||||||
|  |         }); | ||||||
|  |     if (failedClaims.length) { | ||||||
|  |         throw Errors.FAILED_CLAIMS(failedClaims, Object.keys(claims)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     exp = decoded.claims.exp; |     exp = jws.claims.exp; | ||||||
|     if (exp && false !== opts.exp) { |     if (exp && false !== opts.exp) { | ||||||
|         now = Date.now(); |         now = Date.now(); | ||||||
|         // TODO document that opts.exp can be used as leeway? Or introduce opts.leeway?
 |         // 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); |         then = (opts.exp || 0) + parseInt(exp, 10); | ||||||
|         active = then - now / 1000 > 0; |         active = then - now / 1000 > 0; | ||||||
|         // expiration was on the token or, if not, such a token is not allowed
 |         // expiration was on the token or, if not, such a token is not allowed
 | ||||||
|         if (!active) { |         if (!active) { | ||||||
|             throw Errors.TOKEN_EXPIRED(exp); |             throw Errors.EXPIRED(exp); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     nbf = decoded.claims.nbf; |     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.TOKEN_INACTIVE(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 { | ||||||
| @ -317,10 +328,10 @@ keyfetch.jwt.verify = async function (jwt, opts) { | |||||||
|     return fetcher(iss).then(verifyAny); |     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 Errors.TOKEN_INVALID_SIGNATURE(); |         throw Errors.BAD_SIGNATURE(jws.protected + "." + jws.payload + "." + jws.signature); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     function verifyAny(hits) { |     function verifyAny(hits) { | ||||||
| @ -330,20 +341,19 @@ 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(decoded, hit)) { |                     if (true === keyfetch.jws.verify(jws, hit)) { | ||||||
|                         return true; |                         return true; | ||||||
|                     } |                     } | ||||||
|                     throw Errors.TOKEN_INVALID_SIGNATURE(); |                     throw Errors.BAD_SIGNATURE(); | ||||||
|                 } else { |  | ||||||
|                     if (true === keyfetch.jws.verify(decoded, hit)) { |  | ||||||
|                         return true; |  | ||||||
|                 } |                 } | ||||||
|  |                 if (true === keyfetch.jws.verify(jws, hit)) { | ||||||
|  |                     return true; | ||||||
|                 } |                 } | ||||||
|             }) |             }) | ||||||
|         ) { |         ) { | ||||||
|             return decoded; |             return jws; | ||||||
|         } |         } | ||||||
|         throw Errors.TOKEN_UNKNOWN_SIGNER(); |         throw Errors.JWK_NOT_FOUND_OLD(kid); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     function overrideLookup(jwks) { |     function overrideLookup(jwks) { | ||||||
|  | |||||||
							
								
								
									
										206
									
								
								lib/errors.js
									
									
									
									
									
								
							
							
						
						
									
										206
									
								
								lib/errors.js
									
									
									
									
									
								
							| @ -19,30 +19,47 @@ | |||||||
|  * }} opts |  * }} opts | ||||||
|  * @returns {AuthError} |  * @returns {AuthError} | ||||||
|  */ |  */ | ||||||
| function create(msg, { status = 401, code = "", details }) { | function create(old, msg, code, status, details) { | ||||||
|     /** @type AuthError */ |     /** @type AuthError */ | ||||||
|     //@ts-ignore
 |     //@ts-ignore
 | ||||||
|     var err = new Error(msg); |     let err = new Error(msg); | ||||||
|     err.message = err.message; |     err.message = msg; | ||||||
|     err.status = status; |     err._old_message = old; | ||||||
|     err.code = code; |     err.code = code; | ||||||
|  |     err.status = status; | ||||||
|     if (details) { |     if (details) { | ||||||
|         err.details = details; |         err.details = details; | ||||||
|     } |     } | ||||||
|     err.source = "keyfetch"; |     err.source = "keyfetch"; | ||||||
|  |     err.toJSON = toJSON; | ||||||
|  |     err.toString = toString; | ||||||
|     return err; |     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
 | // DEVELOPER_ERROR - a good token won't make a difference
 | ||||||
| var E_DEVELOPER = "DEVELOPER_ERROR"; | var E_DEVELOPER = "DEVELOPER_ERROR"; | ||||||
| 
 | 
 | ||||||
| // BAD_GATEWAY - there may be a temporary error fetching the public or or whatever
 | // BAD_GATEWAY - there may be a temporary error fetching the public or or whatever
 | ||||||
| var E_BAD_GATEWAY = "BAD_GATEWAY"; | var E_BAD_GATEWAY = "BAD_GATEWAY"; | ||||||
| 
 | 
 | ||||||
| // MALFORMED_TOKEN - the token could not be verified - not parsable, missing claims, etc
 | // MALFORMED_JWT - the token could not be verified - not parsable, missing claims, etc
 | ||||||
| var E_MALFORMED = "MALFORMED_JWT"; | var E_MALFORMED = "MALFORMED_JWT"; | ||||||
| 
 | 
 | ||||||
| // INVALID_TOKEN - the token's properties don't meet requirements - iss, claims, sig, exp
 | // INVALID_JWT - the token's properties don't meet requirements - iss, claims, sig, exp
 | ||||||
| var E_INVALID = "INVALID_JWT"; | var E_INVALID = "INVALID_JWT"; | ||||||
| 
 | 
 | ||||||
| module.exports = { | module.exports = { | ||||||
| @ -54,12 +71,20 @@ module.exports = { | |||||||
|      * @param {string} msg |      * @param {string} msg | ||||||
|      * @returns {AuthError} |      * @returns {AuthError} | ||||||
|      */ |      */ | ||||||
|     DEVELOPER_ERROR: function (msg) { |     DEVELOPER_ERROR: function (old, msg, details) { | ||||||
|         return create(msg, { status: 500, code: E_DEVELOPER }); |         return create(old, msg || old, E_DEVELOPER, 500, details); | ||||||
|     }, |     }, | ||||||
|     BAD_GATEWAY: function (/*err*/) { |     BAD_GATEWAY: function (err) { | ||||||
|         var msg = "The server encountered a network error or a bad gateway."; |         var msg = | ||||||
|         return create(msg, { status: 502, code: E_BAD_GATEWAY }); |             "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); | ||||||
|     }, |     }, | ||||||
| 
 | 
 | ||||||
|     //
 |     //
 | ||||||
| @ -71,25 +96,46 @@ module.exports = { | |||||||
|      * @returns {AuthError} |      * @returns {AuthError} | ||||||
|      */ |      */ | ||||||
|     INSECURE_ISSUER: function (iss) { |     INSECURE_ISSUER: function (iss) { | ||||||
|         var msg = |         var old = | ||||||
|             "'" + iss + "' is NOT secure. Set env 'KEYFETCH_ALLOW_INSECURE_HTTP=true' to allow for testing. (iss)"; |             "'" + iss + "' is NOT secure. Set env 'KEYFETCH_ALLOW_INSECURE_HTTP=true' to allow for testing. (iss)"; | ||||||
|         return create(msg, { status: 400, code: E_MALFORMED }); |         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 |      * @param {string} jwt | ||||||
|      * @returns {AuthError} |      * @returns {AuthError} | ||||||
|      */ |      */ | ||||||
|     TOKEN_PARSE_ERROR: function (jwt) { |     PARSE_ERROR: function (jwt) { | ||||||
|         var msg = "could not parse jwt: '" + jwt + "'"; |         var old = "could not parse jwt: '" + jwt + "'"; | ||||||
|         return create(msg, { status: 400, code: E_MALFORMED }); |         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 |      * @param {string} iss | ||||||
|      * @returns {AuthError} |      * @returns {AuthError} | ||||||
|      */ |      */ | ||||||
|     TOKEN_NO_ISSUER: function (iss) { |     NO_ISSUER: function (iss) { | ||||||
|         var msg = "'iss' is not defined"; |         var old = "'iss' is not defined"; | ||||||
|         return create(msg, { status: 400, code: E_MALFORMED }); |         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); | ||||||
|     }, |     }, | ||||||
| 
 | 
 | ||||||
|     //
 |     //
 | ||||||
| @ -100,77 +146,125 @@ module.exports = { | |||||||
|      * @param {number} exp |      * @param {number} exp | ||||||
|      * @returns {AuthError} |      * @returns {AuthError} | ||||||
|      */ |      */ | ||||||
|     TOKEN_EXPIRED: function (exp) { |     EXPIRED: function (exp) { | ||||||
|         //var msg = "The auth token is expired. (exp='" + exp + "')";
 |         var old = "token's 'exp' has passed or could not parsed: '" + exp + "'"; | ||||||
|         var msg = "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.";
 | ||||||
|         return create(msg, { code: E_INVALID }); |         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 |      * @param {number} nbf | ||||||
|      * @returns {AuthError} |      * @returns {AuthError} | ||||||
|      */ |      */ | ||||||
|     TOKEN_INACTIVE: function (nbf) { |     INACTIVE: function (nbf) { | ||||||
|         //var msg = "The auth token is not active yet. (nbf='" + nbf + "')";
 |         var old = "token's 'nbf' has not been reached or could not parsed: '" + nbf + "'"; | ||||||
|         var msg = "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."; | ||||||
|         return create(msg, { code: E_INVALID }); |         var details = ["jwt.claims.nbf = " + JSON.stringify(nbf)]; | ||||||
|  |         return create(old, msg, E_INVALID, 401, details); | ||||||
|     }, |     }, | ||||||
|     /** @returns {AuthError} */ |     /** @returns {AuthError} */ | ||||||
|     TOKEN_INVALID_SIGNATURE: function () { |     BAD_SIGNATURE: function (jwt) { | ||||||
|         //var msg = "The auth token is not properly signed and could not be verified.";
 |         var old = "token signature verification was unsuccessful"; | ||||||
|         var msg = "token signature verification was unsuccessful"; |         var msg = "The auth token did not pass verification because it is not properly signed."; | ||||||
|         return create(msg, { code: E_INVALID }); |         var details = ["jwt = " + JSON.stringify(jwt)]; | ||||||
|  |         return create(old, msg, E_INVALID, 401, details); | ||||||
|     }, |     }, | ||||||
|     /** @returns {AuthError} */ |     /** | ||||||
|     TOKEN_UNKNOWN_SIGNER: function () { |      * @param {string} kid | ||||||
|         var msg = "Retrieved a list of keys, but none of them matched the 'kid' (key id) of the token."; |      * @returns {AuthError} | ||||||
|         return create(msg, { code: E_INVALID }); |      */ | ||||||
|  |     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 |      * @param {string} id | ||||||
|      * @returns {AuthError} |      * @returns {AuthError} | ||||||
|      */ |      */ | ||||||
|     JWK_NOT_FOUND: function (id) { |     JWK_NOT_FOUND: function (id) { | ||||||
|         var msg = "No JWK found by kid or thumbprint '" + id + "'"; |         // TODO Distinguish between when it's a kid vs thumbprint.
 | ||||||
|         return create(msg, { code: E_INVALID }); |         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} */ |     /** @returns {AuthError} */ | ||||||
|     OIDC_CONFIG_NOT_FOUND: function () { |     NO_JWKWS_URI: function (url) { | ||||||
|         //var msg = "Failed to retrieve OpenID configuration for token issuer";
 |         var old = "Failed to retrieve openid configuration"; | ||||||
|         var msg = "Failed to retrieve openid configuration"; |         var msg = | ||||||
|         return create(msg, { code: E_INVALID }); |             '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 |      * @param {string} iss | ||||||
|      * @returns {AuthError} |      * @returns {AuthError} | ||||||
|      */ |      */ | ||||||
|     ISSUER_NOT_TRUSTED: function (iss) { |     UNKNOWN_ISSUER: function (iss) { | ||||||
|         var msg = "token was issued by an untrusted issuer: '" + iss + "'"; |         var old = "token was issued by an untrusted issuer: '" + iss + "'"; | ||||||
|         return create(msg, { code: E_INVALID }); |         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>} claimNames |      * @param {Array<string>} details | ||||||
|      * @returns {AuthError} |      * @returns {AuthError} | ||||||
|      */ |      */ | ||||||
|     CLAIMS_MISMATCH: function (claimNames) { |     FAILED_CLAIMS: function (details, claimNames) { | ||||||
|         var msg = "token did not match on one or more authorization claims: '" + claimNames + "'"; |         var old = "token did not match on one or more authorization claims: '" + claimNames + "'"; | ||||||
|         return create(msg, { code: E_INVALID }); |         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
 | // for README
 | ||||||
| if (require.main === module) { | if (require.main === module) { | ||||||
|     console.info("| Name | Status | Message (truncated) |"); |     let maxWidth = 54; | ||||||
|     console.info("| ---- | ------ | ------------------- |"); |     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) { |     Object.keys(module.exports).forEach(function (k) { | ||||||
|         //@ts-ignore
 |         //@ts-ignore
 | ||||||
|         var E = module.exports[k]; |         var E = module.exports[k]; | ||||||
|         var e = E(); |         var e = E("test"); | ||||||
|         var code = e.code; |         var code = e.code; | ||||||
|         var msg = e.message; |         var msg = e.message; | ||||||
|         if ("E_" + k !== e.code) { |         var hint = k.toLowerCase().replace(/_/g, " "); | ||||||
|             code = k; |         widths[0] = Math.max(widths[0], String(hint).length); | ||||||
|             msg = e.details || msg; |         widths[1] = Math.max(widths[1], String(code).length); | ||||||
|         } |         widths[2] = Math.max(widths[2], String(e.status).length); | ||||||
|         console.info(`| ${code} | ${e.status} | ${msg.slice(0, 45)}... |`); |         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)); | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										4
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										4
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -1,12 +1,12 @@ | |||||||
| { | { | ||||||
|     "name": "keyfetch", |     "name": "keyfetch", | ||||||
|     "version": "2.0.0", |     "version": "3.0.2", | ||||||
|     "lockfileVersion": 2, |     "lockfileVersion": 2, | ||||||
|     "requires": true, |     "requires": true, | ||||||
|     "packages": { |     "packages": { | ||||||
|         "": { |         "": { | ||||||
|             "name": "keyfetch", |             "name": "keyfetch", | ||||||
|             "version": "2.0.0", |             "version": "3.0.2", | ||||||
|             "license": "MPL-2.0", |             "license": "MPL-2.0", | ||||||
|             "dependencies": { |             "dependencies": { | ||||||
|                 "@root/request": "^1.8.0", |                 "@root/request": "^1.8.0", | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|     "name": "keyfetch", |     "name": "keyfetch", | ||||||
|     "version": "2.0.0", |     "version": "3.0.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", | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user