From 10457e96e88025141774f68f3fd501a447fbd419 Mon Sep 17 00:00:00 2001 From: amaliadwiy Date: Wed, 20 May 2026 12:23:38 +0800 Subject: [PATCH] feat: tambah autentikasi x-api-key pada NOC API dan ekstrak isValidApiKey ke shared lib --- src/app/api/ai/[[...slug]]/route.ts | 15 +-------------- src/app/api/noc/[[...slug]]/route.ts | 23 ++++++++++++++++++++++- src/lib/apiKey.ts | 15 +++++++++++++++ 3 files changed, 38 insertions(+), 15 deletions(-) create mode 100644 src/lib/apiKey.ts diff --git a/src/app/api/ai/[[...slug]]/route.ts b/src/app/api/ai/[[...slug]]/route.ts index 491be13..dad4a4f 100644 --- a/src/app/api/ai/[[...slug]]/route.ts +++ b/src/app/api/ai/[[...slug]]/route.ts @@ -1,3 +1,4 @@ +import { isValidApiKey } from "@/lib/apiKey"; import { prisma } from "@/module/_global"; import cors from "@elysiajs/cors"; import { swagger } from "@elysiajs/swagger"; @@ -6,20 +7,6 @@ import _ from "lodash"; import moment from "moment"; import "moment/locale/id"; -const CACHE_TTL_MS = 60_000; -let apiKeyCache: Set = new Set(); -let cacheExpiresAt = 0; - -async function isValidApiKey(incoming: string): Promise { - const now = Date.now(); - if (now > cacheExpiresAt) { - const rows = await prisma.apiKey.findMany({ where: { isActive: true }, select: { key: true } }); - apiKeyCache = new Set(rows.map((r) => r.key)); - cacheExpiresAt = now + CACHE_TTL_MS; - } - return apiKeyCache.has(incoming); -} - const AiServer = new Elysia({ prefix: "/api/ai" }) .use(cors({ origin: "*", diff --git a/src/app/api/noc/[[...slug]]/route.ts b/src/app/api/noc/[[...slug]]/route.ts index 68df427..54e0e4c 100644 --- a/src/app/api/noc/[[...slug]]/route.ts +++ b/src/app/api/noc/[[...slug]]/route.ts @@ -1,3 +1,4 @@ +import { isValidApiKey } from "@/lib/apiKey"; import { prisma } from "@/module/_global"; import cors from "@elysiajs/cors"; import { swagger } from "@elysiajs/swagger"; @@ -11,20 +12,40 @@ const NocServer = new Elysia({ prefix: "/api/noc" }) .use(cors({ origin: "*", methods: ["GET", "POST", "OPTIONS"], + allowedHeaders: ["Content-Type", "x-api-key"], })) .use(swagger({ - path: "/docs", // Karena prefix instance adalah /api/noc, maka ini akan diakses di /api/noc/docs + path: "/docs", documentation: { info: { title: "Sistem Desa Mandiri - NOC API", version: "1.0.0", description: "API Khusus untuk kebutuhan NOC (Network Operation Center) dan Monitoring Desa", }, + components: { + securitySchemes: { + ApiKeyAuth: { + type: "apiKey", + in: "header", + name: "x-api-key", + }, + }, + }, + security: [{ ApiKeyAuth: [] }], tags: [ { name: "NOC", description: "Endpoint khusus monitoring" } ] } })) + .onBeforeHandle(async ({ request, set, path }) => { + if (path.startsWith("/api/noc/docs")) return; + + const incoming = request.headers.get("x-api-key"); + if (!incoming || !(await isValidApiKey(incoming))) { + set.status = 401; + return { success: false, message: "Unauthorized" }; + } + }) // ── GET /api/noc/active-divisions ────────────────────────────────────────── .get( diff --git a/src/lib/apiKey.ts b/src/lib/apiKey.ts new file mode 100644 index 0000000..fa6788a --- /dev/null +++ b/src/lib/apiKey.ts @@ -0,0 +1,15 @@ +import { prisma } from "@/module/_global"; + +const CACHE_TTL_MS = 60_000; +let apiKeyCache: Set = new Set(); +let cacheExpiresAt = 0; + +export async function isValidApiKey(incoming: string): Promise { + const now = Date.now(); + if (now > cacheExpiresAt) { + const rows = await prisma.apiKey.findMany({ where: { isActive: true }, select: { key: true } }); + apiKeyCache = new Set(rows.map((r) => r.key)); + cacheExpiresAt = now + CACHE_TTL_MS; + } + return apiKeyCache.has(incoming); +}