mirror of
https://github.com/fosrl/pangolin.git
synced 2025-07-25 13:15:02 +02:00
add failed auth logging
This commit is contained in:
parent
fdb1ab4bd9
commit
0bd8217d9e
16 changed files with 175 additions and 25 deletions
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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")
|
||||
);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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")
|
||||
);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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")
|
||||
);
|
||||
|
|
|
@ -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")
|
||||
);
|
||||
|
|
|
@ -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")
|
||||
);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue