add failed authentication attempt logger
When `isAuthed()` is called and the password cookie is not what we expected, the failed login attempt is logged with the provided password, remote address and user agent. To allow for logging failed attempts with a reverse proxy, the `--trust-proxy` argument has been added to trust the `X-Forwarded-For` header. This implementation of an `X-Forwarded-For` parser uses the last value in the list, therefore only trusting the nearest proxy.
This commit is contained in:
parent
242bb6ffa2
commit
a65773338c
|
@ -38,6 +38,7 @@ commander.version(process.env.VERSION || "development")
|
|||
.option("-P, --password <value>", "DEPRECATED: Use the PASSWORD environment variable instead. Specify a password for authentication.")
|
||||
.option("--disable-telemetry", "Disables ALL telemetry.", false)
|
||||
.option("--socket <value>", "Listen on a UNIX socket. Host and port will be ignored when set.")
|
||||
.option("--trust-proxy", "Trust the X-Forwarded-For header, useful when using a reverse proxy.", false)
|
||||
.option("--install-extension <value>", "Install an extension by its ID.")
|
||||
.option("--bootstrap-fork <name>", "Used for development. Never set.")
|
||||
.option("--extra-args <args>", "Used for development. Never set.")
|
||||
|
@ -74,6 +75,7 @@ const bold = (text: string | number): string | number => {
|
|||
readonly cert?: string;
|
||||
readonly certKey?: string;
|
||||
readonly socket?: string;
|
||||
readonly trustProxy?: boolean;
|
||||
|
||||
readonly installExtension?: string;
|
||||
|
||||
|
@ -273,6 +275,7 @@ const bold = (text: string | number): string | number => {
|
|||
},
|
||||
},
|
||||
password,
|
||||
trustProxy: options.trustProxy,
|
||||
httpsOptions: hasCustomHttps ? {
|
||||
key: certKeyData,
|
||||
cert: certData,
|
||||
|
|
|
@ -31,6 +31,7 @@ interface CreateAppOptions {
|
|||
httpsOptions?: https.ServerOptions;
|
||||
allowHttp?: boolean;
|
||||
bypassAuth?: boolean;
|
||||
trustProxy?: boolean;
|
||||
}
|
||||
|
||||
export const createApp = async (options: CreateAppOptions): Promise<{
|
||||
|
@ -62,6 +63,21 @@ export const createApp = async (options: CreateAppOptions): Promise<{
|
|||
return true;
|
||||
};
|
||||
|
||||
const remoteAddress = (req: http.IncomingMessage): string | void => {
|
||||
let xForwardedFor = req.headers["x-forwarded-for"];
|
||||
if (Array.isArray(xForwardedFor)) {
|
||||
xForwardedFor = xForwardedFor.join(", ");
|
||||
}
|
||||
|
||||
if (options.trustProxy && xForwardedFor !== undefined) {
|
||||
const addresses = xForwardedFor.split(",").map(s => s.trim());
|
||||
|
||||
return addresses.pop();
|
||||
}
|
||||
|
||||
return req.socket.remoteAddress;
|
||||
};
|
||||
|
||||
const isAuthed = (req: http.IncomingMessage): boolean => {
|
||||
try {
|
||||
if (!options.password || options.bypassAuth) {
|
||||
|
@ -70,7 +86,20 @@ export const createApp = async (options: CreateAppOptions): Promise<{
|
|||
|
||||
// Try/catch placed here just in case
|
||||
const cookies = parseCookies(req);
|
||||
if (cookies.password && safeCompare(cookies.password, options.password)) {
|
||||
if (cookies.password) {
|
||||
if (!safeCompare(cookies.password, options.password)) {
|
||||
let userAgent = req.headers["user-agent"];
|
||||
if (Array.isArray(userAgent)) {
|
||||
userAgent = userAgent.join(", ");
|
||||
}
|
||||
logger.info("Failed login attempt",
|
||||
field("password", cookies.password),
|
||||
field("remote_address", remoteAddress(req)),
|
||||
field("user_agent", userAgent));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} catch (ex) {
|
||||
|
@ -214,7 +243,9 @@ export const createApp = async (options: CreateAppOptions): Promise<{
|
|||
const staticGzip = expressStaticGzip(path.join(baseDir, "build/web"));
|
||||
|
||||
app.use((req, res, next) => {
|
||||
logger.trace(`\u001B[1m${req.method} ${res.statusCode} \u001B[0m${req.originalUrl}`, field("host", req.hostname), field("ip", req.ip));
|
||||
logger.trace(`\u001B[1m${req.method} ${res.statusCode} \u001B[0m${req.originalUrl}`,
|
||||
field("host", req.hostname),
|
||||
field("remote_address", remoteAddress(req)));
|
||||
|
||||
// Force HTTPS unless allowing HTTP.
|
||||
if (!isEncrypted(req.socket) && !options.allowHttp) {
|
||||
|
|
Loading…
Reference in New Issue