mirror of
https://github.com/fosrl/pangolin.git
synced 2026-02-19 19:36:38 +00:00
Access logs working
This commit is contained in:
@@ -11,7 +11,7 @@ import { fromError } from "zod-validation-error";
|
||||
import { QueryRequestAuditLogResponse } from "@server/routers/auditLogs/types";
|
||||
import response from "@server/lib/response";
|
||||
import logger from "@server/logger";
|
||||
import { queryAccessAuditLogsQuery, queryRequestAuditLogsParams, querySites } from "./queryRequstAuditLog";
|
||||
import { queryAccessAuditLogsQuery, queryRequestAuditLogsParams, queryRequest } from "./queryRequstAuditLog";
|
||||
import { generateCSV } from "./generateCSV";
|
||||
|
||||
registry.registerPath({
|
||||
@@ -54,7 +54,7 @@ export async function exportRequestAuditLogs(
|
||||
}
|
||||
const { orgId } = parsedParams.data;
|
||||
|
||||
const baseQuery = querySites(timeStart, timeEnd, orgId);
|
||||
const baseQuery = queryRequest(timeStart, timeEnd, orgId);
|
||||
|
||||
const log = await baseQuery.limit(limit).offset(offset);
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ export const queryRequestAuditLogsParams = z.object({
|
||||
orgId: z.string()
|
||||
});
|
||||
|
||||
export function querySites(timeStart: number, timeEnd: number, orgId: string) {
|
||||
export function queryRequest(timeStart: number, timeEnd: number, orgId: string) {
|
||||
return db
|
||||
.select({
|
||||
timestamp: requestAuditLog.timestamp,
|
||||
@@ -84,7 +84,7 @@ export function querySites(timeStart: number, timeEnd: number, orgId: string) {
|
||||
.orderBy(requestAuditLog.timestamp);
|
||||
}
|
||||
|
||||
export function countQuery(timeStart: number, timeEnd: number, orgId: string) {
|
||||
export function countRequestQuery(timeStart: number, timeEnd: number, orgId: string) {
|
||||
const countQuery = db
|
||||
.select({ count: count() })
|
||||
.from(requestAuditLog)
|
||||
@@ -138,11 +138,11 @@ export async function queryRequestAuditLogs(
|
||||
}
|
||||
const { orgId } = parsedParams.data;
|
||||
|
||||
const baseQuery = querySites(timeStart, timeEnd, orgId);
|
||||
const baseQuery = queryRequest(timeStart, timeEnd, orgId);
|
||||
|
||||
const log = await baseQuery.limit(limit).offset(offset);
|
||||
|
||||
const totalCountResult = await countQuery(timeStart, timeEnd, orgId);
|
||||
const totalCountResult = await countRequestQuery(timeStart, timeEnd, orgId);
|
||||
const totalCount = totalCountResult[0].count;
|
||||
|
||||
return response<QueryRequestAuditLogResponse>(res, {
|
||||
|
||||
@@ -44,4 +44,28 @@ export type QueryRequestAuditLogResponse = {
|
||||
limit: number;
|
||||
offset: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export type QueryAccessAuditLogResponse = {
|
||||
log: {
|
||||
orgId: string;
|
||||
action: string;
|
||||
type: string;
|
||||
resourceId: number | null;
|
||||
resourceName: string | null;
|
||||
resourceNiceId: string | null;
|
||||
ip: string | null;
|
||||
location: string | null;
|
||||
userAgent: string | null;
|
||||
metadata: string | null;
|
||||
actorType: string;
|
||||
actorId: string;
|
||||
timestamp: number;
|
||||
actor: string;
|
||||
}[];
|
||||
pagination: {
|
||||
total: number;
|
||||
limit: number;
|
||||
offset: number;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
generateSessionToken,
|
||||
serializeSessionCookie
|
||||
} from "@server/auth/sessions/app";
|
||||
import { db } from "@server/db";
|
||||
import { db, resources } from "@server/db";
|
||||
import { users, securityKeys } from "@server/db";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
import response from "@server/lib/response";
|
||||
@@ -18,12 +18,14 @@ import logger from "@server/logger";
|
||||
import { verifyPassword } from "@server/auth/password";
|
||||
import { verifySession } from "@server/auth/sessions/verifySession";
|
||||
import { UserType } from "@server/types/UserTypes";
|
||||
import { logAccessAudit } from "@server/private/lib/logAccessAudit";
|
||||
|
||||
export const loginBodySchema = z
|
||||
.object({
|
||||
email: z.string().toLowerCase().email(),
|
||||
password: z.string(),
|
||||
code: z.string().optional()
|
||||
code: z.string().optional(),
|
||||
resourceGuid: z.string().optional()
|
||||
})
|
||||
.strict();
|
||||
|
||||
@@ -52,7 +54,7 @@ export async function login(
|
||||
);
|
||||
}
|
||||
|
||||
const { email, password, code } = parsedBody.data;
|
||||
const { email, password, code, resourceGuid } = parsedBody.data;
|
||||
|
||||
try {
|
||||
const { session: existingSession } = await verifySession(req);
|
||||
@@ -66,6 +68,28 @@ export async function login(
|
||||
});
|
||||
}
|
||||
|
||||
let resourceId: number | null = null;
|
||||
let orgId: string | null = null;
|
||||
if (resourceGuid) {
|
||||
const [resource] = await db
|
||||
.select()
|
||||
.from(resources)
|
||||
.where(eq(resources.resourceGuid, resourceGuid))
|
||||
.limit(1);
|
||||
|
||||
if (!resource) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.NOT_FOUND,
|
||||
`Resource with GUID ${resourceGuid} not found`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
resourceId = resource.resourceId;
|
||||
orgId = resource.orgId;
|
||||
}
|
||||
|
||||
const existingUserRes = await db
|
||||
.select()
|
||||
.from(users)
|
||||
@@ -78,6 +102,18 @@ export async function login(
|
||||
`Username or password incorrect. Email: ${email}. IP: ${req.ip}.`
|
||||
);
|
||||
}
|
||||
|
||||
if (resourceId && orgId) {
|
||||
logAccessAudit({
|
||||
orgId: orgId,
|
||||
resourceId: resourceId,
|
||||
action: false,
|
||||
type: "login",
|
||||
userAgent: req.headers["user-agent"],
|
||||
requestIp: req.ip
|
||||
});
|
||||
}
|
||||
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.UNAUTHORIZED,
|
||||
@@ -98,6 +134,18 @@ export async function login(
|
||||
`Username or password incorrect. Email: ${email}. IP: ${req.ip}.`
|
||||
);
|
||||
}
|
||||
|
||||
if (resourceId && orgId) {
|
||||
logAccessAudit({
|
||||
orgId: orgId,
|
||||
resourceId: resourceId,
|
||||
action: false,
|
||||
type: "login",
|
||||
userAgent: req.headers["user-agent"],
|
||||
requestIp: req.ip
|
||||
});
|
||||
}
|
||||
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.UNAUTHORIZED,
|
||||
@@ -158,6 +206,18 @@ export async function login(
|
||||
`Two-factor code incorrect. Email: ${email}. IP: ${req.ip}.`
|
||||
);
|
||||
}
|
||||
|
||||
if (resourceId && orgId) {
|
||||
logAccessAudit({
|
||||
orgId: orgId,
|
||||
resourceId: resourceId,
|
||||
action: false,
|
||||
type: "login",
|
||||
userAgent: req.headers["user-agent"],
|
||||
requestIp: req.ip
|
||||
});
|
||||
}
|
||||
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.UNAUTHORIZED,
|
||||
|
||||
@@ -10,11 +10,10 @@ import { z } from "zod";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { createResourceSession } from "@server/auth/sessions/resource";
|
||||
import logger from "@server/logger";
|
||||
import {
|
||||
verifyResourceAccessToken
|
||||
} from "@server/auth/verifyResourceAccessToken";
|
||||
import { verifyResourceAccessToken } from "@server/auth/verifyResourceAccessToken";
|
||||
import config from "@server/lib/config";
|
||||
import stoi from "@server/lib/stoi";
|
||||
import { logAccessAudit } from "@server/private/lib/logAccessAudit";
|
||||
|
||||
const authWithAccessTokenBodySchema = z
|
||||
.object({
|
||||
@@ -131,6 +130,16 @@ export async function authWithAccessToken(
|
||||
`Resource access token invalid. Resource ID: ${resource.resourceId}. IP: ${req.ip}.`
|
||||
);
|
||||
}
|
||||
|
||||
logAccessAudit({
|
||||
orgId: resource.orgId,
|
||||
resourceId: resource.resourceId,
|
||||
action: false,
|
||||
type: "accessToken",
|
||||
userAgent: req.headers["user-agent"],
|
||||
requestIp: req.ip
|
||||
});
|
||||
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.UNAUTHORIZED,
|
||||
@@ -150,6 +159,15 @@ export async function authWithAccessToken(
|
||||
doNotExtend: true
|
||||
});
|
||||
|
||||
logAccessAudit({
|
||||
orgId: resource.orgId,
|
||||
resourceId: resource.resourceId,
|
||||
action: true,
|
||||
type: "accessToken",
|
||||
userAgent: req.headers["user-agent"],
|
||||
requestIp: req.ip
|
||||
});
|
||||
|
||||
return response<AuthWithAccessTokenResponse>(res, {
|
||||
data: {
|
||||
session: token,
|
||||
|
||||
@@ -13,6 +13,7 @@ import { createResourceSession } from "@server/auth/sessions/resource";
|
||||
import logger from "@server/logger";
|
||||
import { verifyPassword } from "@server/auth/password";
|
||||
import config from "@server/lib/config";
|
||||
import { logAccessAudit } from "@server/private/lib/logAccessAudit";
|
||||
|
||||
export const authWithPasswordBodySchema = z
|
||||
.object({
|
||||
@@ -113,6 +114,16 @@ export async function authWithPassword(
|
||||
`Resource password incorrect. Resource ID: ${resource.resourceId}. IP: ${req.ip}.`
|
||||
);
|
||||
}
|
||||
|
||||
logAccessAudit({
|
||||
orgId: org.orgId,
|
||||
resourceId: resource.resourceId,
|
||||
action: false,
|
||||
type: "password",
|
||||
userAgent: req.headers["user-agent"],
|
||||
requestIp: req.ip
|
||||
});
|
||||
|
||||
return next(
|
||||
createHttpError(HttpCode.UNAUTHORIZED, "Incorrect password")
|
||||
);
|
||||
@@ -129,6 +140,15 @@ export async function authWithPassword(
|
||||
doNotExtend: true
|
||||
});
|
||||
|
||||
logAccessAudit({
|
||||
orgId: org.orgId,
|
||||
resourceId: resource.resourceId,
|
||||
action: true,
|
||||
type: "password",
|
||||
userAgent: req.headers["user-agent"],
|
||||
requestIp: req.ip
|
||||
});
|
||||
|
||||
return response<AuthWithPasswordResponse>(res, {
|
||||
data: {
|
||||
session: token
|
||||
|
||||
@@ -12,6 +12,7 @@ import { createResourceSession } from "@server/auth/sessions/resource";
|
||||
import logger from "@server/logger";
|
||||
import { verifyPassword } from "@server/auth/password";
|
||||
import config from "@server/lib/config";
|
||||
import { logAccessAudit } from "@server/private/lib/logAccessAudit";
|
||||
|
||||
export const authWithPincodeBodySchema = z
|
||||
.object({
|
||||
@@ -112,6 +113,16 @@ export async function authWithPincode(
|
||||
`Resource pin code incorrect. Resource ID: ${resource.resourceId}. IP: ${req.ip}.`
|
||||
);
|
||||
}
|
||||
|
||||
logAccessAudit({
|
||||
orgId: org.orgId,
|
||||
resourceId: resource.resourceId,
|
||||
action: false,
|
||||
type: "pincode",
|
||||
userAgent: req.headers["user-agent"],
|
||||
requestIp: req.ip
|
||||
});
|
||||
|
||||
return next(
|
||||
createHttpError(HttpCode.UNAUTHORIZED, "Incorrect PIN")
|
||||
);
|
||||
@@ -128,6 +139,15 @@ export async function authWithPincode(
|
||||
doNotExtend: true
|
||||
});
|
||||
|
||||
logAccessAudit({
|
||||
orgId: org.orgId,
|
||||
resourceId: resource.resourceId,
|
||||
action: true,
|
||||
type: "pincode",
|
||||
userAgent: req.headers["user-agent"],
|
||||
requestIp: req.ip
|
||||
});
|
||||
|
||||
return response<AuthWithPincodeResponse>(res, {
|
||||
data: {
|
||||
session: token
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
import { generateSessionToken } from "@server/auth/sessions/app";
|
||||
import { db } from "@server/db";
|
||||
import {
|
||||
orgs,
|
||||
resourceOtp,
|
||||
resources,
|
||||
resourceWhitelist
|
||||
} from "@server/db";
|
||||
import { orgs, resourceOtp, resources, resourceWhitelist } from "@server/db";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
import response from "@server/lib/response";
|
||||
import { eq, and } from "drizzle-orm";
|
||||
@@ -17,13 +12,11 @@ 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";
|
||||
import { logAccessAudit } from "@server/private/lib/logAccessAudit";
|
||||
|
||||
const authWithWhitelistBodySchema = z
|
||||
.object({
|
||||
email: z
|
||||
.string()
|
||||
.toLowerCase()
|
||||
.email(),
|
||||
email: z.string().toLowerCase().email(),
|
||||
otp: z.string().optional()
|
||||
})
|
||||
.strict();
|
||||
@@ -126,6 +119,19 @@ export async function authWithWhitelist(
|
||||
`Email is not whitelisted. Email: ${email}. IP: ${req.ip}.`
|
||||
);
|
||||
}
|
||||
|
||||
if (org && resource) {
|
||||
logAccessAudit({
|
||||
orgId: org.orgId,
|
||||
resourceId: resource.resourceId,
|
||||
action: false,
|
||||
type: "whitelistedEmail",
|
||||
metadata: { email },
|
||||
userAgent: req.headers["user-agent"],
|
||||
requestIp: req.ip
|
||||
});
|
||||
}
|
||||
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.UNAUTHORIZED,
|
||||
@@ -219,6 +225,16 @@ export async function authWithWhitelist(
|
||||
doNotExtend: true
|
||||
});
|
||||
|
||||
logAccessAudit({
|
||||
orgId: org.orgId,
|
||||
resourceId: resource.resourceId,
|
||||
action: true,
|
||||
metadata: { email },
|
||||
type: "whitelistedEmail",
|
||||
userAgent: req.headers["user-agent"],
|
||||
requestIp: req.ip
|
||||
});
|
||||
|
||||
return response<AuthWithWhitelistResponse>(res, {
|
||||
data: {
|
||||
session: token
|
||||
|
||||
@@ -10,11 +10,10 @@ import { fromError } from "zod-validation-error";
|
||||
import logger from "@server/logger";
|
||||
import { generateSessionToken } from "@server/auth/sessions/app";
|
||||
import config from "@server/lib/config";
|
||||
import {
|
||||
encodeHexLowerCase
|
||||
} from "@oslojs/encoding";
|
||||
import { encodeHexLowerCase } from "@oslojs/encoding";
|
||||
import { sha256 } from "@oslojs/crypto/sha2";
|
||||
import { response } from "@server/lib/response";
|
||||
import { logAccessAudit } from "@server/private/lib/logAccessAudit";
|
||||
|
||||
const getExchangeTokenParams = z
|
||||
.object({
|
||||
@@ -47,13 +46,13 @@ export async function getExchangeToken(
|
||||
|
||||
const { resourceId } = parsedParams.data;
|
||||
|
||||
const resource = await db
|
||||
const [resource] = await db
|
||||
.select()
|
||||
.from(resources)
|
||||
.where(eq(resources.resourceId, resourceId))
|
||||
.limit(1);
|
||||
|
||||
if (resource.length === 0) {
|
||||
if (!resource) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.NOT_FOUND,
|
||||
@@ -89,6 +88,21 @@ export async function getExchangeToken(
|
||||
doNotExtend: true
|
||||
});
|
||||
|
||||
if (req.user) {
|
||||
logAccessAudit({
|
||||
orgId: resource.orgId,
|
||||
resourceId: resourceId,
|
||||
user: {
|
||||
username: req.user.username,
|
||||
userId: req.user.userId
|
||||
},
|
||||
action: true,
|
||||
type: "login",
|
||||
userAgent: req.headers["user-agent"],
|
||||
requestIp: req.ip
|
||||
});
|
||||
}
|
||||
|
||||
logger.debug("Request token created successfully");
|
||||
|
||||
return response<GetExchangeTokenResponse>(res, {
|
||||
|
||||
Reference in New Issue
Block a user