2021-10-21 00:12:06 +00:00
|
|
|
"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}
|
|
|
|
*/
|
2021-10-21 19:21:41 +00:00
|
|
|
function create(old, msg, code, status, details) {
|
2021-10-21 00:12:06 +00:00
|
|
|
/** @type AuthError */
|
|
|
|
//@ts-ignore
|
2021-10-21 19:28:20 +00:00
|
|
|
let err = new Error(msg);
|
|
|
|
err.message = msg;
|
|
|
|
err._old_message = old;
|
2021-10-21 00:12:06 +00:00
|
|
|
err.code = code;
|
2021-10-21 19:21:41 +00:00
|
|
|
err.status = status;
|
2021-10-21 00:12:06 +00:00
|
|
|
if (details) {
|
|
|
|
err.details = details;
|
|
|
|
}
|
|
|
|
err.source = "keyfetch";
|
2021-10-21 19:21:41 +00:00
|
|
|
err.toJSON = toJSON;
|
|
|
|
err.toString = toString;
|
2021-10-21 00:12:06 +00:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2021-10-21 19:21:41 +00:00
|
|
|
function toJSON() {
|
|
|
|
/*jshint validthis:true*/
|
|
|
|
return {
|
2021-10-21 19:37:12 +00:00
|
|
|
message: this.message,
|
2021-10-21 19:21:41 +00:00
|
|
|
status: this.status,
|
|
|
|
code: this.code,
|
|
|
|
details: this.details
|
|
|
|
};
|
|
|
|
}
|
|
|
|
function toString() {
|
|
|
|
/*jshint validthis:true*/
|
|
|
|
return this.stack + "\n" + JSON.stringify(this);
|
|
|
|
}
|
|
|
|
|
2021-10-21 00:12:06 +00:00
|
|
|
// 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";
|
|
|
|
|
2021-10-21 19:21:41 +00:00
|
|
|
// MALFORMED_JWT - the token could not be verified - not parsable, missing claims, etc
|
2021-10-21 00:12:06 +00:00
|
|
|
var E_MALFORMED = "MALFORMED_JWT";
|
|
|
|
|
2021-10-21 19:21:41 +00:00
|
|
|
// INVALID_JWT - the token's properties don't meet requirements - iss, claims, sig, exp
|
2021-10-21 00:12:06 +00:00
|
|
|
var E_INVALID = "INVALID_JWT";
|
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
//
|
|
|
|
// DEVELOPER_ERROR (dev / server)
|
|
|
|
//
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {string} msg
|
|
|
|
* @returns {AuthError}
|
|
|
|
*/
|
2021-10-21 19:21:41 +00:00
|
|
|
DEVELOPER_ERROR: function (old, msg, details) {
|
2021-10-21 19:37:12 +00:00
|
|
|
return create(old, msg || old, E_DEVELOPER, 500, details);
|
2021-10-21 00:12:06 +00:00
|
|
|
},
|
2021-10-21 19:21:41 +00:00
|
|
|
BAD_GATEWAY: function (err) {
|
|
|
|
var msg =
|
2021-10-21 20:19:23 +00:00
|
|
|
"The auth token could not be verified because our server encountered a network error (or a bad gateway) when connecting to its issuing server.";
|
2021-10-21 19:21:41 +00:00
|
|
|
var details = [];
|
|
|
|
if (err.message) {
|
|
|
|
details.push("error.message = " + err.message);
|
|
|
|
}
|
|
|
|
if (err.response && err.response.statusCode) {
|
|
|
|
details.push("response.statusCode = " + err.response.statusCode);
|
|
|
|
}
|
2021-10-21 19:37:12 +00:00
|
|
|
return create(msg, msg, E_BAD_GATEWAY, 502, details);
|
2021-10-21 00:12:06 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
//
|
|
|
|
// MALFORMED_TOKEN (dev / client)
|
|
|
|
//
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {string} iss
|
|
|
|
* @returns {AuthError}
|
|
|
|
*/
|
|
|
|
INSECURE_ISSUER: function (iss) {
|
2021-10-21 19:21:41 +00:00
|
|
|
var old =
|
2021-10-21 00:12:06 +00:00
|
|
|
"'" + iss + "' is NOT secure. Set env 'KEYFETCH_ALLOW_INSECURE_HTTP=true' to allow for testing. (iss)";
|
2021-10-21 19:21:41 +00:00
|
|
|
var details = [
|
|
|
|
"jwt.claims.iss = " + JSON.stringify(iss),
|
|
|
|
"DEBUG: Set ENV 'KEYFETCH_ALLOW_INSECURE_HTTP=true' to allow insecure issuers (for testing)."
|
|
|
|
];
|
|
|
|
var msg =
|
2021-10-21 20:19:23 +00:00
|
|
|
'The auth token could not be verified because our server could connect to its issuing server ("iss") securely.';
|
2021-10-21 19:21:41 +00:00
|
|
|
return create(old, msg, E_MALFORMED, 400, details);
|
2021-10-21 00:12:06 +00:00
|
|
|
},
|
|
|
|
/**
|
|
|
|
* @param {string} jwt
|
|
|
|
* @returns {AuthError}
|
|
|
|
*/
|
2021-10-21 19:21:41 +00:00
|
|
|
PARSE_ERROR: function (jwt) {
|
|
|
|
var old = "could not parse jwt: '" + jwt + "'";
|
2021-10-21 20:19:23 +00:00
|
|
|
var msg = "The auth token could not be verified because it is malformed.";
|
2021-10-21 19:21:41 +00:00
|
|
|
var details = ["jwt = " + JSON.stringify(jwt)];
|
|
|
|
return create(old, msg, E_MALFORMED, 400, details);
|
2021-10-21 00:12:06 +00:00
|
|
|
},
|
|
|
|
/**
|
|
|
|
* @param {string} iss
|
|
|
|
* @returns {AuthError}
|
|
|
|
*/
|
2021-10-21 19:21:41 +00:00
|
|
|
NO_ISSUER: function (iss) {
|
|
|
|
var old = "'iss' is not defined";
|
2021-10-21 20:19:23 +00:00
|
|
|
var msg = 'The auth token could not be verified because it doesn\'t specify an issuer ("iss").';
|
2021-10-21 19:21:41 +00:00
|
|
|
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);
|
2021-10-21 00:12:06 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
//
|
|
|
|
// INVALID_TOKEN (dev / client)
|
|
|
|
//
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {number} exp
|
|
|
|
* @returns {AuthError}
|
|
|
|
*/
|
2021-10-21 19:21:41 +00:00
|
|
|
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);
|
2021-10-21 00:12:06 +00:00
|
|
|
},
|
|
|
|
/**
|
|
|
|
* @param {number} nbf
|
|
|
|
* @returns {AuthError}
|
|
|
|
*/
|
2021-10-21 19:21:41 +00:00
|
|
|
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);
|
2021-10-21 00:12:06 +00:00
|
|
|
},
|
|
|
|
/** @returns {AuthError} */
|
2021-10-21 19:21:41 +00:00
|
|
|
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);
|
2021-10-21 00:12:06 +00:00
|
|
|
},
|
2021-10-21 19:21:41 +00:00
|
|
|
/**
|
|
|
|
* @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);
|
2021-10-21 00:12:06 +00:00
|
|
|
},
|
|
|
|
/**
|
|
|
|
* @param {string} id
|
|
|
|
* @returns {AuthError}
|
|
|
|
*/
|
|
|
|
JWK_NOT_FOUND: function (id) {
|
2021-10-21 19:21:41 +00:00
|
|
|
// 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);
|
2021-10-21 00:12:06 +00:00
|
|
|
},
|
|
|
|
/** @returns {AuthError} */
|
2021-10-21 19:21:41 +00:00
|
|
|
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);
|
2021-10-21 00:12:06 +00:00
|
|
|
},
|
|
|
|
/**
|
|
|
|
* @param {string} iss
|
|
|
|
* @returns {AuthError}
|
|
|
|
*/
|
2021-10-21 19:21:41 +00:00
|
|
|
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);
|
2021-10-21 00:12:06 +00:00
|
|
|
},
|
|
|
|
/**
|
2021-10-21 19:21:41 +00:00
|
|
|
* @param {Array<string>} details
|
2021-10-21 00:12:06 +00:00
|
|
|
* @returns {AuthError}
|
|
|
|
*/
|
2021-10-21 19:21:41 +00:00
|
|
|
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);
|
2021-10-21 00:12:06 +00:00
|
|
|
}
|
|
|
|
};
|
2021-10-21 19:21:41 +00:00
|
|
|
var Errors = module.exports;
|
2021-10-21 00:12:06 +00:00
|
|
|
|
|
|
|
// for README
|
|
|
|
if (require.main === module) {
|
2021-10-21 20:19:23 +00:00
|
|
|
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 = [];
|
2021-10-21 00:12:06 +00:00
|
|
|
Object.keys(module.exports).forEach(function (k) {
|
|
|
|
//@ts-ignore
|
|
|
|
var E = module.exports[k];
|
2021-10-21 19:21:41 +00:00
|
|
|
var e = E("test");
|
2021-10-21 00:12:06 +00:00
|
|
|
var code = e.code;
|
2021-10-21 20:19:23 +00:00
|
|
|
var msg = e.message;
|
2021-10-21 19:21:41 +00:00
|
|
|
var hint = k.toLowerCase().replace(/_/g, " ");
|
2021-10-21 20:19:23 +00:00
|
|
|
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]} |`);
|
|
|
|
}
|
2021-10-21 00:12:06 +00:00
|
|
|
});
|
2021-10-21 20:19:23 +00:00
|
|
|
console.log();
|
2021-10-21 19:21:41 +00:00
|
|
|
console.log(Errors.MALFORMED_EXP());
|
2021-10-21 20:19:23 +00:00
|
|
|
console.log();
|
2021-10-21 19:21:41 +00:00
|
|
|
console.log(JSON.stringify(Errors.MALFORMED_EXP(), null, 2));
|
2021-10-21 00:12:06 +00:00
|
|
|
}
|