mirror of
https://github.com/elder-plinius/LEAKHUB.git
synced 2026-02-12 16:52:53 +00:00
Merge pull request #2 from 2alf/main
[1] Stored XSS fix, [2] X-Frame add, [3] Cron job to remove clustering
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -86,7 +86,7 @@ jspm_packages/
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
public
|
||||
|
||||
|
||||
# Storybook build outputs
|
||||
.out
|
||||
|
||||
12
convex/crons.ts
Normal file
12
convex/crons.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { cronJobs } from "convex/server";
|
||||
import { internal } from "./_generated/api";
|
||||
|
||||
const crons = cronJobs();
|
||||
|
||||
crons.daily(
|
||||
"delete closed requests after a day",
|
||||
{ hourUTC: 1, minuteUTC: 0 },
|
||||
internal.requests.deleteOldClosedRequests
|
||||
);
|
||||
|
||||
export default crons;
|
||||
@@ -236,6 +236,13 @@ export const insertLeak = mutation({
|
||||
};
|
||||
}
|
||||
|
||||
if (args.url && !/^https?:\/\/.+/i.test(args.url)) {
|
||||
return {
|
||||
success: false as const,
|
||||
error: "Invalid URL.",
|
||||
};
|
||||
}
|
||||
|
||||
// Validate required fields
|
||||
if (!args.targetName || !args.provider || !args.leakText) {
|
||||
return {
|
||||
@@ -537,6 +544,7 @@ export const applyConsensusResult = internalMutation({
|
||||
|
||||
await ctx.db.patch(args.requestId, {
|
||||
closed: true,
|
||||
closedBy: "verification",
|
||||
});
|
||||
|
||||
const submitter = await ctx.db.get(leak.submittedBy);
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import { v } from "convex/values";
|
||||
import { getAuthUserId } from "@convex-dev/auth/server";
|
||||
import { query, mutation } from "./_generated/server";
|
||||
import { query, mutation, internalMutation } from "./_generated/server";
|
||||
import { Id } from "./_generated/dataModel";
|
||||
|
||||
// url validation helper
|
||||
function isValidUrl(url: string): boolean {
|
||||
return /^https?:\/\/.+/i.test(url); // only http and https
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all open (non-closed) requests from the database.
|
||||
* Returns requests with submitter names populated.
|
||||
@@ -104,6 +109,7 @@ export const getUserOpenRequests = query({
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Create a new request for a leak target.
|
||||
* Validates that:
|
||||
@@ -147,6 +153,13 @@ export const createRequest = mutation({
|
||||
};
|
||||
}
|
||||
|
||||
if (!isValidUrl(args.targetUrl)) {
|
||||
return {
|
||||
success: false as const,
|
||||
error: "The provided target URL is not valid. Please provide a valid URL starting with http:// or https://",
|
||||
};
|
||||
}
|
||||
|
||||
// Check if a request with the same target name already exists (case-insensitive)
|
||||
// Query ALL requests, not just open ones
|
||||
const allRequests = await ctx.db.query("requests").collect();
|
||||
@@ -264,6 +277,7 @@ export const closeRequest = mutation({
|
||||
// Close the request
|
||||
await ctx.db.patch(args.requestId, {
|
||||
closed: true,
|
||||
closedBy: "user",
|
||||
});
|
||||
|
||||
return { success: true as const };
|
||||
@@ -439,3 +453,41 @@ export const getRequestsWithVerificationStatus = query({
|
||||
return requestsWithStatus;
|
||||
},
|
||||
});
|
||||
|
||||
/*
|
||||
Delete closed requests after a day --> internal mutation called by a cron job
|
||||
*/
|
||||
|
||||
export const deleteOldClosedRequests = internalMutation({
|
||||
args: {},
|
||||
returns: v.number(),
|
||||
handler: async (ctx) => {
|
||||
|
||||
//const daysAgo = Date.now() - 30 * 24 * 60 * 60 * 1000; // 30 days in milliseconds
|
||||
const daysAgo = Date.now() - 24 * 60 * 60 * 1000; // 1 day in milliseconds
|
||||
|
||||
const oldClosedRequests = await ctx.db
|
||||
.query("requests")
|
||||
.withIndex("by_closed", (q) => q.eq("closed", true))
|
||||
.collect();
|
||||
|
||||
let deletedCount = 0;
|
||||
|
||||
for (const request of oldClosedRequests) {
|
||||
if (request._creationTime < daysAgo && request.closedBy === "user") {
|
||||
// remove from users array of requests
|
||||
if (request.submittedBy) {
|
||||
const user = await ctx.db.get(request.submittedBy);
|
||||
if (user && user.requests) {
|
||||
await ctx.db.patch(request.submittedBy, {
|
||||
requests: user.requests.filter((id) => id !== request._id),
|
||||
});
|
||||
}
|
||||
}
|
||||
await ctx.db.delete(request._id);
|
||||
deletedCount++;
|
||||
}
|
||||
}
|
||||
return deletedCount;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -43,6 +43,7 @@ const requests = defineTable({
|
||||
),
|
||||
targetUrl: v.string(),
|
||||
closed: v.boolean(),
|
||||
closedBy: v.optional(v.union(v.literal("user"), v.literal("verification"))),
|
||||
leaks: v.array(v.id("leaks")),
|
||||
submittedBy: v.id("users"),
|
||||
})
|
||||
|
||||
3
public/_headers
Normal file
3
public/_headers
Normal file
@@ -0,0 +1,3 @@
|
||||
/*
|
||||
X-Frame-Options: DENY
|
||||
Content-Security-Policy: frame-ancestors 'none'
|
||||
Reference in New Issue
Block a user