@discover-cloud/shared 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/authorization/index.d.ts +2 -0
  2. package/dist/authorization/index.js +18 -0
  3. package/dist/authorization/permission-cache.service.d.ts +12 -0
  4. package/dist/authorization/permission-cache.service.js +132 -0
  5. package/dist/authorization/permissions.d.ts +74 -0
  6. package/dist/authorization/permissions.js +171 -0
  7. package/dist/dto/auth-service.dtos.d.ts +5 -12
  8. package/dist/dto/response.dtos.d.ts +34 -27
  9. package/dist/dto/response.dtos.js +4 -0
  10. package/dist/dto/user-service.dtos.d.ts +9 -13
  11. package/dist/enums/domain.enums.d.ts +42 -0
  12. package/dist/enums/domain.enums.js +79 -0
  13. package/dist/enums/index.d.ts +2 -3
  14. package/dist/enums/index.js +2 -3
  15. package/dist/enums/permissions.enums.d.ts +124 -0
  16. package/dist/enums/permissions.enums.js +141 -0
  17. package/dist/errors/app-error.d.ts +19 -2
  18. package/dist/errors/app-error.js +17 -2
  19. package/dist/errors/http-errors.d.ts +18 -0
  20. package/dist/errors/http-errors.js +25 -1
  21. package/dist/http/index.d.ts +1 -0
  22. package/dist/http/index.js +17 -0
  23. package/dist/http/service-client.d.ts +23 -7
  24. package/dist/http/service-client.js +54 -22
  25. package/dist/index.d.ts +2 -1
  26. package/dist/index.js +2 -1
  27. package/dist/jwt/index.d.ts +1 -2
  28. package/dist/jwt/index.js +1 -2
  29. package/dist/jwt/internal-jwt-verifier.d.ts +35 -0
  30. package/dist/jwt/internal-jwt-verifier.js +162 -0
  31. package/dist/middleware/authorize.middleware.d.ts +22 -0
  32. package/dist/middleware/authorize.middleware.js +77 -0
  33. package/dist/middleware/error-handler.middleware.d.ts +23 -0
  34. package/dist/middleware/error-handler.middleware.js +52 -0
  35. package/dist/middleware/index.d.ts +4 -5
  36. package/dist/middleware/index.js +4 -5
  37. package/dist/middleware/request-id.middleware.d.ts +20 -0
  38. package/dist/middleware/request-id.middleware.js +34 -0
  39. package/dist/middleware/validate.middleware.d.ts +26 -0
  40. package/dist/middleware/validate.middleware.js +41 -0
  41. package/dist/types/express.types.d.ts +148 -0
  42. package/dist/types/express.types.js +42 -0
  43. package/dist/types/index.d.ts +1 -1
  44. package/dist/types/index.js +1 -1
  45. package/dist/utils/response.d.ts +2 -1
  46. package/dist/utils/response.js +6 -3
  47. package/package.json +3 -2
package/dist/index.d.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  export * from "./enums";
2
2
  export * from "./errors";
3
3
  export * from "./dto";
4
- export * from "./security";
4
+ export * from "./authorization";
5
5
  export * from "./utils";
6
6
  export * from "./middleware";
7
7
  export * from "./types";
8
8
  export * from "./jwt";
9
+ export * from "./http";
package/dist/index.js CHANGED
@@ -17,8 +17,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./enums"), exports);
18
18
  __exportStar(require("./errors"), exports);
19
19
  __exportStar(require("./dto"), exports);
20
- __exportStar(require("./security"), exports);
20
+ __exportStar(require("./authorization"), exports);
21
21
  __exportStar(require("./utils"), exports);
22
22
  __exportStar(require("./middleware"), exports);
23
23
  __exportStar(require("./types"), exports);
24
24
  __exportStar(require("./jwt"), exports);
