Promote GRO-2294 to UAT: Route Optimization security hardening (#194)
CI / Lint & Typecheck (push) Successful in 28s
CI / Test (push) Successful in 29s
CI / Build & Push Docker Images (push) Successful in 39s
CI / Test (pull_request) Successful in 25s
CI / Lint & Typecheck (pull_request) Successful in 37s
CI / Build & Push Docker Images (pull_request) Successful in 1m8s

This commit was merged in pull request #194.
This commit is contained in:
2026-06-09 06:27:17 +00:00
parent 4868f18dfd
commit 2566fb8f20
5 changed files with 206 additions and 4 deletions
+10 -1
View File
@@ -12,6 +12,12 @@ import {
export const clientsRouter = new Hono<AppEnv>();
// Batch-geocode bounds (GRO-2294): default 50, hard cap 500. The cap bounds how
// long one synchronous request stays open and the per-request external API cost
// when routeOptimizationProvider = "google".
const GEOCODE_BATCH_DEFAULT_LIMIT = 50;
const GEOCODE_BATCH_MAX_LIMIT = 500;
type ClientRow = typeof clients.$inferSelect;
/**
@@ -185,12 +191,15 @@ clientsRouter.post("/:clientId/geocode", async (c) => {
clientsRouter.post("/geocode-batch", async (c) => {
const db = getDb();
const limitRaw = c.req.query("limit");
let limit = 50;
let limit = GEOCODE_BATCH_DEFAULT_LIMIT;
if (limitRaw !== undefined) {
limit = Number(limitRaw);
if (!Number.isFinite(limit) || limit <= 0) {
return c.json({ error: "limit must be a positive integer" }, 400);
}
// Clamp to the documented maximum to bound synchronous request duration
// and (for the Google provider) per-request external API cost.
limit = Math.min(Math.floor(limit), GEOCODE_BATCH_MAX_LIMIT);
}
const summary = await geocodeUngeocodedClients(db, limit);
return c.json(summary);
+14 -2
View File
@@ -7,6 +7,17 @@ import { requireSuperUser } from "../middleware/rbac.js";
export const settingsRouter = new Hono();
type BusinessSettingsRow = typeof businessSettings.$inferSelect;
// Strip the encrypted googleMapsApiKey ciphertext from settings responses
// (GRO-2294, defense-in-depth). The secret is never needed client-side; it is
// only written via the dedicated provider-config endpoint.
function redactSettings(row: BusinessSettingsRow) {
const rest: Partial<BusinessSettingsRow> = { ...row };
delete rest.googleMapsApiKey;
return rest;
}
// GET /api/admin/settings — return current business settings
settingsRouter.get("/", async (c) => {
const db = getDb();
@@ -14,9 +25,10 @@ settingsRouter.get("/", async (c) => {
if (!row) {
// Auto-create default settings if none exist
const [created] = await db.insert(businessSettings).values({}).returning();
return c.json(created);
if (!created) throw new Error("Failed to create default settings");
return c.json(redactSettings(created));
}
return c.json(row);
return c.json(redactSettings(row));
});
const hexColorRegex = /^#[0-9a-fA-F]{6}$/;