in middleware/errorHandler.ts [50:226]
export default function SiteErrorHandler(err, req, res, next) {
// CONSIDER: Let's eventually decouple all of our error message improvements to another area to keep the error handler intact.
const { applicationProfile, config } = getProviders(req);
var correlationId = req.correlationId;
var errorStatus = err ? (err.status || err.statusCode) : undefined;
// Per GitHub: https://developer.github.com/v3/oauth/#bad-verification-code
// When they offer a code that another GitHub auth server interprets as invalid,
// the app should retry.
if ((err.message === 'The code passed is incorrect or expired.' || (err.message === 'Failed to obtain access token' && err.oauthError.message === 'The code passed is incorrect or expired.')) && req.scrubbedUrl.startsWith('/auth/github/')) {
req.insights.trackMetric({ name: 'GitHubInvalidExpiredCodeRedirect', value: 1 });
req.insights.trackEvent({ name: 'GitHubInvalidExpiredCodeRetry' });
return res.redirect(req.scrubbedUrl === '/auth/github/callback/increased-scope?code=*****' ? '/auth/github/increased-scope' : '/auth/github');
}
const isGitHubAbuseRateLimit = err && err.message && err.message.includes && err.message.includes('#abuse-rate-limits');
if (isGitHubAbuseRateLimit) {
req.insights.trackMetric({ name: 'GitHubAbuseRateLimit', value: 1 });
}
if (err.message && err.message.includes && err.message.includes('ETIMEDOUT') && (err.message.includes('192.30.253.116') || err.message.includes('192.30.253.117'))) {
req.insights.trackMetric({ name: 'GitHubApiTimeout', value: 1 });
req.insights.trackEvent({ name: 'GitHubApiTimeout' });
err = wrapError(err, 'The GitHub API is temporarily down. Please try again soon.', false);
}
var primaryUserInstance = req.user ? req.user.github : null;
if (config) {
if (config.authentication.scheme !== 'github') {
primaryUserInstance = req.user ? req.user.azure : null;
}
var version = config && config.logging && config.logging.version ? config.logging.version : '?';
if (config.logging.errors && err.status !== 403 && err.skipLog !== true) {
let appSource = 'unknown';
if (process.argv.length > 1) {
appSource = process.argv.slice(1).join(' ');
}
const insightsProperties = {
url: req.scrubbedUrl || req.originalUrl || req.url,
entrypoint: appSource,
stk: undefined,
message: undefined,
};
if (req.insights && req.insights.trackException) {
for (let i = 0; err && i < exceptionFieldsOfInterest.length; i++) {
const key = exceptionFieldsOfInterest[i];
const value = err[key];
if (value && typeof value === 'number') {
insightsProperties[key] = value.toString();
} else if (value) {
insightsProperties[key] = value;
}
try {
// Try and store our own stack representation to compare it with
// zone aware error stacks that App Insights produces. This is an
// experiment at this time. (May 2018)
if (err) {
insightsProperties.stk = err.stack;
}
} catch (stackProblem) { /* ignore */ }
}
if (isGitHubAbuseRateLimit) {
insightsProperties.message = err.message;
req.insights.trackEvent({
name: 'GitHubAbuseRateLimitError',
properties: insightsProperties,
});
} else {
if (err && err['json']) {
// not tracking jsonErrors for now, they pollute app insights
} else {
req.insights.trackException({ exception: err, properties: insightsProperties });
}
}
}
}
}
if (err !== undefined && err.skipLog !== true) {
console.log('Error: ' + (err && err.message ? err.message : 'Error is undefined.'));
if (err.stack) {
console.error(err.stack);
}
if (err.innerError) {
var inner = err.innerError;
console.log('Inner: ' + inner.message);
if (inner.stack) {
console.log(inner.stack);
}
}
}
// Bubble OAuth errors to the forefront... this is the rate limit scenario.
if (err && err.oauthError && err.oauthError.statusCode && err.oauthError.statusCode && err.oauthError.data) {
var detailed = err.message;
err = err.oauthError;
err.status = err.statusCode;
var data = JSON.parse(err.data);
if (data && data.message) {
err.message = err.statusCode + ': ' + data.message;
} else {
err.message = err.statusCode + ' Unauthorized received. You may have exceeded your GitHub API rate limit or have an invalid auth token at this time.';
}
err.detailed = detailed;
}
// Don't leak the Redis connection information.
if (err && err.message && err.message.indexOf('Redis connection') >= 0 && err.message.indexOf('ETIMEDOUT')) {
err.message = 'The session store was temporarily unavailable. Please try again.';
}
if (res.headersSent) {
console.error('Headers were already sent.');
return next(err);
}
if (err && err.forceSignOut === true && req && req.logout) {
req.logout();
}
var safeMessage = redactRootPaths(err.message);
const defaultErrorTitle = err && err.skipOops ? 'FYI' : 'Oops';
const view = {
message: safeMessage,
encodedMessage: querystring.escape(safeMessage),
messageHasNonHtmlNewlines: containsNewlinesNotHtml(err),
serviceBanner: config && config.serviceMessage ? config.serviceMessage.banner : undefined,
detailed: err && err.detailed ? redactRootPaths(err.detailed) : undefined,
encodedDetailed: err && err.detailed ? querystring.escape(redactRootPaths(err.detailed)) : undefined,
errorFancyLink: err && err.fancyLink ? err.fancyLink : undefined,
errorFancySecondaryLink: err && err.fancySecondaryLink ? err.fancySecondaryLink : undefined,
errorStatus: errorStatus,
skipLog: err.skipLog,
skipOops: err && err.skipOops ? err.skipOops : false,
error: {},
title: err.title || (err.status === 404 ? 'Not Found' : defaultErrorTitle),
primaryUser: primaryUserInstance,
user: req.user,
config: config && config.obfuscatedConfig ? config.obfuscatedConfig : null,
};
// Depending on the library in use, we get everything from non-numeric textual status
// descriptions to status codes as strings and more. Set the status code found in
// the error if we have it.
var errStatusAsNumber = null;
if (err.status) {
errStatusAsNumber = parseInt(err.status);
}
let resCode = errStatusAsNumber || (err.status && typeof (err.status) === 'number' ? err.status : false) || err.statusCode || 500;
if (err && err.isAxiosError) {
const axiosError = err as AxiosError;
if (axiosError?.response?.status) {
resCode = axiosError.response.status;
}
}
res.status(resCode);
// Support JSON-based error display for the API route, showing just a small
// subset of typical view properties to share from the error instance.
if (err && err.json === true) {
const safeError = {
message: safeMessage,
correlationId: correlationId,
documentation_url: undefined,
};
if (err.documentation_url) {
safeError.documentation_url = err.documentation_url;
}
const fieldsOfInterest = ['serviceBanner', 'detailed'];
fieldsOfInterest.forEach((fieldName) => {
if (view[fieldName]) {
safeError[fieldName] = view[fieldName];
}
});
res.json(safeError);
} else {
if (!applicationProfile.customErrorHandlerRender) {
return res.render('error', view);
}
return applicationProfile.customErrorHandlerRender(view, err, req, res, next).then(ok => {
// done
}).catch(error => {
console.error(error);
res.end();
});
}
};