Explorar o código

ref: better IP resolution and store them hashed in mem

Sv443 hai 5 meses
pai
achega
547f3b77ec
Modificáronse 5 ficheiros con 44 adicións e 5 borrados
  1. 4 1
      CHANGELOG.md
  2. 2 0
      package.json
  3. 18 0
      pnpm-lock.yaml
  4. 5 2
      src/server.ts
  5. 15 2
      src/utils.ts

+ 4 - 1
CHANGELOG.md

@@ -1,9 +1,12 @@
 ### v2.0.0
 **Features:**
 - Added `?redirect` parameter for automatic HTTP redirection instead of returning a JSON response ([#22](https://github.com/Sv443/geniURL/issues/22))
+
 **Changes:**
-- Removed fuzzy filtering and `?disableFuzzy` and `?threshold` parameters altogether (to maybe be added back in the future as an opt-in feature) ([#24](https://github.com/Sv443/geniURL/issues/24))
+- **Breaking:** Removed fuzzy filtering and `?disableFuzzy` and `?threshold` parameters altogether (to maybe be added back in the future as an opt-in feature) ([#24](https://github.com/Sv443/geniURL/issues/24))
+- geniURL v2's public instance now requires using the base path `api.sv443.net/geniurl/v2/`. All other requests will be redirected to the old version which will be deprecated soon.
 - Reduced ratelimit budget from 25 requests every 30 seconds to 20 requests
+- IP addresses are now hashed before being stored in the ratelimit cache in memory
 
 <br>
 

+ 2 - 0
package.json

@@ -41,6 +41,7 @@
     "kleur": "^4.1.5",
     "nanoid": "^3.3.7",
     "rate-limiter-flexible": "^2.4.2",
+    "request-ip": "^3.3.0",
     "svcorelib": "^1.18.2",
     "tcp-port-used": "^1.0.2"
   },
@@ -49,6 +50,7 @@
     "@types/cors": "^2.8.17",
     "@types/express": "^4.17.21",
     "@types/node": "^20.17.6",
+    "@types/request-ip": "^0.0.41",
     "@types/tcp-port-used": "^1.0.4",
     "@typescript-eslint/eslint-plugin": "^6.21.0",
     "@typescript-eslint/parser": "^6.21.0",

+ 18 - 0
pnpm-lock.yaml

@@ -35,6 +35,9 @@ importers:
       rate-limiter-flexible:
         specifier: ^2.4.2
         version: 2.4.2
+      request-ip:
+        specifier: ^3.3.0
+        version: 3.3.0
       svcorelib:
         specifier: ^1.18.2
         version: 1.18.2
@@ -54,6 +57,9 @@ importers:
       '@types/node':
         specifier: ^20.17.6
         version: 20.17.6
+      '@types/request-ip':
+        specifier: ^0.0.41
+        version: 0.0.41
       '@types/tcp-port-used':
         specifier: ^1.0.4
         version: 1.0.4
@@ -478,6 +484,9 @@ packages:
   '@types/[email protected]':
     resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==}
 
+  '@types/[email protected]':
+    resolution: {integrity: sha512-Qzz0PM2nSZej4lsLzzNfADIORZhhxO7PED0fXpg4FjXiHuJ/lMyUg+YFF5q8x9HPZH3Gl6N+NOM8QZjItNgGKg==}
+
   '@types/[email protected]':
     resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==}
 
@@ -1756,6 +1765,9 @@ packages:
     resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
     engines: {node: '>=8.10.0'}
 
+  [email protected]:
+    resolution: {integrity: sha512-cA6Xh6e0fDBBBwH77SLJaJPBmD3nWVAcF9/XAcsrIHdjhFzFiB5aNQFytdjCGPezU3ROwrR11IddKAM08vohxA==}
+
   [email protected]:
     resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
     engines: {node: '>=0.10.0'}
@@ -2620,6 +2632,10 @@ snapshots:
 
   '@types/[email protected]': {}
 
+  '@types/[email protected]':
+    dependencies:
+      '@types/node': 20.17.6
+
   '@types/[email protected]': {}
 
   '@types/[email protected]':
@@ -4127,6 +4143,8 @@ snapshots:
     dependencies:
       picomatch: 2.3.1
 
+  [email protected]: {}
+
   [email protected]: {}
 
   [email protected]:

+ 5 - 2
src/server.ts

@@ -5,11 +5,12 @@ import helmet from "helmet";
 import { RateLimiterMemory, RateLimiterRes } from "rate-limiter-flexible";
 import k from "kleur";
 import cors from "cors";
+import { getClientIp } from "request-ip";
 
 import packageJson from "../package.json";
 import { error } from "./error";
 import { initRouter } from "./routes";
-import { respond } from "./utils";
+import { hashStr, respond } from "./utils";
 import { rateLimitOptions } from "./constants";
 
 const { env } = process;
@@ -74,7 +75,9 @@ export async function init() {
       res.setHeader("X-RateLimit-Reset", new Date(Date.now() + rateLimiterRes.msBeforeNext).toISOString());
     };
 
-    rateLimiter.consume(req.ip)
+    const ipHash = await hashStr(getClientIp(req) ?? "IP_RESOLUTION_ERROR");
+
+    rateLimiter.consume(ipHash)
       .then((rateLimiterRes: RateLimiterRes) => {
         setRateLimitHeaders(rateLimiterRes);
         return next();

+ 15 - 2
src/utils.ts

@@ -1,3 +1,4 @@
+import { createHash } from "node:crypto";
 import { Response } from "express";
 import { Stringifiable, byteLength } from "svcorelib";
 import { parse as jsonToXml } from "js2xmlparser";
@@ -14,8 +15,7 @@ export function paramValid(val: unknown): val is string {
  * @param data The data to send in the response body
  * @param format json / xml
  */
-export function respond(res: Response, type: ResponseType | number, data: Stringifiable | Record<string, unknown>, format = "json", matchesAmt?: number)
-{
+export function respond(res: Response, type: ResponseType | number, data: Stringifiable | Record<string, unknown>, format = "json", matchesAmt?: number) {
   let statusCode = 500;
   let error = true;
   let matches = null;
@@ -71,3 +71,16 @@ export function respond(res: Response, type: ResponseType | number, data: String
   contentLen > -1 && res.setHeader("Content-Length", contentLen);
   res.status(statusCode).send(finalData);
 }
+
+export function hashStr(str: string): Promise<string> {
+  return new Promise((resolve, reject) => {
+    try {
+      const hash = createHash("sha512");
+      hash.update(str);
+      resolve(hash.digest("hex"));
+    }
+    catch(e) {
+      reject(e);
+    }
+  });
+}