security: remove dead free signup route (abuse vector) + add test
All checks were successful
Build & Deploy to Staging / Build & Deploy to Staging (push) Successful in 9m39s
All checks were successful
Build & Deploy to Staging / Build & Deploy to Staging (push) Successful in 9m39s
The /v1/signup/free endpoint was still mounted despite free tier being removed in v0.3.0. Anyone could generate unlimited free API keys. - Removed signup route registration from index.ts - Deleted src/routes/signup.ts (dead code) - Added signup-removed.test.ts verifying 404 on signup endpoints - Cleaned up leaked test key from production DB 334 tests passing.
This commit is contained in:
parent
740c70f905
commit
f3a363fb17
3 changed files with 44 additions and 31 deletions
|
|
@ -16,7 +16,7 @@ import { loadKeys, getAllKeys } from "./services/keys.js";
|
||||||
import { initDatabase, pool } from "./services/db.js";
|
import { initDatabase, pool } from "./services/db.js";
|
||||||
import { billingRouter } from "./routes/billing.js";
|
import { billingRouter } from "./routes/billing.js";
|
||||||
import { statusRouter } from "./routes/status.js";
|
import { statusRouter } from "./routes/status.js";
|
||||||
import { signupRouter } from "./routes/signup.js";
|
|
||||||
import { usageRouter } from "./routes/usage.js";
|
import { usageRouter } from "./routes/usage.js";
|
||||||
import { openapiSpec } from "./docs/openapi.js";
|
import { openapiSpec } from "./docs/openapi.js";
|
||||||
|
|
||||||
|
|
@ -96,7 +96,7 @@ app.use("/health", healthRouter);
|
||||||
app.use("/v1/billing", billingRouter);
|
app.use("/v1/billing", billingRouter);
|
||||||
app.use("/status", statusRouter);
|
app.use("/status", statusRouter);
|
||||||
app.use("/v1/playground", playgroundRouter);
|
app.use("/v1/playground", playgroundRouter);
|
||||||
app.use("/v1/signup", signupRouter);
|
|
||||||
|
|
||||||
// Authenticated routes
|
// Authenticated routes
|
||||||
app.use("/v1/usage", usageRouter);
|
app.use("/v1/usage", usageRouter);
|
||||||
|
|
|
||||||
42
src/routes/__tests__/signup-removed.test.ts
Normal file
42
src/routes/__tests__/signup-removed.test.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { describe, it, expect, vi, beforeAll } from "vitest";
|
||||||
|
import request from "supertest";
|
||||||
|
|
||||||
|
// Mock dependencies before importing app
|
||||||
|
vi.mock("../../services/db.js", () => ({
|
||||||
|
initDatabase: vi.fn(),
|
||||||
|
pool: { query: vi.fn(), end: vi.fn() },
|
||||||
|
}));
|
||||||
|
vi.mock("../../services/browser.js", () => ({
|
||||||
|
initBrowser: vi.fn(),
|
||||||
|
closeBrowser: vi.fn(),
|
||||||
|
}));
|
||||||
|
vi.mock("../../services/keys.js", () => ({
|
||||||
|
loadKeys: vi.fn(),
|
||||||
|
getAllKeys: vi.fn().mockReturnValue([]),
|
||||||
|
findKey: vi.fn(),
|
||||||
|
createKey: vi.fn(),
|
||||||
|
}));
|
||||||
|
vi.mock("../../middleware/usage.js", () => ({
|
||||||
|
loadUsageData: vi.fn(),
|
||||||
|
usageMiddleware: vi.fn((_req: any, _res: any, next: any) => next()),
|
||||||
|
}));
|
||||||
|
|
||||||
|
let app: any;
|
||||||
|
beforeAll(async () => {
|
||||||
|
const mod = await import("../../index.js");
|
||||||
|
app = mod.app;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Free signup removal (v0.3.0)", () => {
|
||||||
|
it("POST /v1/signup/free should return 404 — free tier removed", async () => {
|
||||||
|
const res = await request(app)
|
||||||
|
.post("/v1/signup/free")
|
||||||
|
.send({ email: "test@example.com" });
|
||||||
|
expect(res.status).toBe(404);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("GET /v1/signup should return 404", async () => {
|
||||||
|
const res = await request(app).get("/v1/signup");
|
||||||
|
expect(res.status).toBe(404);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
import { Router } from "express";
|
|
||||||
import { createKey } from "../services/keys.js";
|
|
||||||
import logger from "../services/logger.js";
|
|
||||||
|
|
||||||
export const signupRouter = Router();
|
|
||||||
|
|
||||||
// Simple signup: email → instant API key (no verification for now)
|
|
||||||
signupRouter.post("/free", async (req, res) => {
|
|
||||||
const { email } = req.body;
|
|
||||||
|
|
||||||
if (!email || typeof email !== "string" || !email.includes("@")) {
|
|
||||||
res.status(400).json({ error: "Valid email required" });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const key = await createKey(email.toLowerCase().trim(), "free");
|
|
||||||
logger.info({ email: email.slice(0, 3) + "***" }, "Free signup");
|
|
||||||
res.json({
|
|
||||||
apiKey: key.key,
|
|
||||||
tier: "free",
|
|
||||||
limit: 100,
|
|
||||||
message: "Your API key is ready! 100 free screenshots/month.",
|
|
||||||
});
|
|
||||||
} catch (err: any) {
|
|
||||||
logger.error({ err }, "Signup failed");
|
|
||||||
res.status(500).json({ error: "Signup failed" });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue