+29
src/flags.ts
+29
src/flags.ts
···
35
35
name: string;
36
36
description: string;
37
37
paths: string[]; // The paths this flag blocks
38
+
redact?: Record<string, string[]>; // path -> fields to strip from JSON
38
39
}
39
40
40
41
export interface ServiceDefinition {
···
147
148
148
149
return false;
149
150
}
151
+
152
+
// Get fields to redact from a JSON response based on host and path
153
+
export function getRedactions(host: string, path: string): string[] {
154
+
const config = getConfig();
155
+
const fields: string[] = [];
156
+
157
+
for (const [serviceId, service] of Object.entries(config.services)) {
158
+
if (!host.includes(serviceId) && !serviceId.includes(host)) {
159
+
continue;
160
+
}
161
+
162
+
for (const [flagId, flag] of Object.entries(service.flags)) {
163
+
if (!getFlagStatus(flagId)) {
164
+
continue;
165
+
}
166
+
167
+
if (flag.redact) {
168
+
for (const [redactPath, redactFields] of Object.entries(flag.redact)) {
169
+
if (path === redactPath || path.startsWith(redactPath + "?")) {
170
+
fields.push(...redactFields);
171
+
}
172
+
}
173
+
}
174
+
}
175
+
}
176
+
177
+
return fields;
178
+
}
+35
src/index.ts
+35
src/index.ts
···
19
19
setFlag,
20
20
getFlagDefinition,
21
21
shouldBlock,
22
+
getRedactions,
22
23
} from "./flags";
23
24
24
25
import homepage from "../public/index.html";
···
55
56
}
56
57
57
58
return c.text("OK", 200);
59
+
});
60
+
61
+
// Proxy JSON endpoint and redact configured fields
62
+
app.get("/proxy/*", async (c) => {
63
+
const host = c.req.header("X-Orig-Host") || c.req.header("Host") || "";
64
+
const origPath = c.req.header("X-Orig-Path") || "";
65
+
const backendUrl = c.req.header("X-Backend-Url");
66
+
67
+
if (!backendUrl) {
68
+
return c.text("Missing X-Backend-Url header", 400);
69
+
}
70
+
71
+
const res = await fetch(backendUrl);
72
+
if (!res.ok) {
73
+
return c.text("Backend error", res.status);
74
+
}
75
+
76
+
const data = await res.json();
77
+
78
+
// Redact fields based on config
79
+
const fieldsToRedact = getRedactions(host, origPath);
80
+
for (const field of fieldsToRedact) {
81
+
if (field in data) {
82
+
if (Array.isArray(data[field])) {
83
+
data[field] = [];
84
+
} else if (typeof data[field] === "object" && data[field] !== null) {
85
+
data[field] = {};
86
+
} else {
87
+
delete data[field];
88
+
}
89
+
}
90
+
}
91
+
92
+
return c.json(data);
58
93
});
59
94
60
95
app.get("/auth/login", (c) => {