Files
ts-scripts/access-guard-ts/main.ts
2025-01-19 01:23:14 +08:00

206 lines
5.6 KiB
TypeScript
Executable File

#!/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<void> {
await globalIpAddressMapMutex.acquire();
try {
globalIpAddressMap[ip] = new Date().getTime();
} finally {
globalIpAddressMapMutex.release();
}
}
async function cleanAndGetIpAddresses(): Promise<Array<string>> {
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<Response>;
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);
});