feat(api): implement rate limiting and SSRF protection across endpoints
- Added rate limiting to `reaction-users`, `search`, and `image-proxy` APIs to prevent abuse. - Introduced SSRF protection in `image-proxy` to block requests to private IP ranges. - Enhanced `link-preview` to use `linkedom` for HTML parsing and improved meta tag extraction. - Refactored authentication checks in various pages to utilize middleware for cleaner code. - Improved JWT key loading with error handling and security warnings for production. - Updated `authFetch` utility to handle token refresh more efficiently with deduplication. - Enhanced rate limiting utility to trust proxy headers from known sources. - Numerous layout / design changes
This commit is contained in:
@@ -1,7 +1,24 @@
|
||||
// requireAuthHook.js
|
||||
import { API_URL } from "@/config";
|
||||
|
||||
// WeakMap to cache auth promises per Astro context (request)
|
||||
const authCache = new WeakMap();
|
||||
|
||||
export const requireAuthHook = async (Astro) => {
|
||||
// Check if we already have a cached promise for this request
|
||||
if (authCache.has(Astro)) {
|
||||
return authCache.get(Astro);
|
||||
}
|
||||
|
||||
// Create a promise and cache it immediately to prevent race conditions
|
||||
const authPromise = performAuth(Astro);
|
||||
authCache.set(Astro, authPromise);
|
||||
|
||||
// Return the promise - all callers will await the same promise
|
||||
return authPromise;
|
||||
};
|
||||
|
||||
async function performAuth(Astro) {
|
||||
try {
|
||||
const cookieHeader = Astro.request.headers.get("cookie") ?? "";
|
||||
let res = await fetch(`${API_URL}/auth/id`, {
|
||||
@@ -21,19 +38,30 @@ export const requireAuthHook = async (Astro) => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const setCookieHeader = refreshRes.headers.get("set-cookie");
|
||||
let newCookieHeader = cookieHeader;
|
||||
|
||||
if (setCookieHeader) {
|
||||
const cookiesArray = setCookieHeader.split(/,(?=\s*\w+=)/);
|
||||
cookiesArray.forEach((c) => Astro.response.headers.append("set-cookie", c));
|
||||
|
||||
newCookieHeader = cookiesArray.map(c => c.split(";")[0]).join("; ");
|
||||
// Get all Set-Cookie headers (getSetCookie returns an array)
|
||||
let setCookies = [];
|
||||
if (typeof refreshRes.headers.getSetCookie === 'function') {
|
||||
setCookies = refreshRes.headers.getSetCookie();
|
||||
} else {
|
||||
// Fallback for older Node versions
|
||||
const setCookieHeader = refreshRes.headers.get("set-cookie");
|
||||
if (setCookieHeader) {
|
||||
// Split on comma followed by a cookie name (word=), avoiding splitting on Expires dates
|
||||
setCookies = setCookieHeader.split(/,(?=\s*[a-zA-Z_][a-zA-Z0-9_]*=)/);
|
||||
}
|
||||
}
|
||||
|
||||
if (setCookies.length === 0) {
|
||||
console.error("No set-cookie header found in refresh response");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Forward cookies to client
|
||||
setCookies.forEach((c) => Astro.response.headers.append("set-cookie", c.trim()));
|
||||
|
||||
// Build new cookie header for the retry request
|
||||
const newCookieHeader = setCookies.map(c => c.split(";")[0].trim()).join("; ");
|
||||
|
||||
res = await fetch(`${API_URL}/auth/id`, {
|
||||
headers: { Cookie: newCookieHeader },
|
||||
credentials: "include",
|
||||
@@ -52,4 +80,4 @@ export const requireAuthHook = async (Astro) => {
|
||||
console.error("[SSR] requireAuthHook error:", err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user