#!/usr/bin/env -S deno run --allow-env --allow-net import { Mutex } from "https://deno.land/x/async@v2.1.0/mutex.ts"; // Reference: // * https://docs.deno.com/runtime/reference/env_variables/ // * https://docs.deno.com/runtime/fundamentals/http_server/ const TOKEN = Deno.env.get("TOKEN"); const listen = { port: Deno.env.get("LISTEN_PORT") || 3000, hostname: Deno.env.get("LISTEN_HOSTNAME") || "127.0.0.1", }; const startTime = new Date().getTime(); // IP: Add time const globalIpAddressMap = {}; const globalIpAddressMapMutex = new Mutex(); async function addIpAddress(ip: string): Promise { await globalIpAddressMapMutex.acquire(); try { globalIpAddressMap[ip] = new Date().getTime(); } finally { globalIpAddressMapMutex.release(); } } async function cleanAndGetIpAddresses(): Promise> { await globalIpAddressMapMutex.acquire(); try { const currentTimeMillis = new Date().getTime(); const invalidKeys = []; const validKeys = []; for (let k in globalIpAddressMap) { const t = globalIpAddressMap[k]; if ((currentTimeMillis - t) > 60 * 60 * 1000) { invalidKeys.push(k); } else { validKeys.push(k); } } for (let i = 0; i < invalidKeys.length; i++) { delete globalIpAddressMap[invalidKeys[i]]; } return validKeys; } finally { globalIpAddressMapMutex.release(); } } const globalFilters: any[] = []; const globalHandlerMap = {}; type RequestHandler = (url: URL, req: any) => Promise; function registerFilter(handler: RequestHandler) { globalFilters.push(handler); } function registerHandler( method: string, path: string, handler: RequestHandler, ) { const requestIdent = `${method}::${path}`; if (globalHandlerMap[requestIdent] != null) { throw `Handler for ${method} ${path} exists.`; } globalHandlerMap[requestIdent] = handler; } function buildJsonResponse(status: number, body: any): Response { return new Response(JSON.stringify(body, null, " "), { status: status, headers: { "content-type": "application/json; charset=utf-8", }, }); } function buildOkJsonResponse(body: any): Response { return buildJsonResponse(200, body); } function notFoundHandler(url: URL, req: any): Response { return buildJsonResponse(404, { "error": "not_found", "message": "Resource not found.", }); } registerFilter(async (url: URL, req: any) => { if (url.pathname == "/version") { return null; } if (TOKEN == null) { return buildJsonResponse(500, { "error": "bad_token", "message": "Bad token.", }); } const token = url.searchParams.get("__token"); if (TOKEN !== token) { return buildJsonResponse(401, { "error": "invalid_token", "message": "Invalid token.", }); } return null; }); function formatHumanTime(time: number): string { const t = []; const leftMs = time % 1000; if (leftMs > 0) { t.push(`${leftMs}ms`); } const secs = Math.floor(time / 1000); const leftSecs = secs % 60; if (leftSecs > 0) { t.push(`${leftSecs}s`); } const mins = Math.floor(secs / 60); const leftMins = mins % 60; if (leftMins > 0) { t.push(`${leftMins}min`); } const hours = Math.floor(mins / 60); const leftHours = hours % 24; if (leftHours > 0) { t.push(`${leftHours}hour`); } const days = Math.floor(hours / 24); if (days > 0) { t.push(`${days}day`); } return t.reverse().join(" "); } registerHandler("GET", "/version", async (url, req) => { return buildOkJsonResponse({ "version": "0.0.1", "uptime": formatHumanTime(new Date().getTime() - startTime), }); }); interface AddIpAddressRequest { ip: string; } registerHandler("POST", "/ip_addresses", async (url, req) => { let addIpAddressRequest: AddIpAddressRequest; try { addIpAddressRequest = await req.json(); } catch (e) { return buildJsonResponse(400, { "error": "bad_request", "message": "Bad request.", }); } await addIpAddress(addIpAddressRequest.ip); return buildOkJsonResponse({}); }); registerHandler("GET", "/ip_addresses", async (url, req) => { const ipAddresses = await cleanAndGetIpAddresses(); return buildOkJsonResponse({ ipAddresses: ipAddresses, }); }); registerHandler("*", "/check_ip_address", async (url, req) => { const clientIp = req.headers.get("x-real-ip"); if (clientIp == null) { return buildJsonResponse(400, { "error": "bad_request", "message": "Bad request: no client IP", }); } const ipAddresses = await cleanAndGetIpAddresses(); if (ipAddresses.indexOf(clientIp) >= 0) { return buildOkJsonResponse({}); } return buildJsonResponse(401, { "error": "access_denied", "message": "Access denied: not allowed IP", }); }); Deno.serve(listen, async (req) => { const url = new URL(req.url); for (let i = 0; i < globalFilters.length; i++) { const response = await globalFilters[i](url, req); if (response != null) { return response; } } console.log("Handler request:", req.method, url.pathname); const req1 = `${req.method}::${url.pathname}`; const req2 = `*::${url.pathname}`; const req_handler = globalHandlerMap[req1] || globalHandlerMap[req2] || notFoundHandler; return await req_handler(url, req); }); // @SCRIPT-SIGNATURE-V1: yk-r1.ES256.20250124T004321+08:00.MEYCIQCymxaZsiML7bkV8GHM // ebW8aPxf5Iz/cc5d9HwAvvNbBQIhAOXCxdXyKJjTDNWv/IIZukzdO+nb1NXLPnBiwCfRpTiy