25
+ __exportStar(require("./http"), exports);
@@ -1,2 +1 @@
1
- export * from "./jwt-verifier";
2
- export * from "./service-client";
1
+ export * from "./internal-jwt-verifier";
package/dist/jwt/index.js CHANGED
@@ -14,5 +14,4 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- __exportStar(require("./jwt-verifier"), exports);
18
- __exportStar(require("./service-client"), exports);
17
+ __exportStar(require("./internal-jwt-verifier"), exports);
@@ -0,0 +1,35 @@
1
+ import { AccountRole, GlobalPermission } from "../enums";
2
+ import { InternalJwtPayload, AccessContext } from "../types";
3
+ import { PermissionCacheService } from "../authorization";
4
+ export declare class InternalJwtVerifier {
5
+ private readonly permissionCache;
6
+ private readonly jwks;
7
+ private readonly issuer;
8
+ private readonly audience;
9
+ constructor(permissionCache: PermissionCacheService);
10
+ verifyInternal(token: string): Promise<InternalJwtPayload>;
11
+ buildAccessContext(token: string): Promise<{
12
+ payload: InternalJwtPayload;
13
+ context: AccessContext;
14
+ }>;
15
+ /**
16
+ * Returns true if the context has the given permission.
17
+ * Machine contexts always return false — they carry no user permissions.
18
+ */
19
+ static hasPermission(ctx: AccessContext, permission: GlobalPermission): boolean;
20
+ /**
21
+ * Throws JWTClaimValidationFailed if the permission is missing.
22
+ * Use in middleware for a one-liner gate:
23
+ * InternalJwtVerifier.requirePermission(ctx, GlobalPermission.MANAGE_ACCOUNTS);
24
+ */
25
+ static requirePermission(ctx: AccessContext, permission: GlobalPermission): void;
26
+ /**
27
+ * Returns true if the context carries the given role.
28
+ * Machine contexts always return false.
29
+ */
30
+ static hasRole(ctx: AccessContext, role: AccountRole): boolean;
31
+ /**
32
+ * Throws JWTClaimValidationFailed if the role doesn't match.
33
+ */
34
+ static requireRole(ctx: AccessContext, role: AccountRole): void;
35
+ }
@@ -0,0 +1,162 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.InternalJwtVerifier = void 0;
37
+ const jose = __importStar(require("jose"));
38
+ /**
39
+ * INTERNAL JWT VERIFIER (@discover-cloud/shared)
40
+ * ──────────────────────────────────────────────────
41
+ * Verifies the internal JWT forwarded by the API Gateway, resolves
42
+ * permissions from Redis, and builds the AccessContext for req.accessContext.
43
+ *
44
+ * Key source: Gateway JWKS
45
+ * GET http://api-gateway:3000/.well-known/jwks.json
46
+ *
47
+ * Flow per request:
48
+ * 1. Verify JWT signature, iss, aud, exp, nbf, typ (jose.jwtVerify)
49
+ * 2. Guard typ === "internal" (token confusion protection)
50
+ * 3. For human tokens: resolve perms from Redis (derive from role on miss)
51
+ * 4. Build and return AccessContext
52
+ */
53
+ const CLOCK_TOLERANCE_SECONDS = 30;
54
+ const JWKS_CACHE_MAX_AGE_MS = 10 * 60 * 1000; // 10 minutes
55
+ const JWKS_FETCH_TIMEOUT_MS = 5000;
56
+ class InternalJwtVerifier {
57
+ constructor(permissionCache) {
58
+ this.permissionCache = permissionCache;
59
+ const gatewayJwksUri = process.env.GATEWAY_JWKS_URI ??
60
+ "http://api-gateway:3000/.well-known/jwks.json";
61
+ this.issuer = process.env.INTERNAL_JWT_ISSUER ?? "discover-cloud:api-gateway";
62
+ this.audience = process.env.INTERNAL_JWT_AUDIENCE ?? "discover-cloud:internal";
63
+ this.jwks = jose.createRemoteJWKSet(new URL(gatewayJwksUri), {
64
+ cacheMaxAge: JWKS_CACHE_MAX_AGE_MS,
65
+ timeoutDuration: JWKS_FETCH_TIMEOUT_MS,
66
+ });
67
+ }
68
+ /* ----------------------------------------------------------------
69
+ verifyInternal
70
+ Full cryptographic verification + typ guard + clock skew tolerance.
71
+ Returns the raw verified payload.
72
+
73
+ Use when you need raw JWT claims (e.g. jti for blacklisting).
74
+ For everything else, use buildAccessContext().
75
+ ---------------------------------------------------------------- */
76
+ async verifyInternal(token) {
77
+ // jose.jwtVerify accepts string directly — the Uint8Array overload is
78
+ // for raw JWS. The type error comes from jose's union overloads; casting
79
+ // to the correct overload signature resolves it.
80
+ const { payload } = await jose.jwtVerify(token, this.jwks, {
81
+ issuer: this.issuer,
82
+ audience: this.audience,
83
+ algorithms: ["RS256"],
84
+ clockTolerance: CLOCK_TOLERANCE_SECONDS,
85
+ });
86
+ // typ guard — rejects external tokens accidentally forwarded downstream
87
+ if (payload.typ !== "internal") {
88
+ throw new jose.errors.JWTClaimValidationFailed(`Token type mismatch: expected "internal", got "${String(payload.typ)}". ` +
89
+ `Ensure clients are not forwarding external tokens past the gateway.`, payload, "typ", "check_failed");
90
+ }
91
+ return payload;
92
+ }
93
+ /* ----------------------------------------------------------------
94
+ buildAccessContext
95
+ Primary method — called by RequireAuthMiddleware on every request.
96
+ Verifies the token, resolves permissions, returns payload + context.
97
+
98
+ Usage in auth middleware:
99
+ const token = extractBearer(req);
100
+ const { payload, context } = await verifier.buildAccessContext(token);
101
+ req.internalAuth = payload;
102
+ req.accessContext = context;
103
+ ---------------------------------------------------------------- */
104
+ async buildAccessContext(token) {
105
+ const payload = await this.verifyInternal(token);
106
+ // Machine token — no user context, no permission cache lookup
107
+ if (payload.isMachine === true) {
108
+ const machinePayload = payload;
109
+ const context = {
110
+ kind: "machine",
111
+ serviceId: machinePayload.serviceId,
112
+ };
113
+ return { payload, context };
114
+ }
115
+ // Human token — resolve permissions from Redis (derive from role on miss)
116
+ const humanPayload = payload;
117
+ const perms = await this.permissionCache.resolve(humanPayload.accountId, humanPayload.accountRole);
118
+ const context = {
119
+ kind: "human",
120
+ accountId: humanPayload.accountId,
121
+ accountRole: humanPayload.accountRole,
122
+ perms, // populated from cache — never from the JWT
123
+ };
124
+ return { payload, context };
125
+ }
126
+ /* ----------------------------------------------------------------
127
+ Static helpers — use on req.accessContext after auth middleware runs
128
+ ---------------------------------------------------------------- */
129
+ /**
130
+ * Returns true if the context has the given permission.
131
+ * Machine contexts always return false — they carry no user permissions.
132
+ */
133
+ static hasPermission(ctx, permission) {
134
+ return ctx.kind === "human" && ctx.perms.includes(permission);
135
+ }
136
+ /**
137
+ * Throws JWTClaimValidationFailed if the permission is missing.
138
+ * Use in middleware for a one-liner gate:
139
+ * InternalJwtVerifier.requirePermission(ctx, GlobalPermission.MANAGE_ACCOUNTS);
140
+ */
141
+ static requirePermission(ctx, permission) {
142
+ if (!InternalJwtVerifier.hasPermission(ctx, permission)) {
143
+ throw new jose.errors.JWTClaimValidationFailed(`Missing required permission: ${permission}`, {}, "perms", "check_failed");
144
+ }
145
+ }
146
+ /**
147
+ * Returns true if the context carries the given role.
148
+ * Machine contexts always return false.
149
+ */
150
+ static hasRole(ctx, role) {
151
+ return ctx.kind === "human" && ctx.accountRole === role;
152
+ }
153
+ /**
154
+ * Throws JWTClaimValidationFailed if the role doesn't match.
155
+ */
156
+ static requireRole(ctx, role) {
157
+ if (!InternalJwtVerifier.hasRole(ctx, role)) {
158
+ throw new jose.errors.JWTClaimValidationFailed(`Missing required role: ${role}`, {}, "role", "check_failed");
159
+ }
160
+ }
161
+ }
162
+ exports.InternalJwtVerifier = InternalJwtVerifier;
@@ -0,0 +1,22 @@
1
+ import { Request, Response, NextFunction } from "express";
2
+ import { GlobalPermission } from "../enums";
3
+ /**
4
+ * AUTHORIZE MIDDLEWARE
5
+ * ─────────────────────
6
+ * Permission-gates a route. Must run AFTER require-auth (depends on req.accessContext).
7
+ * Uses InternalJwtVerifier.hasPermission — permissions come from the Redis
8
+ * permission cache loaded by RequireAuthMiddleware, not from the JWT itself.
9
+ *
10
+ * Usage:
11
+ * router.delete("/accounts/:id",
12
+ * requireAuth,
13
+ * authorize(GlobalPermission.DELETE_ACCOUNT),
14
+ * controller.delete
15
+ * );
16
+ */
17
+ export declare const authorize: (permission: GlobalPermission) => (req: Request, res: Response, next: NextFunction) => void;
18
+ /**
19
+ * authorizeAny
20
+ * Passes if the context has AT LEAST ONE of the provided permissions.
21
+ */
22
+ export declare const authorizeAny: (...permissions: GlobalPermission[]) => (req: Request, res: Response, next: NextFunction) => void;
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.authorizeAny = exports.authorize = void 0;
4
+ const internal_jwt_verifier_1 = require("../jwt/internal-jwt-verifier");
5
+ /**
6
+ * AUTHORIZE MIDDLEWARE
7
+ * ─────────────────────
8
+ * Permission-gates a route. Must run AFTER require-auth (depends on req.accessContext).
9
+ * Uses InternalJwtVerifier.hasPermission — permissions come from the Redis
10
+ * permission cache loaded by RequireAuthMiddleware, not from the JWT itself.
11
+ *
12
+ * Usage:
13
+ * router.delete("/accounts/:id",
14
+ * requireAuth,
15
+ * authorize(GlobalPermission.DELETE_ACCOUNT),
16
+ * controller.delete
17
+ * );
18
+ */
19
+ const authorize = (permission) => {
20
+ return (req, res, next) => {
21
+ // 1. Ensure RequireAuthMiddleware has already run
22
+ if (!req.accessContext) {
23
+ res.status(401).json({
24
+ success: false,
25
+ error: {
26
+ message: "Unauthorized: No access context",
27
+ code: "UNAUTHORIZED",
28
+ requestId: req.id,
29
+ },
30
+ });
31
+ return;
32
+ }
33
+ // 2. Check permission against accessContext.perms (loaded from Redis)
34
+ if (!internal_jwt_verifier_1.InternalJwtVerifier.hasPermission(req.accessContext, permission)) {
35
+ res.status(403).json({
36
+ success: false,
37
+ error: {
38
+ message: `Forbidden: Missing permission ${permission}`,
39
+ code: "FORBIDDEN",
40
+ requestId: req.id,
41
+ },
42
+ });
43
+ return;
44
+ }
45
+ next();
46
+ };
47
+ };
48
+ exports.authorize = authorize;
49
+ /**
50
+ * authorizeAny
51
+ * Passes if the context has AT LEAST ONE of the provided permissions.
52
+ */
53
+ const authorizeAny = (...permissions) => {
54
+ return (req, res, next) => {
55
+ if (!req.accessContext) {
56
+ res.status(401).json({
57
+ success: false,
58
+ error: { message: "Unauthorized: No access context", code: "UNAUTHORIZED", requestId: req.id },
59
+ });
60
+ return;
61
+ }
62
+ const hasAny = permissions.some((p) => internal_jwt_verifier_1.InternalJwtVerifier.hasPermission(req.accessContext, p));
63
+ if (!hasAny) {
64
+ res.status(403).json({
65
+ success: false,
66
+ error: {
67
+ message: `Forbidden: Missing one of [${permissions.join(", ")}]`,
68
+ code: "FORBIDDEN",
69
+ requestId: req.id,
70
+ },
71
+ });
72
+ return;
73
+ }
74
+ next();
75
+ };
76
+ };
77
+ exports.authorizeAny = authorizeAny;
@@ -0,0 +1,23 @@
1
+ import { Request, Response, NextFunction } from "express";
2
+ /**
3
+ * GLOBAL ERROR HANDLER
4
+ * ─────────────────────
5
+ * Centralized error handler for all Express services.
6
+ *
7
+ * Handles in order:
8
+ * 1. ZodError → 400 with flattened validation details
9
+ * 2. AppError → mapped statusCode + code (BadRequestError, NotFoundError, etc.)
10
+ * 3. Unknown → 500 Internal Server Error
11
+ *
12
+ * Every response includes requestId for traceability.
13
+ *
14
+ * Fix vs original:
15
+ * - Replaced unsafe duck-type cast `(err as { statusCode? })` with
16
+ * proper `instanceof AppError` check. Duck-typing any error with a
17
+ * statusCode property as an AppError is a bug vector.
18
+ * - req.id included in every error response
19
+ * - Stack trace logged only in non-production environments
20
+ */
21
+ export declare class GlobalErrorHandler {
22
+ static handle(err: unknown, req: Request, res: Response, _next: NextFunction): void;
23
+ }
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GlobalErrorHandler = void 0;
4
+ const zod_1 = require("zod");
5
+ const app_error_1 = require("../errors/app-error");
6
+ const utils_1 = require("../utils");
7
+ /**
8
+ * GLOBAL ERROR HANDLER
9
+ * ─────────────────────
10
+ * Centralized error handler for all Express services.
11
+ *
12
+ * Handles in order:
13
+ * 1. ZodError → 400 with flattened validation details
14
+ * 2. AppError → mapped statusCode + code (BadRequestError, NotFoundError, etc.)
15
+ * 3. Unknown → 500 Internal Server Error
16
+ *
17
+ * Every response includes requestId for traceability.
18
+ *
19
+ * Fix vs original:
20
+ * - Replaced unsafe duck-type cast `(err as { statusCode? })` with
21
+ * proper `instanceof AppError` check. Duck-typing any error with a
22
+ * statusCode property as an AppError is a bug vector.
23
+ * - req.id included in every error response
24
+ * - Stack trace logged only in non-production environments
25
+ */
26
+ class GlobalErrorHandler {
27
+ static handle(err, req, res, _next) {
28
+ const requestId = req.id;
29
+ // Always log — replace with your pino/winston logger in production
30
+ if (process.env.NODE_ENV !== "production") {
31
+ console.error(`[req ${requestId}]`, err);
32
+ }
33
+ else {
34
+ // In production, log without the stack trace in the response
35
+ console.error(`[req ${requestId}]`, err instanceof Error ? err.message : err);
36
+ }
37
+ // 1. Zod validation errors
38
+ if (err instanceof zod_1.ZodError) {
39
+ (0, utils_1.failure)(res, "Validation failed", "VALIDATION_ERROR", 400, err.flatten((issue) => issue.message));
40
+ return;
41
+ }
42
+ // 2. Known AppError subclasses (BadRequestError, NotFoundError, etc.)
43
+ // instanceof check — safe, no duck-typing
44
+ if (err instanceof app_error_1.AppError) {
45
+ (0, utils_1.failure)(res, err.message, err.code, err.statusCode, err.details);
46
+ return;
47
+ }
48
+ // 3. Unhandled / unexpected errors — never leak internals
49
+ (0, utils_1.failure)(res, "Internal Server Error", "INTERNAL_SERVER_ERROR", 500);
50
+ }
51
+ }
52
+ exports.GlobalErrorHandler = GlobalErrorHandler;
@@ -1,5 +1,4 @@
1
- export * from "./error-handler";
2
- export * from "./validate";
3
- export * from "./request-id";
4
- export * from "./require-auth";
5
- export * from "./authorize";
1
+ export * from "./error-handler.middleware";
2
+ export * from "./validate.middleware";
3
+ export * from "./request-id.middleware";
4
+ export * from "./authorize.middleware";
@@ -14,8 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- __exportStar(require("./error-handler"), exports);
18
- __exportStar(require("./validate"), exports);
19
- __exportStar(require("./request-id"), exports);
20
- __exportStar(require("./require-auth"), exports);
21
- __exportStar(require("./authorize"), exports);
17
+ __exportStar(require("./error-handler.middleware"), exports);
18
+ __exportStar(require("./validate.middleware"), exports);
19
+ __exportStar(require("./request-id.middleware"), exports);
20
+ __exportStar(require("./authorize.middleware"), exports);
@@ -0,0 +1,20 @@
1
+ import { Request, Response, NextFunction } from "express";
2
+ /**
3
+ * REQUEST ID MIDDLEWARE
4
+ * ──────────────────────
5
+ * Attaches a unique request ID to req.id for distributed tracing.
6
+ *
7
+ * Priority:
8
+ * 1. x-request-id header forwarded by the API Gateway (or load balancer)
9
+ * 2. Generate a new UUID if no upstream header is present
10
+ *
11
+ * Fix vs original:
12
+ * The original always generated a new UUID, breaking distributed tracing.
13
+ * If the gateway mints a requestId and forwards it via x-request-id,
14
+ * the downstream service must carry that same ID — otherwise logs across
15
+ * services cannot be correlated.
16
+ *
17
+ * Also sets x-request-id on the response so clients and proxies can
18
+ * correlate their logs with the service's logs.
19
+ */
20
+ export declare const requestId: (req: Request, res: Response, next: NextFunction) => void;
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.requestId = void 0;
4
+ const crypto_1 = require("crypto");
5
+ /**
6
+ * REQUEST ID MIDDLEWARE
7
+ * ──────────────────────
8
+ * Attaches a unique request ID to req.id for distributed tracing.
9
+ *
10
+ * Priority:
11
+ * 1. x-request-id header forwarded by the API Gateway (or load balancer)
12
+ * 2. Generate a new UUID if no upstream header is present
13
+ *
14
+ * Fix vs original:
15
+ * The original always generated a new UUID, breaking distributed tracing.
16
+ * If the gateway mints a requestId and forwards it via x-request-id,
17
+ * the downstream service must carry that same ID — otherwise logs across
18
+ * services cannot be correlated.
19
+ *
20
+ * Also sets x-request-id on the response so clients and proxies can
21
+ * correlate their logs with the service's logs.
22
+ */
23
+ const requestId = (req, res, next) => {
24
+ const upstream = req.headers["x-request-id"];
25
+ // Header can technically be a string array (multi-value) — take the first
26
+ const id = Array.isArray(upstream)
27
+ ? upstream[0]
28
+ : upstream ?? (0, crypto_1.randomUUID)();
29
+ req.id = id;
30
+ // Echo back on the response for client-side correlation
31
+ res.setHeader("x-request-id", id);
32
+ next();
33
+ };
34
+ exports.requestId = requestId;
@@ -0,0 +1,26 @@
1
+ import { Request, Response, NextFunction } from "express";
2
+ import { ZodType } from "zod";
3
+ /**
4
+ * VALIDATOR MIDDLEWARE
5
+ * ─────────────────────
6
+ * Validates and parses request input using a Zod schema.
7
+ * On success, attaches the parsed data to req.validated.
8
+ * On failure, passes a ZodError to GlobalErrorHandler → 400 response.
9
+ *
10
+ * Changes vs original:
11
+ * - Added "headers" as a valid source (useful for API key / custom header validation)
12
+ * - req.validated typed as unknown — consumers must narrow via z.infer at route level
13
+ *
14
+ * Usage:
15
+ * router.post("/orders",
16
+ * requireAuth,
17
+ * Validator.validate(CreateOrderSchema, "body"),
18
+ * controller.create
19
+ * );
20
+ *
21
+ * // In controller:
22
+ * const body = req.validated as z.infer<typeof CreateOrderSchema>;
23
+ */
24
+ export declare class Validator {
25
+ static validate<T>(schema: ZodType<T>, source?: "body" | "params" | "query" | "headers"): (req: Request, _res: Response, next: NextFunction) => void;
26
+ }
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Validator = void 0;
4
+ /**
5
+ * VALIDATOR MIDDLEWARE
6
+ * ─────────────────────
7
+ * Validates and parses request input using a Zod schema.
8
+ * On success, attaches the parsed data to req.validated.
9
+ * On failure, passes a ZodError to GlobalErrorHandler → 400 response.
10
+ *
11
+ * Changes vs original:
12
+ * - Added "headers" as a valid source (useful for API key / custom header validation)
13
+ * - req.validated typed as unknown — consumers must narrow via z.infer at route level
14
+ *
15
+ * Usage:
16
+ * router.post("/orders",
17
+ * requireAuth,
18
+ * Validator.validate(CreateOrderSchema, "body"),
19
+ * controller.create
20
+ * );
21
+ *
22
+ * // In controller:
23
+ * const body = req.validated as z.infer<typeof CreateOrderSchema>;
24
+ */
25
+ class Validator {
26
+ static validate(schema, source = "body") {
27
+ return (req, _res, next) => {
28
+ const result = schema.safeParse(req[source]);
29
+ if (!result.success) {
30
+ // ZodError propagates to GlobalErrorHandler → 400 with flatten()
31
+ next(result.error);
32
+ return;
33
+ }
34
+ // req.validated typed as unknown in express.d.ts
35
+ // Narrow to your schema type at the route/controller level
36
+ req.validated = result.data;
37
+ next();
38
+ };
39
+ }
40
+ }
41
+ exports.Validator = Validator;