Compare commits
9 Commits
523a4f0d1a
...
a84e833571
Author | SHA1 | Date |
---|---|---|
AJ ONeal | a84e833571 | |
AJ ONeal | 076246e4d0 | |
AJ ONeal | 4c85fc4009 | |
AJ ONeal | d5647ea905 | |
AJ ONeal | 2ea44e3a46 | |
AJ ONeal | 5ef53ecb23 | |
AJ ONeal | 604b42c7ef | |
AJ ONeal | c57a08f0cd | |
AJ ONeal | a3539b0941 |
|
@ -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).
|
||||||
|
|
76
keyfetch.js
76
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;
|
|
||||||
}
|
|
||||||
throw Errors.TOKEN_INVALID_SIGNATURE();
|
|
||||||
} else {
|
|
||||||
if (true === keyfetch.jws.verify(decoded, hit)) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
throw Errors.BAD_SIGNATURE();
|
||||||
|
}
|
||||||
|
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));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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…
Reference in New Issue