Selaa lähdekoodia

add failed auth logging

Milo Schwartz 6 kuukautta sitten
vanhempi
commit
0bd8217d9e

+ 2 - 1
server/lib/config.ts

@@ -40,7 +40,8 @@ const configSchema = z.object({
             .pipe(hostnameSchema)
             .transform((url) => url.toLowerCase()),
         log_level: z.enum(["debug", "info", "warn", "error"]),
-        save_logs: z.boolean()
+        save_logs: z.boolean(),
+        log_failed_attempts: z.boolean().optional(),
     }),
     server: z.object({
         external_port: portSchema

+ 7 - 0
server/middlewares/verifyUser.ts

@@ -8,6 +8,7 @@ import HttpCode from "@server/types/HttpCode";
 import config from "@server/lib/config";
 import { verifySession } from "@server/auth/sessions/verifySession";
 import { unauthorized } from "@server/auth/unauthorizedResponse";
+import logger from "@server/logger";
 
 export const verifySessionUserMiddleware = async (
     req: any,
@@ -16,6 +17,9 @@ export const verifySessionUserMiddleware = async (
 ) => {
     const { session, user } = await verifySession(req);
     if (!session || !user) {
+        if (config.getRawConfig().app.log_failed_attempts) {
+            logger.info(`User session not found. IP: ${req.ip}.`);
+        }
         return next(unauthorized());
     }
 
@@ -25,6 +29,9 @@ export const verifySessionUserMiddleware = async (
         .where(eq(users.userId, user.userId));
 
     if (!existingUser || !existingUser[0]) {
+        if (config.getRawConfig().app.log_failed_attempts) {
+            logger.info(`User session not found. IP: ${req.ip}.`);
+        }
         return next(
             createHttpError(HttpCode.BAD_REQUEST, "User does not exist")
         );

+ 5 - 0
server/routers/auth/disable2fa.ts

@@ -79,6 +79,11 @@ export async function disable2fa(
         );
 
         if (!validOTP) {
+            if (config.getRawConfig().app.log_failed_attempts) {
+                logger.info(
+                    `Two-factor authentication code is incorrect. Email: ${user.email}. IP: ${req.ip}.`
+                );
+            }
             return next(
                 createHttpError(
                     HttpCode.BAD_REQUEST,

+ 15 - 0
server/routers/auth/login.ts

@@ -71,6 +71,11 @@ export async function login(
             .from(users)
             .where(eq(users.email, email));
         if (!existingUserRes || !existingUserRes.length) {
+            if (config.getRawConfig().app.log_failed_attempts) {
+                logger.info(
+                    `Username or password incorrect. Email: ${email}. IP: ${req.ip}.`
+                );
+            }
             return next(
                 createHttpError(
                     HttpCode.BAD_REQUEST,
@@ -86,6 +91,11 @@ export async function login(
             existingUser.passwordHash
         );
         if (!validPassword) {
+            if (config.getRawConfig().app.log_failed_attempts) {
+                logger.info(
+                    `Username or password incorrect. Email: ${email}. IP: ${req.ip}.`
+                );
+            }
             return next(
                 createHttpError(
                     HttpCode.BAD_REQUEST,
@@ -112,6 +122,11 @@ export async function login(
             );
 
             if (!validOTP) {
+                if (config.getRawConfig().app.log_failed_attempts) {
+                    logger.info(
+                        `Two-factor code incorrect. Email: ${email}. IP: ${req.ip}.`
+                    );
+                }
                 return next(
                     createHttpError(
                         HttpCode.BAD_REQUEST,

+ 6 - 0
server/routers/auth/logout.ts

@@ -8,6 +8,7 @@ import {
     invalidateSession
 } from "@server/auth/sessions/app";
 import { verifySession } from "@server/auth/sessions/verifySession";
+import config from "@server/lib/config";
 
 export async function logout(
     req: Request,
@@ -16,6 +17,11 @@ export async function logout(
 ): Promise<any> {
     const { user, session } = await verifySession(req);
     if (!user || !session) {
+        if (config.getRawConfig().app.log_failed_attempts) {
+            logger.info(
+                `Log out failed because missing or invalid session. IP: ${req.ip}.`
+            );
+        }
         return next(
             createHttpError(
                 HttpCode.BAD_REQUEST,

+ 15 - 0
server/routers/auth/resetPassword.ts

@@ -60,6 +60,11 @@ export async function resetPassword(
             .where(eq(passwordResetTokens.email, email));
 
         if (!resetRequest || !resetRequest.length) {
+            if (config.getRawConfig().app.log_failed_attempts) {
+                logger.info(
+                    `Password reset code is incorrect. Email: ${email}. IP: ${req.ip}.`
+                );
+            }
             return next(
                 createHttpError(
                     HttpCode.BAD_REQUEST,
@@ -109,6 +114,11 @@ export async function resetPassword(
             );
 
             if (!validOTP) {
+                if (config.getRawConfig().app.log_failed_attempts) {
+                    logger.info(
+                        `Two-factor authentication code is incorrect. Email: ${email}. IP: ${req.ip}.`
+                    );
+                }
                 return next(
                     createHttpError(
                         HttpCode.BAD_REQUEST,
@@ -124,6 +134,11 @@ export async function resetPassword(
         );
 
         if (!isTokenValid) {
+            if (config.getRawConfig().app.log_failed_attempts) {
+                logger.info(
+                    `Password reset code is incorrect. Email: ${email}. IP: ${req.ip}.`
+                );
+            }
             return next(
                 createHttpError(
                     HttpCode.BAD_REQUEST,

+ 19 - 1
server/routers/auth/signup.ts

@@ -23,7 +23,10 @@ import { checkValidInvite } from "@server/auth/checkValidInvite";
 import { passwordSchema } from "@server/auth/passwordSchema";
 
 export const signupBodySchema = z.object({
-    email: z.string().email().transform((v) => v.toLowerCase()),
+    email: z
+        .string()
+        .email()
+        .transform((v) => v.toLowerCase()),
     password: passwordSchema,
     inviteToken: z.string().optional(),
     inviteId: z.string().optional()
@@ -60,6 +63,11 @@ export async function signup(
 
     if (config.getRawConfig().flags?.disable_signup_without_invite) {
         if (!inviteToken || !inviteId) {
+            if (config.getRawConfig().app.log_failed_attempts) {
+                logger.info(
+                    `Signup blocked without invite. Email: ${email}. IP: ${req.ip}.`
+                );
+            }
             return next(
                 createHttpError(
                     HttpCode.BAD_REQUEST,
@@ -84,6 +92,11 @@ export async function signup(
         }
 
         if (existingInvite.email !== email) {
+            if (config.getRawConfig().app.log_failed_attempts) {
+                logger.info(
+                    `User attempted to use an invite for another user. Email: ${email}. IP: ${req.ip}.`
+                );
+            }
             return next(
                 createHttpError(
                     HttpCode.BAD_REQUEST,
@@ -185,6 +198,11 @@ export async function signup(
         });
     } catch (e) {
         if (e instanceof SqliteError && e.code === "SQLITE_CONSTRAINT_UNIQUE") {
+            if (config.getRawConfig().app.log_failed_attempts) {
+                logger.info(
+                    `Account already exists with that email. Email: ${email}. IP: ${req.ip}.`
+                );
+            }
             return next(
                 createHttpError(
                     HttpCode.BAD_REQUEST,

+ 5 - 0
server/routers/auth/verifyEmail.ts

@@ -75,6 +75,11 @@ export async function verifyEmail(
                     .where(eq(users.userId, user.userId));
             });
         } else {
+            if (config.getRawConfig().app.log_failed_attempts) {
+                logger.info(
+                    `Email verification code incorrect. Email: ${user.email}. IP: ${req.ip}.`
+                );
+            }
             return next(
                 createHttpError(
                     HttpCode.BAD_REQUEST,

+ 5 - 0
server/routers/auth/verifyTotp.ts

@@ -96,6 +96,11 @@ export async function verifyTotp(
         }
 
         if (!valid) {
+            if (config.getRawConfig().app.log_failed_attempts) {
+                logger.info(
+                    `Two-factor authentication code is incorrect. Email: ${user.email}. IP: ${req.ip}.`
+                );
+            }
             return next(
                 createHttpError(
                     HttpCode.BAD_REQUEST,

+ 15 - 2
server/routers/badger/exchangeSession.ts

@@ -20,7 +20,8 @@ import { response } from "@server/lib";
 
 const exchangeSessionBodySchema = z.object({
     requestToken: z.string(),
-    host: z.string()
+    host: z.string(),
+    requestIp: z.string().optional()
 });
 
 export type ExchangeSessionBodySchema = z.infer<
@@ -51,7 +52,9 @@ export async function exchangeSession(
     }
 
     try {
-        const { requestToken, host } = parsedBody.data;
+        const { requestToken, host, requestIp } = parsedBody.data;
+
+        const clientIp = requestIp?.split(":")[0];
 
         const [resource] = await db
             .select()
@@ -75,12 +78,22 @@ export async function exchangeSession(
             );
 
         if (!requestSession) {
+            if (config.getRawConfig().app.log_failed_attempts) {
+                logger.info(
+                    `Exchange token is invalid. Resource ID: ${resource.resourceId}. IP: ${clientIp}.`
+                );
+            }
             return next(
                 createHttpError(HttpCode.UNAUTHORIZED, "Invalid request token")
             );
         }
 
         if (!requestSession.isRequestToken) {
+            if (config.getRawConfig().app.log_failed_attempts) {
+                logger.info(
+                    `Exchange token is invalid. Resource ID: ${resource.resourceId}. IP: ${clientIp}.`
+                );
+            }
             return next(
                 createHttpError(HttpCode.UNAUTHORIZED, "Invalid request token")
             );

+ 29 - 1
server/routers/badger/verifySession.ts

@@ -42,7 +42,8 @@ const verifyResourceSessionSchema = z.object({
     path: z.string(),
     method: z.string(),
     accessToken: z.string().optional(),
-    tls: z.boolean()
+    tls: z.boolean(),
+    requestIp: z.string().optional()
 });
 
 export type VerifyResourceSessionSchema = z.infer<
@@ -77,9 +78,12 @@ export async function verifyResourceSession(
             sessions,
             host,
             originalRequestURL,
+            requestIp,
             accessToken: token
         } = parsedBody.data;
 
+        const clientIp = requestIp?.split(":")[0];
+
         const resourceCacheKey = `resource:${host}`;
         let resourceData:
             | {
@@ -160,6 +164,14 @@ export async function verifyResourceSession(
                 logger.debug("Access token invalid: " + error);
             }
 
+            if (!valid) {
+                if (config.getRawConfig().app.log_failed_attempts) {
+                    logger.info(
+                        `Resource access token is invalid. Resource ID: ${resource.resourceId}. IP: ${clientIp}.`
+                    );
+                }
+            }
+
             if (valid && tokenItem) {
                 validAccessToken = tokenItem;
 
@@ -174,6 +186,11 @@ export async function verifyResourceSession(
         }
 
         if (!sessions) {
+            if (config.getRawConfig().app.log_failed_attempts) {
+                logger.info(
+                    `Missing resource sessions. Resource ID: ${resource.resourceId}. IP: ${clientIp}.`
+                );
+            }
             return notAllowed(res);
         }
 
@@ -200,6 +217,11 @@ export async function verifyResourceSession(
                 logger.debug(
                     "Resource not allowed because session is a temporary request token"
                 );
+                if (config.getRawConfig().app.log_failed_attempts) {
+                    logger.info(
+                        `Resource session is an exchange token. Resource ID: ${resource.resourceId}. IP: ${clientIp}.`
+                    );
+                }
                 return notAllowed(res);
             }
 
@@ -271,6 +293,12 @@ export async function verifyResourceSession(
         }
 
         logger.debug("No more auth to check, resource not allowed");
+
+        if (config.getRawConfig().app.log_failed_attempts) {
+            logger.info(
+                `Resource access not allowed. Resource ID: ${resource.resourceId}. IP: ${clientIp}.`
+            );
+        }
         return notAllowed(res, redirectUrl);
     } catch (e) {
         console.error(e);

+ 17 - 4
server/routers/newt/getToken.ts

@@ -1,6 +1,4 @@
-import {
-    generateSessionToken,
-} from "@server/auth/sessions/app";
+import { generateSessionToken } from "@server/auth/sessions/app";
 import db from "@server/db";
 import { newts } from "@server/db/schema";
 import HttpCode from "@server/types/HttpCode";
@@ -10,8 +8,13 @@ import { NextFunction, Request, Response } from "express";
 import createHttpError from "http-errors";
 import { z } from "zod";
 import { fromError } from "zod-validation-error";
-import { createNewtSession, validateNewtSessionToken } from "@server/auth/sessions/newt";
+import {
+    createNewtSession,
+    validateNewtSessionToken
+} from "@server/auth/sessions/newt";
 import { verifyPassword } from "@server/auth/password";
+import logger from "@server/logger";
+import config from "@server/lib/config";
 
 export const newtGetTokenBodySchema = z.object({
     newtId: z.string(),
@@ -43,6 +46,11 @@ export async function getToken(
         if (token) {
             const { session, newt } = await validateNewtSessionToken(token);
             if (session) {
+                if (config.getRawConfig().app.log_failed_attempts) {
+                    logger.info(
+                        `Newt session already valid. Newt ID: ${newtId}. IP: ${req.ip}.`
+                    );
+                }
                 return response<null>(res, {
                     data: null,
                     success: true,
@@ -73,6 +81,11 @@ export async function getToken(
             existingNewt.secretHash
         );
         if (!validSecret) {
+            if (config.getRawConfig().app.log_failed_attempts) {
+                logger.info(
+                    `Newt id or secret is incorrect. Newt: ID ${newtId}. IP: ${req.ip}.`
+                );
+            }
             return next(
                 createHttpError(HttpCode.BAD_REQUEST, "Secret is incorrect")
             );

+ 7 - 3
server/routers/resource/authWithAccessToken.ts

@@ -8,11 +8,10 @@ import { NextFunction, Request, Response } from "express";
 import createHttpError from "http-errors";
 import { z } from "zod";
 import { fromError } from "zod-validation-error";
-import {
-    createResourceSession,
-} from "@server/auth/sessions/resource";
+import { createResourceSession } from "@server/auth/sessions/resource";
 import logger from "@server/logger";
 import { verifyResourceAccessToken } from "@server/auth/verifyResourceAccessToken";
+import config from "@server/lib/config";
 
 const authWithAccessTokenBodySchema = z
     .object({
@@ -84,6 +83,11 @@ export async function authWithAccessToken(
         });
 
         if (!valid) {
+            if (config.getRawConfig().app.log_failed_attempts) {
+                logger.info(
+                    `Resource access token invalid. Resource ID: ${resource.resourceId}. IP: ${req.ip}.`
+                );
+            }
             return next(
                 createHttpError(
                     HttpCode.UNAUTHORIZED,

+ 8 - 4
server/routers/resource/authWithPassword.ts

@@ -9,11 +9,10 @@ import { NextFunction, Request, Response } from "express";
 import createHttpError from "http-errors";
 import { z } from "zod";
 import { fromError } from "zod-validation-error";
-import {
-    createResourceSession,
-} from "@server/auth/sessions/resource";
+import { createResourceSession } from "@server/auth/sessions/resource";
 import logger from "@server/logger";
 import { verifyPassword } from "@server/auth/password";
+import config from "@server/lib/config";
 
 export const authWithPasswordBodySchema = z
     .object({
@@ -82,7 +81,7 @@ export async function authWithPassword(
 
         if (!org) {
             return next(
-                createHttpError(HttpCode.BAD_REQUEST, "Resource does not exist")
+                createHttpError(HttpCode.BAD_REQUEST, "Org does not exist")
             );
         }
 
@@ -109,6 +108,11 @@ export async function authWithPassword(
             definedPassword.passwordHash
         );
         if (!validPassword) {
+            if (config.getRawConfig().app.log_failed_attempts) {
+                logger.info(
+                    `Resource password incorrect. Resource ID: ${resource.resourceId}. IP: ${req.ip}.`
+                );
+            }
             return next(
                 createHttpError(HttpCode.UNAUTHORIZED, "Incorrect password")
             );

+ 8 - 8
server/routers/resource/authWithPincode.ts

@@ -1,10 +1,6 @@
 import { generateSessionToken } from "@server/auth/sessions/app";
 import db from "@server/db";
-import {
-    orgs,
-    resourcePincode,
-    resources,
-} from "@server/db/schema";
+import { orgs, resourcePincode, resources } from "@server/db/schema";
 import HttpCode from "@server/types/HttpCode";
 import response from "@server/lib/response";
 import { eq } from "drizzle-orm";
@@ -12,11 +8,10 @@ import { NextFunction, Request, Response } from "express";
 import createHttpError from "http-errors";
 import { z } from "zod";
 import { fromError } from "zod-validation-error";
-import {
-    createResourceSession,
-} from "@server/auth/sessions/resource";
+import { createResourceSession } from "@server/auth/sessions/resource";
 import logger from "@server/logger";
 import { verifyPassword } from "@server/auth/password";
+import config from "@server/lib/config";
 
 export const authWithPincodeBodySchema = z
     .object({
@@ -112,6 +107,11 @@ export async function authWithPincode(
             definedPincode.pincodeHash
         );
         if (!validPincode) {
+            if (config.getRawConfig().app.log_failed_attempts) {
+                logger.info(
+                    `Resource pin code incorrect. Resource ID: ${resource.resourceId}. IP: ${req.ip}.`
+                );
+            }
             return next(
                 createHttpError(HttpCode.UNAUTHORIZED, "Incorrect PIN")
             );

+ 12 - 1
server/routers/resource/authWithWhitelist.ts

@@ -16,6 +16,7 @@ import { fromError } from "zod-validation-error";
 import { createResourceSession } from "@server/auth/sessions/resource";
 import { isValidOtp, sendResourceOtpEmail } from "@server/auth/resourceOtp";
 import logger from "@server/logger";
+import config from "@server/lib/config";
 
 const authWithWhitelistBodySchema = z
     .object({
@@ -96,7 +97,7 @@ export async function authWithWhitelist(
             // if email is not found, check for wildcard email
             const wildcard = "*@" + email.split("@")[1];
 
-            logger.debug("Checking for wildcard email: " + wildcard)
+            logger.debug("Checking for wildcard email: " + wildcard);
 
             const [result] = await db
                 .select()
@@ -120,6 +121,11 @@ export async function authWithWhitelist(
 
             // if wildcard is still not found, return unauthorized
             if (!whitelistedEmail) {
+                if (config.getRawConfig().app.log_failed_attempts) {
+                    logger.info(
+                        `Email is not whitelisted. Resource ID: ${resource?.resourceId}. Email: ${email}. IP: ${req.ip}.`
+                    );
+                }
                 return next(
                     createHttpError(
                         HttpCode.UNAUTHORIZED,
@@ -151,6 +157,11 @@ export async function authWithWhitelist(
                 otp
             );
             if (!isValidCode) {
+                if (config.getRawConfig().app.log_failed_attempts) {
+                    logger.info(
+                        `Resource email otp incorrect. Resource ID: ${resource.resourceId}. Email: ${email}. IP: ${req.ip}.`
+                    );
+                }
                 return next(
                     createHttpError(HttpCode.UNAUTHORIZED, "Incorrect OTP")
                 );