mirror of
https://github.com/fosrl/pangolin.git
synced 2026-02-20 11:56:38 +00:00
Compare commits
7 Commits
1.12.3
...
1.12.2-s.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de8262d7b9 | ||
|
|
8ad7bcc0d6 | ||
|
|
e62806d6fb | ||
|
|
aabe39137b | ||
|
|
ca66637270 | ||
|
|
fbce392137 | ||
|
|
d38b321f85 |
@@ -238,7 +238,6 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("CrowdSec installed successfully!")
|
fmt.Println("CrowdSec installed successfully!")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
3615
package-lock.json
generated
3615
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -94,7 +94,7 @@
|
|||||||
"lucide-react": "^0.552.0",
|
"lucide-react": "^0.552.0",
|
||||||
"maxmind": "5.0.0",
|
"maxmind": "5.0.0",
|
||||||
"moment": "2.30.1",
|
"moment": "2.30.1",
|
||||||
"next": "15.5.7",
|
"next": "15.5.6",
|
||||||
"next-intl": "^4.4.0",
|
"next-intl": "^4.4.0",
|
||||||
"next-themes": "0.4.6",
|
"next-themes": "0.4.6",
|
||||||
"nextjs-toploader": "^3.9.17",
|
"nextjs-toploader": "^3.9.17",
|
||||||
|
|||||||
@@ -79,6 +79,12 @@ export function createApiServer() {
|
|||||||
// Add request timeout middleware
|
// Add request timeout middleware
|
||||||
apiServer.use(requestTimeoutMiddleware(60000)); // 60 second timeout
|
apiServer.use(requestTimeoutMiddleware(60000)); // 60 second timeout
|
||||||
|
|
||||||
|
apiServer.use(logIncomingMiddleware);
|
||||||
|
|
||||||
|
if (build !== "oss") {
|
||||||
|
apiServer.use(`${prefix}/hybrid`, hybridRouter); // put before rate limiting because we will rate limit there separately because some of the routes are heavily used
|
||||||
|
}
|
||||||
|
|
||||||
if (!dev) {
|
if (!dev) {
|
||||||
apiServer.use(
|
apiServer.use(
|
||||||
rateLimit({
|
rateLimit({
|
||||||
@@ -101,11 +107,7 @@ export function createApiServer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// API routes
|
// API routes
|
||||||
apiServer.use(logIncomingMiddleware);
|
|
||||||
apiServer.use(prefix, unauthenticated);
|
apiServer.use(prefix, unauthenticated);
|
||||||
if (build !== "oss") {
|
|
||||||
apiServer.use(`${prefix}/hybrid`, hybridRouter);
|
|
||||||
}
|
|
||||||
apiServer.use(prefix, authenticated);
|
apiServer.use(prefix, authenticated);
|
||||||
|
|
||||||
// WebSocket routes
|
// WebSocket routes
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import path from "path";
|
|||||||
import { fileURLToPath } from "url";
|
import { fileURLToPath } from "url";
|
||||||
|
|
||||||
// This is a placeholder value replaced by the build process
|
// This is a placeholder value replaced by the build process
|
||||||
export const APP_VERSION = "1.12.3";
|
export const APP_VERSION = "1.12.1";
|
||||||
|
|
||||||
export const __FILENAME = fileURLToPath(import.meta.url);
|
export const __FILENAME = fileURLToPath(import.meta.url);
|
||||||
export const __DIRNAME = path.dirname(__FILENAME);
|
export const __DIRNAME = path.dirname(__FILENAME);
|
||||||
|
|||||||
@@ -72,6 +72,43 @@ export class RateLimitService {
|
|||||||
return `ratelimit:${clientId}:${messageType}`;
|
return `ratelimit:${clientId}:${messageType}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper function to clean up old timestamp fields from a Redis hash
|
||||||
|
private async cleanupOldTimestamps(key: string, windowStart: number): Promise<void> {
|
||||||
|
if (!redisManager.isRedisEnabled()) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const client = redisManager.getClient();
|
||||||
|
if (!client) return;
|
||||||
|
|
||||||
|
// Get all fields in the hash
|
||||||
|
const allData = await redisManager.hgetall(key);
|
||||||
|
if (!allData || Object.keys(allData).length === 0) return;
|
||||||
|
|
||||||
|
// Find fields that are older than the window
|
||||||
|
const fieldsToDelete: string[] = [];
|
||||||
|
for (const timestamp of Object.keys(allData)) {
|
||||||
|
const time = parseInt(timestamp);
|
||||||
|
if (time < windowStart) {
|
||||||
|
fieldsToDelete.push(timestamp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete old fields in batches to avoid call stack size exceeded errors
|
||||||
|
// The spread operator can cause issues with very large arrays
|
||||||
|
if (fieldsToDelete.length > 0) {
|
||||||
|
const batchSize = 1000; // Process 1000 fields at a time
|
||||||
|
for (let i = 0; i < fieldsToDelete.length; i += batchSize) {
|
||||||
|
const batch = fieldsToDelete.slice(i, i + batchSize);
|
||||||
|
await client.hdel(key, ...batch);
|
||||||
|
}
|
||||||
|
logger.debug(`Cleaned up ${fieldsToDelete.length} old timestamp fields from ${key}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`Failed to cleanup old timestamps for key ${key}:`, error);
|
||||||
|
// Don't throw - cleanup failures shouldn't block rate limiting
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Helper function to sync local rate limit data to Redis
|
// Helper function to sync local rate limit data to Redis
|
||||||
private async syncRateLimitToRedis(
|
private async syncRateLimitToRedis(
|
||||||
clientId: string,
|
clientId: string,
|
||||||
@@ -81,8 +118,12 @@ export class RateLimitService {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const currentTime = Math.floor(Date.now() / 1000);
|
const currentTime = Math.floor(Date.now() / 1000);
|
||||||
|
const windowStart = currentTime - RATE_LIMIT_WINDOW;
|
||||||
const globalKey = this.getRateLimitKey(clientId);
|
const globalKey = this.getRateLimitKey(clientId);
|
||||||
|
|
||||||
|
// Clean up old timestamp fields before writing
|
||||||
|
await this.cleanupOldTimestamps(globalKey, windowStart);
|
||||||
|
|
||||||
// Get current value and add pending count
|
// Get current value and add pending count
|
||||||
const currentValue = await redisManager.hget(
|
const currentValue = await redisManager.hget(
|
||||||
globalKey,
|
globalKey,
|
||||||
@@ -93,7 +134,7 @@ export class RateLimitService {
|
|||||||
).toString();
|
).toString();
|
||||||
await redisManager.hset(globalKey, currentTime.toString(), newValue);
|
await redisManager.hset(globalKey, currentTime.toString(), newValue);
|
||||||
|
|
||||||
// Set TTL using the client directly
|
// Set TTL using the client directly - this prevents the key from persisting forever
|
||||||
if (redisManager.getClient()) {
|
if (redisManager.getClient()) {
|
||||||
await redisManager
|
await redisManager
|
||||||
.getClient()
|
.getClient()
|
||||||
@@ -119,8 +160,12 @@ export class RateLimitService {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const currentTime = Math.floor(Date.now() / 1000);
|
const currentTime = Math.floor(Date.now() / 1000);
|
||||||
|
const windowStart = currentTime - RATE_LIMIT_WINDOW;
|
||||||
const messageTypeKey = this.getMessageTypeRateLimitKey(clientId, messageType);
|
const messageTypeKey = this.getMessageTypeRateLimitKey(clientId, messageType);
|
||||||
|
|
||||||
|
// Clean up old timestamp fields before writing
|
||||||
|
await this.cleanupOldTimestamps(messageTypeKey, windowStart);
|
||||||
|
|
||||||
// Get current value and add pending count
|
// Get current value and add pending count
|
||||||
const currentValue = await redisManager.hget(
|
const currentValue = await redisManager.hget(
|
||||||
messageTypeKey,
|
messageTypeKey,
|
||||||
@@ -135,7 +180,7 @@ export class RateLimitService {
|
|||||||
newValue
|
newValue
|
||||||
);
|
);
|
||||||
|
|
||||||
// Set TTL using the client directly
|
// Set TTL using the client directly - this prevents the key from persisting forever
|
||||||
if (redisManager.getClient()) {
|
if (redisManager.getClient()) {
|
||||||
await redisManager
|
await redisManager
|
||||||
.getClient()
|
.getClient()
|
||||||
@@ -170,6 +215,10 @@ export class RateLimitService {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const globalKey = this.getRateLimitKey(clientId);
|
const globalKey = this.getRateLimitKey(clientId);
|
||||||
|
|
||||||
|
// Clean up old timestamp fields before reading
|
||||||
|
await this.cleanupOldTimestamps(globalKey, windowStart);
|
||||||
|
|
||||||
const globalRateLimitData = await redisManager.hgetall(globalKey);
|
const globalRateLimitData = await redisManager.hgetall(globalKey);
|
||||||
|
|
||||||
let count = 0;
|
let count = 0;
|
||||||
@@ -215,6 +264,10 @@ export class RateLimitService {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const messageTypeKey = this.getMessageTypeRateLimitKey(clientId, messageType);
|
const messageTypeKey = this.getMessageTypeRateLimitKey(clientId, messageType);
|
||||||
|
|
||||||
|
// Clean up old timestamp fields before reading
|
||||||
|
await this.cleanupOldTimestamps(messageTypeKey, windowStart);
|
||||||
|
|
||||||
const messageTypeRateLimitData = await redisManager.hgetall(messageTypeKey);
|
const messageTypeRateLimitData = await redisManager.hgetall(messageTypeKey);
|
||||||
|
|
||||||
let count = 0;
|
let count = 0;
|
||||||
|
|||||||
@@ -227,6 +227,8 @@ export type UserSessionWithUser = {
|
|||||||
export const hybridRouter = Router();
|
export const hybridRouter = Router();
|
||||||
hybridRouter.use(verifySessionRemoteExitNodeMiddleware);
|
hybridRouter.use(verifySessionRemoteExitNodeMiddleware);
|
||||||
|
|
||||||
|
// TODO: ADD RATE LIMITING TO THESE ROUTES AS NEEDED BASED ON USAGE PATTERNS
|
||||||
|
|
||||||
hybridRouter.get(
|
hybridRouter.get(
|
||||||
"/general-config",
|
"/general-config",
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
|||||||
@@ -1 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of a proprietary work.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025 Fossorial, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This file is licensed under the Fossorial Commercial License.
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
||||||
|
*
|
||||||
|
* This file is not licensed under the AGPLv3.
|
||||||
|
*/
|
||||||
|
|
||||||
export * from "./sendSupportEmail";
|
export * from "./sendSupportEmail";
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ export async function sendSupportEmail(
|
|||||||
{
|
{
|
||||||
name: req.user?.email || "Support User",
|
name: req.user?.email || "Support User",
|
||||||
to: "support@pangolin.net",
|
to: "support@pangolin.net",
|
||||||
from: req.user?.email || config.getNoReplyEmail(),
|
from: config.getNoReplyEmail(),
|
||||||
subject: `Support Request: ${subject}`
|
subject: `Support Request: ${subject}`
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user