Skip to content

Commit 66707b1

Browse files
Merge PR #725
2 parents 45952e6 + 2eb112a commit 66707b1

4 files changed

Lines changed: 22 additions & 14 deletions

File tree

src/config/env-schema.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ const baseSchema = z.object({
5050
// ── Markets CORS ─────────────────────────────────────────
5151
MARKETS_CORS_ALLOWED_ORIGINS: z.string().default(""),
5252

53-
// ── Audit CORS ──────────────────────────────────────────
54-
AUDIT_CORS_ALLOWED_ORIGINS: z.string().default(""),
53+
// ── Notifications CORS ──────────────────────────────────
54+
NOTIFICATIONS_CORS_ALLOWED_ORIGINS: z.string().default(""),
5555

5656
// ── Geo-blocking ──────────────────────────────────────────
5757
GEO_BLOCKED_COUNTRIES: z.string().default("").transform((val) =>

src/middleware/cors.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -137,26 +137,26 @@ export function marketsCors(): ReturnType<typeof createCorsAllowlistMiddleware>
137137
}
138138

139139
/**
140-
* Pre-configured CORS middleware for the audit endpoint.
141-
* Reads allowed origins from the `AUDIT_CORS_ALLOWED_ORIGINS` env variable.
142-
* When the allowlist is empty, all cross-origin requests to /api/audit are denied.
140+
* Pre-configured CORS middleware for the notifications endpoint.
141+
* Reads allowed origins from the `NOTIFICATIONS_CORS_ALLOWED_ORIGINS` env variable.
142+
* When the allowlist is empty, all cross-origin requests to /api/notifications are denied.
143143
*/
144-
let auditCorsMiddleware: ReturnType<typeof createCorsAllowlistMiddleware> | null = null;
144+
let notificationsCorsMiddleware: ReturnType<typeof createCorsAllowlistMiddleware> | null = null;
145145

146-
export function auditCors(): ReturnType<typeof createCorsAllowlistMiddleware> {
147-
if (!auditCorsMiddleware) {
148-
const raw = env.AUDIT_CORS_ALLOWED_ORIGINS ?? "";
146+
export function notificationsCors(): ReturnType<typeof createCorsAllowlistMiddleware> {
147+
if (!notificationsCorsMiddleware) {
148+
const raw = env.NOTIFICATIONS_CORS_ALLOWED_ORIGINS ?? "";
149149
const allowedOrigins = raw
150150
.split(",")
151151
.map((o) => o.trim())
152152
.filter((o) => o.length > 0);
153-
auditCorsMiddleware = createCorsAllowlistMiddleware({
153+
notificationsCorsMiddleware = createCorsAllowlistMiddleware({
154154
allowedOrigins,
155155
allowCredentials: true,
156156
maxAgeSeconds: 600,
157157
});
158158
}
159-
return auditCorsMiddleware;
159+
return notificationsCorsMiddleware;
160160
}
161161

162162
export const enforceCors = marketsCors();

src/routes/notifications.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import {
1313
} from "../services/notificationPrefs";
1414
import { markNotificationsAsRead } from "../services/notificationService";
1515
import { idempotency } from "../middleware/idempotency";
16+
import { RouteErrorFactory } from "../errors";
17+
import { notificationsCors } from "../middleware/cors";
1618
import { notificationsMetricsMiddleware } from "../metrics/notificationsMetrics";
1719

1820
const notificationCategorySchema = z.enum(notificationCategories);
@@ -51,6 +53,9 @@ const markReadBodySchema = z
5153

5254
export const notificationsRouter = Router();
5355

56+
// Enforce CORS allowlist early so unapproved origins are rejected
57+
// before any processing (preflight responses cached via Access-Control-Max-Age).
58+
notificationsRouter.use(notificationsCors());
5459
notificationsRouter.use(requireAuth);
5560
notificationsRouter.use(notificationsMetricsMiddleware);
5661

@@ -94,7 +99,7 @@ notificationsRouter.patch(
9499
},
95100
"notification_preferences_validation_failed",
96101
);
97-
throw RouteErrorFactory.validation("Invalid request body");
102+
return next(RouteErrorFactory.validation("Invalid request body"));
98103
}
99104

100105
try {

tests/notifications.test.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
jest.mock("../src/middleware/cors", () => ({
2+
notificationsCors: () => (_req: any, _res: any, next: any) => next(),
3+
}));
4+
15
jest.mock("../src/middleware/requireAuth", () => ({
26
requireAuth: (req: any, _res: any, next: any) => {
37
req.user = { id: "user-123", stellarAddress: "GTEST" };
@@ -76,9 +80,8 @@ describe("notifications preferences routes", () => {
7680
.patch("/api/notifications/preferences")
7781
.send({ preferences: [{ category: "nope", channel: "email", enabled: true }] });
7882

79-
expect(res.status).toBe(400);
83+
expect(res.status).toBe(422);
8084
expect(res.body.error.code).toBe("validation_error");
81-
expect(Array.isArray(res.body.error.details)).toBe(true);
8285
expect(mockPatchNotificationPreferences).not.toHaveBeenCalled();
8386
});
8487

0 commit comments

Comments
 (0)