mirror of
https://github.com/fosrl/pangolin.git
synced 2025-12-05 23:48:01 +00:00
Compare commits
17 Commits
dev-ms
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
447b706909 | ||
|
|
2b31dd955c | ||
|
|
e7aeb4ff89 | ||
|
|
9dd1192033 | ||
|
|
e61da0958f | ||
|
|
fce588057e | ||
|
|
6d1713b6b9 | ||
|
|
de8262d7b9 | ||
|
|
8ad7bcc0d6 | ||
|
|
e62806d6fb | ||
|
|
aabe39137b | ||
|
|
c9786946b7 | ||
|
|
9344ab3546 | ||
|
|
1a4078b8a1 | ||
|
|
ca66637270 | ||
|
|
fbce392137 | ||
|
|
c6611471b1 |
2
.github/workflows/cicd.yml
vendored
2
.github/workflows/cicd.yml
vendored
@@ -39,7 +39,7 @@ jobs:
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
|
||||
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||
|
||||
@@ -3,8 +3,8 @@ module installer
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
golang.org/x/term v0.36.0
|
||||
golang.org/x/term v0.37.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require golang.org/x/sys v0.37.0 // indirect
|
||||
require golang.org/x/sys v0.38.0 // indirect
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
|
||||
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
||||
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
||||
@@ -238,7 +238,6 @@ func main() {
|
||||
}
|
||||
|
||||
fmt.Println("CrowdSec installed successfully!")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
2170
package-lock.json
generated
2170
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
32
package.json
32
package.json
@@ -39,33 +39,33 @@
|
||||
"@node-rs/argon2": "^2.0.2",
|
||||
"@oslojs/crypto": "1.0.1",
|
||||
"@oslojs/encoding": "1.1.0",
|
||||
"@radix-ui/react-avatar": "1.1.10",
|
||||
"@radix-ui/react-avatar": "1.1.11",
|
||||
"@radix-ui/react-checkbox": "1.3.3",
|
||||
"@radix-ui/react-collapsible": "1.1.12",
|
||||
"@radix-ui/react-dialog": "1.1.15",
|
||||
"@radix-ui/react-dropdown-menu": "2.1.16",
|
||||
"@radix-ui/react-icons": "1.3.2",
|
||||
"@radix-ui/react-label": "2.1.7",
|
||||
"@radix-ui/react-label": "2.1.8",
|
||||
"@radix-ui/react-popover": "1.1.15",
|
||||
"@radix-ui/react-progress": "^1.1.7",
|
||||
"@radix-ui/react-progress": "^1.1.8",
|
||||
"@radix-ui/react-radio-group": "1.3.8",
|
||||
"@radix-ui/react-scroll-area": "^1.2.10",
|
||||
"@radix-ui/react-select": "2.2.6",
|
||||
"@radix-ui/react-separator": "1.1.7",
|
||||
"@radix-ui/react-slot": "1.2.3",
|
||||
"@radix-ui/react-separator": "1.1.8",
|
||||
"@radix-ui/react-slot": "1.2.4",
|
||||
"@radix-ui/react-switch": "1.2.6",
|
||||
"@radix-ui/react-tabs": "1.1.13",
|
||||
"@radix-ui/react-toast": "1.2.15",
|
||||
"@radix-ui/react-tooltip": "^1.2.8",
|
||||
"@react-email/components": "0.5.7",
|
||||
"@react-email/render": "^1.3.2",
|
||||
"@react-email/tailwind": "1.2.2",
|
||||
"@react-email/tailwind": "2.0.1",
|
||||
"@simplewebauthn/browser": "^13.2.2",
|
||||
"@simplewebauthn/server": "^13.2.2",
|
||||
"@tailwindcss/forms": "^0.5.10",
|
||||
"@tanstack/react-table": "8.21.3",
|
||||
"arctic": "^3.7.0",
|
||||
"axios": "^1.13.1",
|
||||
"axios": "^1.13.2",
|
||||
"better-sqlite3": "11.7.0",
|
||||
"canvas-confetti": "1.9.4",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
@@ -78,8 +78,8 @@
|
||||
"crypto-js": "^4.2.0",
|
||||
"date-fns": "4.1.0",
|
||||
"drizzle-orm": "0.44.7",
|
||||
"eslint": "9.39.0",
|
||||
"eslint-config-next": "16.0.1",
|
||||
"eslint": "9.39.1",
|
||||
"eslint-config-next": "16.0.3",
|
||||
"express": "5.1.0",
|
||||
"express-rate-limit": "8.2.1",
|
||||
"glob": "11.0.3",
|
||||
@@ -89,10 +89,10 @@
|
||||
"input-otp": "1.4.2",
|
||||
"ioredis": "5.8.2",
|
||||
"jmespath": "^0.16.0",
|
||||
"js-yaml": "4.1.0",
|
||||
"js-yaml": "4.1.1",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"lucide-react": "^0.552.0",
|
||||
"maxmind": "5.0.0",
|
||||
"maxmind": "5.0.1",
|
||||
"moment": "2.30.1",
|
||||
"next": "15.5.6",
|
||||
"next-intl": "^4.4.0",
|
||||
@@ -105,7 +105,7 @@
|
||||
"nprogress": "^0.2.0",
|
||||
"oslo": "1.2.1",
|
||||
"pg": "^8.16.2",
|
||||
"posthog-node": "^5.11.0",
|
||||
"posthog-node": "^5.11.2",
|
||||
"qrcode.react": "4.2.0",
|
||||
"react": "19.2.0",
|
||||
"react-day-picker": "9.11.1",
|
||||
@@ -115,7 +115,7 @@
|
||||
"react-icons": "^5.5.0",
|
||||
"rebuild": "0.1.2",
|
||||
"reodotdev": "^1.0.0",
|
||||
"resend": "^6.4.0",
|
||||
"resend": "^6.4.2",
|
||||
"semver": "^7.7.3",
|
||||
"stripe": "18.2.1",
|
||||
"swagger-ui-express": "^5.0.1",
|
||||
@@ -147,7 +147,7 @@
|
||||
"@types/js-yaml": "4.0.9",
|
||||
"@types/jsonwebtoken": "^9.0.10",
|
||||
"@types/nprogress": "^0.2.3",
|
||||
"@types/node": "24.9.2",
|
||||
"@types/node": "24.10.1",
|
||||
"@types/nodemailer": "7.0.3",
|
||||
"@types/pg": "8.15.6",
|
||||
"@types/react": "19.2.2",
|
||||
@@ -157,8 +157,8 @@
|
||||
"@types/ws": "8.18.1",
|
||||
"@types/yargs": "17.0.34",
|
||||
"drizzle-kit": "0.31.6",
|
||||
"esbuild": "0.25.12",
|
||||
"esbuild-node-externals": "1.18.0",
|
||||
"esbuild": "0.27.0",
|
||||
"esbuild-node-externals": "1.19.1",
|
||||
"postcss": "^8",
|
||||
"react-email": "4.3.2",
|
||||
"tailwindcss": "^4.1.4",
|
||||
|
||||
@@ -79,6 +79,12 @@ export function createApiServer() {
|
||||
// Add request timeout middleware
|
||||
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) {
|
||||
apiServer.use(
|
||||
rateLimit({
|
||||
@@ -101,11 +107,7 @@ export function createApiServer() {
|
||||
}
|
||||
|
||||
// API routes
|
||||
apiServer.use(logIncomingMiddleware);
|
||||
apiServer.use(prefix, unauthenticated);
|
||||
if (build !== "oss") {
|
||||
apiServer.use(`${prefix}/hybrid`, hybridRouter);
|
||||
}
|
||||
apiServer.use(prefix, authenticated);
|
||||
|
||||
// WebSocket routes
|
||||
|
||||
@@ -72,6 +72,43 @@ export class RateLimitService {
|
||||
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
|
||||
private async syncRateLimitToRedis(
|
||||
clientId: string,
|
||||
@@ -81,8 +118,12 @@ export class RateLimitService {
|
||||
|
||||
try {
|
||||
const currentTime = Math.floor(Date.now() / 1000);
|
||||
const windowStart = currentTime - RATE_LIMIT_WINDOW;
|
||||
const globalKey = this.getRateLimitKey(clientId);
|
||||
|
||||
// Clean up old timestamp fields before writing
|
||||
await this.cleanupOldTimestamps(globalKey, windowStart);
|
||||
|
||||
// Get current value and add pending count
|
||||
const currentValue = await redisManager.hget(
|
||||
globalKey,
|
||||
@@ -93,7 +134,7 @@ export class RateLimitService {
|
||||
).toString();
|
||||
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()) {
|
||||
await redisManager
|
||||
.getClient()
|
||||
@@ -119,8 +160,12 @@ export class RateLimitService {
|
||||
|
||||
try {
|
||||
const currentTime = Math.floor(Date.now() / 1000);
|
||||
const windowStart = currentTime - RATE_LIMIT_WINDOW;
|
||||
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
|
||||
const currentValue = await redisManager.hget(
|
||||
messageTypeKey,
|
||||
@@ -135,7 +180,7 @@ export class RateLimitService {
|
||||
newValue
|
||||
);
|
||||
|
||||
// Set TTL using the client directly
|
||||
// Set TTL using the client directly - this prevents the key from persisting forever
|
||||
if (redisManager.getClient()) {
|
||||
await redisManager
|
||||
.getClient()
|
||||
@@ -170,6 +215,10 @@ export class RateLimitService {
|
||||
|
||||
try {
|
||||
const globalKey = this.getRateLimitKey(clientId);
|
||||
|
||||
// Clean up old timestamp fields before reading
|
||||
await this.cleanupOldTimestamps(globalKey, windowStart);
|
||||
|
||||
const globalRateLimitData = await redisManager.hgetall(globalKey);
|
||||
|
||||
let count = 0;
|
||||
@@ -215,6 +264,10 @@ export class RateLimitService {
|
||||
|
||||
try {
|
||||
const messageTypeKey = this.getMessageTypeRateLimitKey(clientId, messageType);
|
||||
|
||||
// Clean up old timestamp fields before reading
|
||||
await this.cleanupOldTimestamps(messageTypeKey, windowStart);
|
||||
|
||||
const messageTypeRateLimitData = await redisManager.hgetall(messageTypeKey);
|
||||
|
||||
let count = 0;
|
||||
|
||||
@@ -227,6 +227,8 @@ export type UserSessionWithUser = {
|
||||
export const hybridRouter = Router();
|
||||
hybridRouter.use(verifySessionRemoteExitNodeMiddleware);
|
||||
|
||||
// TODO: ADD RATE LIMITING TO THESE ROUTES AS NEEDED BASED ON USAGE PATTERNS
|
||||
|
||||
hybridRouter.get(
|
||||
"/general-config",
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
|
||||
@@ -68,7 +68,7 @@ export async function sendSupportEmail(
|
||||
{
|
||||
name: req.user?.email || "Support User",
|
||||
to: "support@pangolin.net",
|
||||
from: req.user?.email || config.getNoReplyEmail(),
|
||||
from: config.getNoReplyEmail(),
|
||||
subject: `Support Request: ${subject}`
|
||||
}
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user