From 96a68b46ae83751bc537ae2d0f6a4007038c1a74 Mon Sep 17 00:00:00 2001 From: Izan Gil <66965250+SrIzan10@users.noreply.github.com> Date: Thu, 12 Mar 2026 21:53:41 +0100 Subject: [PATCH] fix: add authentication to metrics routes --- apps/chat/src/index.ts | 15 +++++++++++++- apps/web/src/app/api/metrics/route.ts | 28 ++++++++++++++++++++++++++- compose.yml | 2 ++ observability/prometheus.yml | 6 ++++++ 4 files changed, 49 insertions(+), 2 deletions(-) diff --git a/apps/chat/src/index.ts b/apps/chat/src/index.ts index 8eed3cf..cdc9039 100644 --- a/apps/chat/src/index.ts +++ b/apps/chat/src/index.ts @@ -37,6 +37,7 @@ import type { ChatSocket, ChatUser, } from './types/chat.js'; +import { basicAuth } from 'hono/basic-auth'; const redis = getRedisConnection(); const MESSAGE_HISTORY_SIZE = 100; @@ -300,13 +301,25 @@ async function deleteMessageFromHistory(targetUsername: string, msgId: string): const app = new Hono(); const { injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app }); +if (process.env.NODE_ENV === 'production') { + app.use('/metrics', basicAuth({ username: process.env.METRICS_USER!, password: process.env.METRICS_PASS! })); +} app.get('/', async (c) => { return c.text(threed); }); app.get('/up', async (c) => { - return c.text('it works'); + return c.text('hello world'); +}); + +app.get('/metrics', async () => { + return new Response(await chatMetricsRegistry.metrics(), { + headers: { + 'Content-Type': chatMetricsRegistry.contentType, + 'Cache-Control': 'no-store, no-cache, must-revalidate, proxy-revalidate', + }, + }); }); app.get( diff --git a/apps/web/src/app/api/metrics/route.ts b/apps/web/src/app/api/metrics/route.ts index 5510a74..563b214 100644 --- a/apps/web/src/app/api/metrics/route.ts +++ b/apps/web/src/app/api/metrics/route.ts @@ -1,9 +1,16 @@ import { webMetricsRegistry } from '@/lib/metrics'; +import { NextRequest, NextResponse } from 'next/server'; export const runtime = 'nodejs'; export const dynamic = 'force-dynamic'; -export async function GET() { +export async function GET(req: NextRequest) { + if (process.env.NODE_ENV === 'production' && !isAuthenticated(req)) { + return new NextResponse('Authentication required', { + status: 401, + headers: { 'WWW-Authenticate': 'Basic' }, + }); + } return new Response(await webMetricsRegistry.metrics(), { headers: { 'Content-Type': webMetricsRegistry.contentType, @@ -11,3 +18,22 @@ export async function GET() { }, }); } + +// source: https://vancelucas.com/blog/how-to-add-http-basic-auth-to-next-js/ +function isAuthenticated(req: NextRequest) { + const authheader = req.headers.get('authorization') || req.headers.get('Authorization'); + + if (!authheader) { + return false; + } + + const auth = Buffer.from(authheader.split(' ')[1], 'base64').toString().split(':'); + const user = auth[0]; + const pass = auth[1]; + + if (user == process.env.METRICS_USER && pass == process.env.METRICS_PASS) { + return true; + } else { + return false; + } +} \ No newline at end of file diff --git a/compose.yml b/compose.yml index 0818668..e80593a 100644 --- a/compose.yml +++ b/compose.yml @@ -77,6 +77,8 @@ services: - '--config.file=/etc/prometheus/prometheus.yml' - '--storage.tsdb.path=/prometheus' - '--web.enable-lifecycle' + env_file: + - .env volumes: - './observability/prometheus.yml:/etc/prometheus/prometheus.yml:ro' - 'prometheus_data:/prometheus' diff --git a/observability/prometheus.yml b/observability/prometheus.yml index a221c0f..ed29dc1 100644 --- a/observability/prometheus.yml +++ b/observability/prometheus.yml @@ -5,12 +5,18 @@ global: scrape_configs: - job_name: web metrics_path: /api/metrics + basic_auth: + username: ${METRICS_USER} + password: ${METRICS_PASS} static_configs: - targets: - hctv:3000 - job_name: chat metrics_path: /metrics + basic_auth: + username: ${METRICS_USER} + password: ${METRICS_PASS} static_configs: - targets: - chat:8000