refactor: improve maintainability by reducing LOC/reuse
This commit is contained in:
@@ -1,33 +1,13 @@
|
||||
import { Elysia, t } from "elysia";
|
||||
import { createHmac, randomBytes } from "crypto";
|
||||
import sql from "../db";
|
||||
import { createRateLimiter } from "../utils/rate-limit";
|
||||
import { getPlanLimits } from "../utils/plans";
|
||||
import { createRateLimiter } from "../../../shared/rate-limit";
|
||||
import { getPlanLimits } from "../../../shared/plans";
|
||||
import { generateKey, hashEmail, resolveKey as sharedResolveKey, extractAuthKey, COOKIE_OPTS } from "../../../shared/auth";
|
||||
|
||||
// ── Per-IP rate limiting for auth endpoints ───────────────────────────
|
||||
const checkAuthRateLimit = createRateLimiter();
|
||||
|
||||
const EMAIL_HMAC_KEY = process.env.EMAIL_HMAC_KEY || "pingql-default-hmac-key";
|
||||
|
||||
function generateKey(): string {
|
||||
return randomBytes(32).toString("base64url");
|
||||
}
|
||||
|
||||
function hashEmail(email: string): string {
|
||||
return createHmac("sha256", EMAIL_HMAC_KEY).update(email.toLowerCase().trim()).digest("hex");
|
||||
}
|
||||
|
||||
async function resolveKey(key: string): Promise<{ accountId: string; keyId: string | null; plan: string } | null> {
|
||||
const [account] = await sql`SELECT id, plan FROM accounts WHERE key = ${key}`;
|
||||
if (account) return { accountId: account.id, keyId: null, plan: account.plan };
|
||||
|
||||
const [apiKey] = await sql`SELECT k.id, k.account_id, a.plan FROM api_keys k JOIN accounts a ON a.id = k.account_id WHERE k.key = ${key}`;
|
||||
if (apiKey) {
|
||||
sql`UPDATE api_keys SET last_used_at = now() WHERE id = ${apiKey.id}`.catch(() => {});
|
||||
return { accountId: apiKey.account_id, keyId: apiKey.id, plan: apiKey.plan };
|
||||
}
|
||||
|
||||
return null;
|
||||
async function resolveKey(key: string) {
|
||||
return sharedResolveKey(sql, key);
|
||||
}
|
||||
|
||||
export { resolveKey };
|
||||
@@ -35,11 +15,7 @@ export { resolveKey };
|
||||
export function requireAuth(app: Elysia) {
|
||||
return app
|
||||
.derive(async ({ headers, cookie, set }) => {
|
||||
const authHeader = headers["authorization"] ?? "";
|
||||
const bearer = authHeader.match(/^bearer\s+(.+)$/i)?.[1]?.trim();
|
||||
const cookieKey = cookie?.pingql_key?.value;
|
||||
|
||||
const key = bearer || cookieKey;
|
||||
const key = extractAuthKey(headers, cookie);
|
||||
if (!key) {
|
||||
set.status = 401;
|
||||
return { accountId: null as string | null, keyId: null as string | null, plan: "free" as string };
|
||||
@@ -59,15 +35,6 @@ export function requireAuth(app: Elysia) {
|
||||
});
|
||||
}
|
||||
|
||||
const COOKIE_OPTS = {
|
||||
httpOnly: true,
|
||||
secure: process.env.COOKIE_SECURE !== "false",
|
||||
sameSite: "none" as const,
|
||||
path: "/",
|
||||
domain: process.env.COOKIE_DOMAIN ?? ".pingql.com",
|
||||
maxAge: 60 * 60 * 24 * 30, // 30 days
|
||||
};
|
||||
|
||||
export const account = new Elysia({ prefix: "/account" })
|
||||
|
||||
.post("/login", async ({ body, cookie, set, request }) => {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import { Elysia } from "elysia";
|
||||
import sql from "../db";
|
||||
import { safeTokenCompare } from "../utils/token";
|
||||
import { safeTokenCompare } from "../../../shared/auth";
|
||||
|
||||
export async function pruneOldPings(retentionDays = 90) {
|
||||
const result = await sql`DELETE FROM pings WHERE checked_at < now() - ${retentionDays + ' days'}::interval`;
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Elysia, t } from "elysia";
|
||||
import { requireAuth } from "./auth";
|
||||
import sql from "../db";
|
||||
import { validateMonitorUrl } from "../utils/ssrf";
|
||||
import { getPlanLimits } from "../utils/plans";
|
||||
import { getPlanLimits } from "../../../shared/plans";
|
||||
|
||||
const MonitorBody = t.Object({
|
||||
name: t.String({ maxLength: 200, description: "Human-readable name" }),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Elysia, t } from "elysia";
|
||||
import sql from "../db";
|
||||
import { resolveKey } from "./auth";
|
||||
import { safeTokenCompare } from "../utils/token";
|
||||
import { extractAuthKey, safeTokenCompare } from "../../../shared/auth";
|
||||
|
||||
// ── SSE bus ───────────────────────────────────────────────────────────────────
|
||||
type SSEController = ReadableStreamDefaultController<Uint8Array>;
|
||||
@@ -121,9 +121,7 @@ export const ingest = new Elysia()
|
||||
|
||||
// Fetch response body for a specific ping
|
||||
.get("/pings/:id/body", async ({ params, headers, cookie, set }) => {
|
||||
const authHeader = headers["authorization"] ?? "";
|
||||
const bearer = authHeader.match(/^bearer\s+(.+)$/i)?.[1]?.trim();
|
||||
const key = bearer ?? cookie?.pingql_key?.value;
|
||||
const key = extractAuthKey(headers, cookie);
|
||||
if (!key) { set.status = 401; return { error: "Unauthorized" }; }
|
||||
|
||||
const resolved = await resolveKey(key);
|
||||
@@ -143,10 +141,7 @@ export const ingest = new Elysia()
|
||||
|
||||
// SSE: single stream for all of the account's monitors
|
||||
.get("/account/stream", async ({ headers, cookie }) => {
|
||||
const authHeader = headers["authorization"] ?? "";
|
||||
const bearer = authHeader.match(/^bearer\s+(.+)$/i)?.[1]?.trim();
|
||||
const key = bearer ?? cookie?.pingql_key?.value;
|
||||
|
||||
const key = extractAuthKey(headers, cookie);
|
||||
if (!key) return new Response(JSON.stringify({ error: "Unauthorized" }), { status: 401 });
|
||||
|
||||
const resolved = await resolveKey(key);
|
||||
|
||||
Reference in New Issue
Block a user