Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions backend/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import bodyParser from "body-parser";
import compression from "compression";
import express from "express";
import fileUpload from "express-fileupload";
import { isDebugMode } from "./lib/config.js";
import { isDebugMode, isVerboseErrors } from "./lib/config.js";
import cors from "./lib/express/cors.js";
import jwt from "./lib/express/jwt.js";
import { debug, express as logger } from "./logger.js";
Expand Down Expand Up @@ -58,20 +58,23 @@ app.use(jwt());
app.use("/", mainRoutes);

// production error handler
// no stacktraces leaked to user
app.use((err, req, res, _) => {
// non-public errors are masked as "Internal Error" unless VERBOSE_ERRORS is set
// stack traces stay gated behind DEBUG
app.use((err, _req, res, _next) => {
const exposeMessage = err.public || isVerboseErrors();

const payload = {
error: {
code: err.status || 500,
message: err.public ? err.message : "Internal Error",
message: exposeMessage ? err.message : "Internal Error",
},
};

if (typeof err.message_i18n !== "undefined") {
payload.error.message_i18n = err.message_i18n;
}

if (isDebugMode() || (req.baseUrl + req.path).includes("nginx/certificates")) {
if (isDebugMode()) {
payload.debug = {
stack: typeof err.stack !== "undefined" && err.stack ? err.stack.split("\n") : null,
previous: err.previous,
Expand Down
7 changes: 7 additions & 0 deletions backend/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import app from "./app.js";
import internalCertificate from "./internal/certificate.js";
import internalIpRanges from "./internal/ip_ranges.js";
import { isVerboseErrors } from "./lib/config.js";
import { global as logger } from "./logger.js";
import { migrateUp } from "./migrate.js";
import { getCompiledSchema } from "./schema/index.js";
Expand Down Expand Up @@ -31,6 +32,12 @@ async function appStart() {
const server = app.listen(3000, () => {
logger.info(`Backend PID ${process.pid} listening on port 3000 ...`);

if (isVerboseErrors()) {
logger.warn(
"VERBOSE_ERRORS is enabled, internal error messages will be returned to API clients",
);
}

process.on("SIGTERM", () => {
logger.info(`PID ${process.pid} received SIGTERM`);
server.close(() => {
Expand Down
9 changes: 8 additions & 1 deletion backend/lib/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,13 @@ const isPostgres = () => {
*/
const isDebugMode = () => !!process.env.DEBUG;

/**
* Should non-public error messages be returned to API clients?
*
* @returns {boolean}
*/
const isVerboseErrors = () => !!process.env.VERBOSE_ERRORS;

/**
* Are we running in CI?
*
Expand Down Expand Up @@ -259,4 +266,4 @@ const useLetsencryptServer = () => {
return null;
};

export { isCI, configHas, configGet, isSqlite, isMysql, isPostgres, isDebugMode, getPrivateKey, getPublicKey, useLetsencryptStaging, useLetsencryptServer };
export { isCI, configHas, configGet, isSqlite, isMysql, isPostgres, isDebugMode, isVerboseErrors, getPrivateKey, getPublicKey, useLetsencryptStaging, useLetsencryptServer };
9 changes: 9 additions & 0 deletions docs/src/advanced-config/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,3 +248,12 @@ On startup, we generate a resolvers directive for Nginx unless this is defined:

In this configuration, all DNS queries performed by Nginx will fall to the `/etc/hosts` file
and then the `/etc/resolv.conf`.

## Verbose Error Messages

By default the API returns a generic `Internal Error` and the real cause stays in the container logs. Set `VERBOSE_ERRORS` to return the real message in the API response (and the UI):

```yml
environment:
VERBOSE_ERRORS: 'true'
```