@morojs/moro 1.0.0
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.
- package/LICENSE +21 -0
- package/README.md +233 -0
- package/dist/core/config/index.d.ts +19 -0
- package/dist/core/config/index.js +59 -0
- package/dist/core/config/index.js.map +1 -0
- package/dist/core/config/loader.d.ts +6 -0
- package/dist/core/config/loader.js +288 -0
- package/dist/core/config/loader.js.map +1 -0
- package/dist/core/config/schema.d.ts +335 -0
- package/dist/core/config/schema.js +286 -0
- package/dist/core/config/schema.js.map +1 -0
- package/dist/core/config/utils.d.ts +50 -0
- package/dist/core/config/utils.js +185 -0
- package/dist/core/config/utils.js.map +1 -0
- package/dist/core/database/adapters/drizzle.d.ts +29 -0
- package/dist/core/database/adapters/drizzle.js +366 -0
- package/dist/core/database/adapters/drizzle.js.map +1 -0
- package/dist/core/database/adapters/index.d.ts +8 -0
- package/dist/core/database/adapters/index.js +48 -0
- package/dist/core/database/adapters/index.js.map +1 -0
- package/dist/core/database/adapters/mongodb.d.ts +35 -0
- package/dist/core/database/adapters/mongodb.js +215 -0
- package/dist/core/database/adapters/mongodb.js.map +1 -0
- package/dist/core/database/adapters/mysql.d.ts +23 -0
- package/dist/core/database/adapters/mysql.js +149 -0
- package/dist/core/database/adapters/mysql.js.map +1 -0
- package/dist/core/database/adapters/postgresql.d.ts +24 -0
- package/dist/core/database/adapters/postgresql.js +160 -0
- package/dist/core/database/adapters/postgresql.js.map +1 -0
- package/dist/core/database/adapters/redis.d.ts +50 -0
- package/dist/core/database/adapters/redis.js +266 -0
- package/dist/core/database/adapters/redis.js.map +1 -0
- package/dist/core/database/adapters/sqlite.d.ts +23 -0
- package/dist/core/database/adapters/sqlite.js +194 -0
- package/dist/core/database/adapters/sqlite.js.map +1 -0
- package/dist/core/database/index.d.ts +2 -0
- package/dist/core/database/index.js +20 -0
- package/dist/core/database/index.js.map +1 -0
- package/dist/core/docs/index.d.ts +63 -0
- package/dist/core/docs/index.js +170 -0
- package/dist/core/docs/index.js.map +1 -0
- package/dist/core/docs/openapi-generator.d.ts +124 -0
- package/dist/core/docs/openapi-generator.js +413 -0
- package/dist/core/docs/openapi-generator.js.map +1 -0
- package/dist/core/docs/simple-docs.d.ts +21 -0
- package/dist/core/docs/simple-docs.js +268 -0
- package/dist/core/docs/simple-docs.js.map +1 -0
- package/dist/core/docs/swagger-ui.d.ts +28 -0
- package/dist/core/docs/swagger-ui.js +317 -0
- package/dist/core/docs/swagger-ui.js.map +1 -0
- package/dist/core/docs/zod-to-openapi.d.ts +29 -0
- package/dist/core/docs/zod-to-openapi.js +414 -0
- package/dist/core/docs/zod-to-openapi.js.map +1 -0
- package/dist/core/events/event-bus.d.ts +27 -0
- package/dist/core/events/event-bus.js +193 -0
- package/dist/core/events/event-bus.js.map +1 -0
- package/dist/core/events/index.d.ts +2 -0
- package/dist/core/events/index.js +7 -0
- package/dist/core/events/index.js.map +1 -0
- package/dist/core/framework.d.ts +57 -0
- package/dist/core/framework.js +432 -0
- package/dist/core/framework.js.map +1 -0
- package/dist/core/http/http-server.d.ts +114 -0
- package/dist/core/http/http-server.js +1154 -0
- package/dist/core/http/http-server.js.map +1 -0
- package/dist/core/http/index.d.ts +3 -0
- package/dist/core/http/index.js +10 -0
- package/dist/core/http/index.js.map +1 -0
- package/dist/core/http/router.d.ts +14 -0
- package/dist/core/http/router.js +113 -0
- package/dist/core/http/router.js.map +1 -0
- package/dist/core/logger/filters.d.ts +9 -0
- package/dist/core/logger/filters.js +134 -0
- package/dist/core/logger/filters.js.map +1 -0
- package/dist/core/logger/index.d.ts +3 -0
- package/dist/core/logger/index.js +26 -0
- package/dist/core/logger/index.js.map +1 -0
- package/dist/core/logger/logger.d.ts +49 -0
- package/dist/core/logger/logger.js +332 -0
- package/dist/core/logger/logger.js.map +1 -0
- package/dist/core/logger/outputs.d.ts +42 -0
- package/dist/core/logger/outputs.js +110 -0
- package/dist/core/logger/outputs.js.map +1 -0
- package/dist/core/middleware/built-in/adapters/cache/file.d.ts +15 -0
- package/dist/core/middleware/built-in/adapters/cache/file.js +128 -0
- package/dist/core/middleware/built-in/adapters/cache/file.js.map +1 -0
- package/dist/core/middleware/built-in/adapters/cache/index.d.ts +5 -0
- package/dist/core/middleware/built-in/adapters/cache/index.js +28 -0
- package/dist/core/middleware/built-in/adapters/cache/index.js.map +1 -0
- package/dist/core/middleware/built-in/adapters/cache/memory.d.ts +11 -0
- package/dist/core/middleware/built-in/adapters/cache/memory.js +65 -0
- package/dist/core/middleware/built-in/adapters/cache/memory.js.map +1 -0
- package/dist/core/middleware/built-in/adapters/cache/redis.d.ts +17 -0
- package/dist/core/middleware/built-in/adapters/cache/redis.js +91 -0
- package/dist/core/middleware/built-in/adapters/cache/redis.js.map +1 -0
- package/dist/core/middleware/built-in/adapters/cdn/azure.d.ts +21 -0
- package/dist/core/middleware/built-in/adapters/cdn/azure.js +40 -0
- package/dist/core/middleware/built-in/adapters/cdn/azure.js.map +1 -0
- package/dist/core/middleware/built-in/adapters/cdn/cloudflare.d.ts +14 -0
- package/dist/core/middleware/built-in/adapters/cdn/cloudflare.js +77 -0
- package/dist/core/middleware/built-in/adapters/cdn/cloudflare.js.map +1 -0
- package/dist/core/middleware/built-in/adapters/cdn/cloudfront.d.ts +15 -0
- package/dist/core/middleware/built-in/adapters/cdn/cloudfront.js +73 -0
- package/dist/core/middleware/built-in/adapters/cdn/cloudfront.js.map +1 -0
- package/dist/core/middleware/built-in/adapters/cdn/index.d.ts +5 -0
- package/dist/core/middleware/built-in/adapters/cdn/index.js +28 -0
- package/dist/core/middleware/built-in/adapters/cdn/index.js.map +1 -0
- package/dist/core/middleware/built-in/adapters/index.d.ts +4 -0
- package/dist/core/middleware/built-in/adapters/index.js +26 -0
- package/dist/core/middleware/built-in/adapters/index.js.map +1 -0
- package/dist/core/middleware/built-in/auth.d.ts +2 -0
- package/dist/core/middleware/built-in/auth.js +38 -0
- package/dist/core/middleware/built-in/auth.js.map +1 -0
- package/dist/core/middleware/built-in/cache.d.ts +3 -0
- package/dist/core/middleware/built-in/cache.js +188 -0
- package/dist/core/middleware/built-in/cache.js.map +1 -0
- package/dist/core/middleware/built-in/cdn.d.ts +3 -0
- package/dist/core/middleware/built-in/cdn.js +115 -0
- package/dist/core/middleware/built-in/cdn.js.map +1 -0
- package/dist/core/middleware/built-in/cookie.d.ts +14 -0
- package/dist/core/middleware/built-in/cookie.js +68 -0
- package/dist/core/middleware/built-in/cookie.js.map +1 -0
- package/dist/core/middleware/built-in/cors.d.ts +2 -0
- package/dist/core/middleware/built-in/cors.js +29 -0
- package/dist/core/middleware/built-in/cors.js.map +1 -0
- package/dist/core/middleware/built-in/csp.d.ts +22 -0
- package/dist/core/middleware/built-in/csp.js +74 -0
- package/dist/core/middleware/built-in/csp.js.map +1 -0
- package/dist/core/middleware/built-in/csrf.d.ts +9 -0
- package/dist/core/middleware/built-in/csrf.js +66 -0
- package/dist/core/middleware/built-in/csrf.js.map +1 -0
- package/dist/core/middleware/built-in/error-tracker.d.ts +1 -0
- package/dist/core/middleware/built-in/error-tracker.js +19 -0
- package/dist/core/middleware/built-in/error-tracker.js.map +1 -0
- package/dist/core/middleware/built-in/index.d.ts +70 -0
- package/dist/core/middleware/built-in/index.js +70 -0
- package/dist/core/middleware/built-in/index.js.map +1 -0
- package/dist/core/middleware/built-in/performance-monitor.d.ts +1 -0
- package/dist/core/middleware/built-in/performance-monitor.js +22 -0
- package/dist/core/middleware/built-in/performance-monitor.js.map +1 -0
- package/dist/core/middleware/built-in/rate-limit.d.ts +6 -0
- package/dist/core/middleware/built-in/rate-limit.js +47 -0
- package/dist/core/middleware/built-in/rate-limit.js.map +1 -0
- package/dist/core/middleware/built-in/request-logger.d.ts +1 -0
- package/dist/core/middleware/built-in/request-logger.js +15 -0
- package/dist/core/middleware/built-in/request-logger.js.map +1 -0
- package/dist/core/middleware/built-in/session.d.ts +41 -0
- package/dist/core/middleware/built-in/session.js +209 -0
- package/dist/core/middleware/built-in/session.js.map +1 -0
- package/dist/core/middleware/built-in/sse.d.ts +6 -0
- package/dist/core/middleware/built-in/sse.js +73 -0
- package/dist/core/middleware/built-in/sse.js.map +1 -0
- package/dist/core/middleware/built-in/validation.d.ts +2 -0
- package/dist/core/middleware/built-in/validation.js +31 -0
- package/dist/core/middleware/built-in/validation.js.map +1 -0
- package/dist/core/middleware/index.d.ts +21 -0
- package/dist/core/middleware/index.js +152 -0
- package/dist/core/middleware/index.js.map +1 -0
- package/dist/core/modules/auto-discovery.d.ts +27 -0
- package/dist/core/modules/auto-discovery.js +255 -0
- package/dist/core/modules/auto-discovery.js.map +1 -0
- package/dist/core/modules/index.d.ts +2 -0
- package/dist/core/modules/index.js +11 -0
- package/dist/core/modules/index.js.map +1 -0
- package/dist/core/modules/modules.d.ts +10 -0
- package/dist/core/modules/modules.js +137 -0
- package/dist/core/modules/modules.js.map +1 -0
- package/dist/core/networking/index.d.ts +2 -0
- package/dist/core/networking/index.js +9 -0
- package/dist/core/networking/index.js.map +1 -0
- package/dist/core/networking/service-discovery.d.ts +38 -0
- package/dist/core/networking/service-discovery.js +233 -0
- package/dist/core/networking/service-discovery.js.map +1 -0
- package/dist/core/networking/websocket-manager.d.ts +27 -0
- package/dist/core/networking/websocket-manager.js +211 -0
- package/dist/core/networking/websocket-manager.js.map +1 -0
- package/dist/core/routing/app-integration.d.ts +42 -0
- package/dist/core/routing/app-integration.js +152 -0
- package/dist/core/routing/app-integration.js.map +1 -0
- package/dist/core/routing/index.d.ts +106 -0
- package/dist/core/routing/index.js +343 -0
- package/dist/core/routing/index.js.map +1 -0
- package/dist/core/runtime/aws-lambda-adapter.d.ts +43 -0
- package/dist/core/runtime/aws-lambda-adapter.js +108 -0
- package/dist/core/runtime/aws-lambda-adapter.js.map +1 -0
- package/dist/core/runtime/base-adapter.d.ts +16 -0
- package/dist/core/runtime/base-adapter.js +105 -0
- package/dist/core/runtime/base-adapter.js.map +1 -0
- package/dist/core/runtime/cloudflare-workers-adapter.d.ts +18 -0
- package/dist/core/runtime/cloudflare-workers-adapter.js +131 -0
- package/dist/core/runtime/cloudflare-workers-adapter.js.map +1 -0
- package/dist/core/runtime/index.d.ts +14 -0
- package/dist/core/runtime/index.js +56 -0
- package/dist/core/runtime/index.js.map +1 -0
- package/dist/core/runtime/node-adapter.d.ts +15 -0
- package/dist/core/runtime/node-adapter.js +204 -0
- package/dist/core/runtime/node-adapter.js.map +1 -0
- package/dist/core/runtime/vercel-edge-adapter.d.ts +10 -0
- package/dist/core/runtime/vercel-edge-adapter.js +106 -0
- package/dist/core/runtime/vercel-edge-adapter.js.map +1 -0
- package/dist/core/utilities/circuit-breaker.d.ts +14 -0
- package/dist/core/utilities/circuit-breaker.js +42 -0
- package/dist/core/utilities/circuit-breaker.js.map +1 -0
- package/dist/core/utilities/container.d.ts +116 -0
- package/dist/core/utilities/container.js +529 -0
- package/dist/core/utilities/container.js.map +1 -0
- package/dist/core/utilities/hooks.d.ts +24 -0
- package/dist/core/utilities/hooks.js +131 -0
- package/dist/core/utilities/hooks.js.map +1 -0
- package/dist/core/utilities/index.d.ts +4 -0
- package/dist/core/utilities/index.js +22 -0
- package/dist/core/utilities/index.js.map +1 -0
- package/dist/core/validation/index.d.ts +30 -0
- package/dist/core/validation/index.js +144 -0
- package/dist/core/validation/index.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +72 -0
- package/dist/index.js.map +1 -0
- package/dist/moro.d.ts +82 -0
- package/dist/moro.js +679 -0
- package/dist/moro.js.map +1 -0
- package/dist/types/cache.d.ts +34 -0
- package/dist/types/cache.js +3 -0
- package/dist/types/cache.js.map +1 -0
- package/dist/types/cdn.d.ts +19 -0
- package/dist/types/cdn.js +3 -0
- package/dist/types/cdn.js.map +1 -0
- package/dist/types/core.d.ts +13 -0
- package/dist/types/core.js +3 -0
- package/dist/types/core.js.map +1 -0
- package/dist/types/database.d.ts +29 -0
- package/dist/types/database.js +3 -0
- package/dist/types/database.js.map +1 -0
- package/dist/types/discovery.d.ts +6 -0
- package/dist/types/discovery.js +3 -0
- package/dist/types/discovery.js.map +1 -0
- package/dist/types/events.d.ts +116 -0
- package/dist/types/events.js +3 -0
- package/dist/types/events.js.map +1 -0
- package/dist/types/hooks.d.ts +38 -0
- package/dist/types/hooks.js +3 -0
- package/dist/types/hooks.js.map +1 -0
- package/dist/types/http.d.ts +51 -0
- package/dist/types/http.js +3 -0
- package/dist/types/http.js.map +1 -0
- package/dist/types/logger.d.ts +77 -0
- package/dist/types/logger.js +3 -0
- package/dist/types/logger.js.map +1 -0
- package/dist/types/module.d.ts +91 -0
- package/dist/types/module.js +3 -0
- package/dist/types/module.js.map +1 -0
- package/dist/types/runtime.d.ts +48 -0
- package/dist/types/runtime.js +3 -0
- package/dist/types/runtime.js.map +1 -0
- package/dist/types/session.d.ts +66 -0
- package/dist/types/session.js +3 -0
- package/dist/types/session.js.map +1 -0
- package/package.json +176 -0
- package/src/core/config/index.ts +47 -0
- package/src/core/config/loader.ts +366 -0
- package/src/core/config/schema.ts +346 -0
- package/src/core/config/utils.ts +220 -0
- package/src/core/database/README.md +228 -0
- package/src/core/database/adapters/drizzle.ts +425 -0
- package/src/core/database/adapters/index.ts +45 -0
- package/src/core/database/adapters/mongodb.ts +292 -0
- package/src/core/database/adapters/mysql.ts +217 -0
- package/src/core/database/adapters/postgresql.ts +211 -0
- package/src/core/database/adapters/redis.ts +331 -0
- package/src/core/database/adapters/sqlite.ts +255 -0
- package/src/core/database/index.ts +3 -0
- package/src/core/docs/index.ts +245 -0
- package/src/core/docs/openapi-generator.ts +588 -0
- package/src/core/docs/simple-docs.ts +305 -0
- package/src/core/docs/swagger-ui.ts +370 -0
- package/src/core/docs/zod-to-openapi.ts +532 -0
- package/src/core/events/event-bus.ts +249 -0
- package/src/core/events/index.ts +12 -0
- package/src/core/framework.ts +621 -0
- package/src/core/http/http-server.ts +1421 -0
- package/src/core/http/index.ts +11 -0
- package/src/core/http/router.ts +153 -0
- package/src/core/logger/filters.ts +148 -0
- package/src/core/logger/index.ts +20 -0
- package/src/core/logger/logger.ts +434 -0
- package/src/core/logger/outputs.ts +136 -0
- package/src/core/middleware/built-in/adapters/cache/file.ts +106 -0
- package/src/core/middleware/built-in/adapters/cache/index.ts +26 -0
- package/src/core/middleware/built-in/adapters/cache/memory.ts +73 -0
- package/src/core/middleware/built-in/adapters/cache/redis.ts +103 -0
- package/src/core/middleware/built-in/adapters/cdn/azure.ts +68 -0
- package/src/core/middleware/built-in/adapters/cdn/cloudflare.ts +100 -0
- package/src/core/middleware/built-in/adapters/cdn/cloudfront.ts +92 -0
- package/src/core/middleware/built-in/adapters/cdn/index.ts +23 -0
- package/src/core/middleware/built-in/adapters/index.ts +7 -0
- package/src/core/middleware/built-in/auth.ts +39 -0
- package/src/core/middleware/built-in/cache.ts +228 -0
- package/src/core/middleware/built-in/cdn.ts +151 -0
- package/src/core/middleware/built-in/cookie.ts +90 -0
- package/src/core/middleware/built-in/cors.ts +38 -0
- package/src/core/middleware/built-in/csp.ts +107 -0
- package/src/core/middleware/built-in/csrf.ts +87 -0
- package/src/core/middleware/built-in/error-tracker.ts +16 -0
- package/src/core/middleware/built-in/index.ts +57 -0
- package/src/core/middleware/built-in/performance-monitor.ts +25 -0
- package/src/core/middleware/built-in/rate-limit.ts +60 -0
- package/src/core/middleware/built-in/request-logger.ts +14 -0
- package/src/core/middleware/built-in/session.ts +311 -0
- package/src/core/middleware/built-in/sse.ts +91 -0
- package/src/core/middleware/built-in/validation.ts +33 -0
- package/src/core/middleware/index.ts +188 -0
- package/src/core/modules/auto-discovery.ts +265 -0
- package/src/core/modules/index.ts +6 -0
- package/src/core/modules/modules.ts +125 -0
- package/src/core/networking/index.ts +7 -0
- package/src/core/networking/service-discovery.ts +309 -0
- package/src/core/networking/websocket-manager.ts +259 -0
- package/src/core/routing/app-integration.ts +229 -0
- package/src/core/routing/index.ts +519 -0
- package/src/core/runtime/aws-lambda-adapter.ts +157 -0
- package/src/core/runtime/base-adapter.ts +140 -0
- package/src/core/runtime/cloudflare-workers-adapter.ts +166 -0
- package/src/core/runtime/index.ts +74 -0
- package/src/core/runtime/node-adapter.ts +210 -0
- package/src/core/runtime/vercel-edge-adapter.ts +125 -0
- package/src/core/utilities/circuit-breaker.ts +46 -0
- package/src/core/utilities/container.ts +760 -0
- package/src/core/utilities/hooks.ts +148 -0
- package/src/core/utilities/index.ts +16 -0
- package/src/core/validation/index.ts +216 -0
- package/src/index.ts +120 -0
- package/src/moro.ts +842 -0
- package/src/types/cache.ts +38 -0
- package/src/types/cdn.ts +22 -0
- package/src/types/core.ts +17 -0
- package/src/types/database.ts +40 -0
- package/src/types/discovery.ts +7 -0
- package/src/types/events.ts +90 -0
- package/src/types/hooks.ts +47 -0
- package/src/types/http.ts +70 -0
- package/src/types/logger.ts +109 -0
- package/src/types/module.ts +87 -0
- package/src/types/runtime.ts +91 -0
- package/src/types/session.ts +89 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
// Built-in Cache Middleware
|
|
2
|
+
import { MiddlewareInterface, HookContext } from "../../../types/hooks";
|
|
3
|
+
import {
|
|
4
|
+
CacheAdapter,
|
|
5
|
+
CacheOptions,
|
|
6
|
+
CachedResponse,
|
|
7
|
+
} from "../../../types/cache";
|
|
8
|
+
import { createFrameworkLogger } from "../../logger";
|
|
9
|
+
import { createCacheAdapter } from "./adapters/cache";
|
|
10
|
+
|
|
11
|
+
const logger = createFrameworkLogger("CacheMiddleware");
|
|
12
|
+
|
|
13
|
+
export const cache = (options: CacheOptions = {}): MiddlewareInterface => ({
|
|
14
|
+
name: "cache",
|
|
15
|
+
version: "1.0.0",
|
|
16
|
+
metadata: {
|
|
17
|
+
name: "cache",
|
|
18
|
+
version: "1.0.0",
|
|
19
|
+
description: "Built-in cache middleware with pluggable storage adapters",
|
|
20
|
+
author: "MoroJS Team",
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
install: async (hooks: any, middlewareOptions: any = {}) => {
|
|
24
|
+
logger.debug("Installing cache middleware", "Installation");
|
|
25
|
+
|
|
26
|
+
// Initialize storage adapter
|
|
27
|
+
let storageAdapter: CacheAdapter;
|
|
28
|
+
|
|
29
|
+
if (
|
|
30
|
+
options.adapter &&
|
|
31
|
+
typeof options.adapter === "object" &&
|
|
32
|
+
"get" in options.adapter
|
|
33
|
+
) {
|
|
34
|
+
storageAdapter = options.adapter as CacheAdapter;
|
|
35
|
+
} else if (typeof options.adapter === "string") {
|
|
36
|
+
storageAdapter = createCacheAdapter(
|
|
37
|
+
options.adapter,
|
|
38
|
+
options.adapterOptions,
|
|
39
|
+
);
|
|
40
|
+
} else {
|
|
41
|
+
// Default to memory cache
|
|
42
|
+
storageAdapter = createCacheAdapter("memory");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Cache key generation
|
|
46
|
+
const generateCacheKey = (req: any, strategy?: any): string => {
|
|
47
|
+
const prefix = options.keyPrefix || "moro:cache:";
|
|
48
|
+
|
|
49
|
+
if (strategy?.key) {
|
|
50
|
+
return `${prefix}${strategy.key(req)}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Default key: method + path + query
|
|
54
|
+
const query = new URLSearchParams(req.query || {}).toString();
|
|
55
|
+
return `${prefix}${req.method}:${req.path}${query ? `?${query}` : ""}`;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// Find matching strategy
|
|
59
|
+
const findStrategy = (req: any): any | undefined => {
|
|
60
|
+
if (!options.strategies) return undefined;
|
|
61
|
+
|
|
62
|
+
for (const [pattern, strategy] of Object.entries(options.strategies)) {
|
|
63
|
+
const regex = new RegExp(pattern);
|
|
64
|
+
if (regex.test(req.path)) {
|
|
65
|
+
return strategy;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return undefined;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
hooks.before("request", async (context: HookContext) => {
|
|
73
|
+
const req = context.request as any;
|
|
74
|
+
const res = context.response as any;
|
|
75
|
+
|
|
76
|
+
// Only cache GET requests by default
|
|
77
|
+
if (req.method !== "GET") {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const strategy = findStrategy(req);
|
|
82
|
+
const cacheKey = generateCacheKey(req, strategy);
|
|
83
|
+
|
|
84
|
+
// Check if caching is disabled for this request
|
|
85
|
+
if (strategy?.condition && !strategy.condition(req, res)) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
// Try to get from cache
|
|
91
|
+
const cachedResponse = await storageAdapter.get(cacheKey);
|
|
92
|
+
|
|
93
|
+
if (cachedResponse) {
|
|
94
|
+
logger.debug(`Cache hit: ${cacheKey}`, "CacheHit");
|
|
95
|
+
|
|
96
|
+
// Set cache headers
|
|
97
|
+
res.setHeader("X-Cache", "HIT");
|
|
98
|
+
res.setHeader("X-Cache-Key", cacheKey);
|
|
99
|
+
|
|
100
|
+
// Set HTTP cache headers
|
|
101
|
+
if (options.maxAge) {
|
|
102
|
+
res.setHeader("Cache-Control", `public, max-age=${options.maxAge}`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (options.vary && options.vary.length > 0) {
|
|
106
|
+
res.setHeader("Vary", options.vary.join(", "));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Send cached response
|
|
110
|
+
res.status(cachedResponse.status || 200);
|
|
111
|
+
|
|
112
|
+
// Set cached headers
|
|
113
|
+
if (cachedResponse.headers) {
|
|
114
|
+
Object.entries(cachedResponse.headers).forEach(([key, value]) => {
|
|
115
|
+
res.setHeader(key, value as string);
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (cachedResponse.contentType) {
|
|
120
|
+
res.setHeader("Content-Type", cachedResponse.contentType);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
res.send(cachedResponse.body);
|
|
124
|
+
|
|
125
|
+
// Mark as handled
|
|
126
|
+
(context as any).handled = true;
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
logger.debug(`Cache miss: ${cacheKey}`, "CacheMiss");
|
|
131
|
+
res.setHeader("X-Cache", "MISS");
|
|
132
|
+
res.setHeader("X-Cache-Key", cacheKey);
|
|
133
|
+
} catch (error) {
|
|
134
|
+
logger.error("Cache retrieval error", "CacheError", {
|
|
135
|
+
error,
|
|
136
|
+
key: cacheKey,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Store original response methods
|
|
141
|
+
const originalJson = res.json;
|
|
142
|
+
const originalSend = res.send;
|
|
143
|
+
const originalEnd = res.end;
|
|
144
|
+
|
|
145
|
+
// Wrap response methods to cache the response
|
|
146
|
+
const cacheResponse = async (body: any, contentType?: string) => {
|
|
147
|
+
try {
|
|
148
|
+
const ttl = strategy?.ttl || options.defaultTtl || 3600;
|
|
149
|
+
|
|
150
|
+
const cacheData: CachedResponse = {
|
|
151
|
+
body,
|
|
152
|
+
status: res.statusCode,
|
|
153
|
+
headers: res.getHeaders ? res.getHeaders() : {},
|
|
154
|
+
contentType: contentType || res.getHeader("Content-Type"),
|
|
155
|
+
timestamp: Date.now(),
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
await storageAdapter.set(cacheKey, cacheData, ttl);
|
|
159
|
+
logger.debug(
|
|
160
|
+
`Response cached: ${cacheKey} (TTL: ${ttl}s)`,
|
|
161
|
+
"CacheSet",
|
|
162
|
+
);
|
|
163
|
+
} catch (error) {
|
|
164
|
+
logger.error("Cache storage error", "CacheError", {
|
|
165
|
+
error,
|
|
166
|
+
key: cacheKey,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
// Override response methods
|
|
172
|
+
res.json = function (data: any) {
|
|
173
|
+
cacheResponse(data, "application/json");
|
|
174
|
+
return originalJson.call(this, data);
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
res.send = function (data: any) {
|
|
178
|
+
cacheResponse(data);
|
|
179
|
+
return originalSend.call(this, data);
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
res.end = function (data?: any) {
|
|
183
|
+
if (data) {
|
|
184
|
+
cacheResponse(data);
|
|
185
|
+
}
|
|
186
|
+
return originalEnd.call(this, data);
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
// Add cache control methods
|
|
190
|
+
res.cacheControl = (directives: any) => {
|
|
191
|
+
const parts: string[] = [];
|
|
192
|
+
|
|
193
|
+
if (directives.public) parts.push("public");
|
|
194
|
+
if (directives.private) parts.push("private");
|
|
195
|
+
if (directives.noCache) parts.push("no-cache");
|
|
196
|
+
if (directives.noStore) parts.push("no-store");
|
|
197
|
+
if (directives.mustRevalidate) parts.push("must-revalidate");
|
|
198
|
+
if (directives.immutable) parts.push("immutable");
|
|
199
|
+
|
|
200
|
+
if (typeof directives.maxAge === "number")
|
|
201
|
+
parts.push(`max-age=${directives.maxAge}`);
|
|
202
|
+
if (typeof directives.staleWhileRevalidate === "number") {
|
|
203
|
+
parts.push(
|
|
204
|
+
`stale-while-revalidate=${directives.staleWhileRevalidate}`,
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
res.setHeader("Cache-Control", parts.join(", "));
|
|
209
|
+
return res;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
// Add ETag generation
|
|
213
|
+
if (options.etag !== false) {
|
|
214
|
+
res.generateETag = (content: string | Buffer) => {
|
|
215
|
+
const crypto = require("crypto");
|
|
216
|
+
const hash = crypto.createHash("md5").update(content).digest("hex");
|
|
217
|
+
const prefix = options.etag === "weak" ? "W/" : "";
|
|
218
|
+
return `${prefix}"${hash}"`;
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
logger.info("Cache middleware installed", "Installation", {
|
|
224
|
+
adapter: typeof options.adapter === "string" ? options.adapter : "custom",
|
|
225
|
+
strategies: Object.keys(options.strategies || {}).length,
|
|
226
|
+
});
|
|
227
|
+
},
|
|
228
|
+
});
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// Built-in CDN Middleware
|
|
2
|
+
import { MiddlewareInterface, HookContext } from "../../../types/hooks";
|
|
3
|
+
import { CDNAdapter, CDNOptions } from "../../../types/cdn";
|
|
4
|
+
import { createFrameworkLogger } from "../../logger";
|
|
5
|
+
import { createCDNAdapter } from "./adapters/cdn";
|
|
6
|
+
|
|
7
|
+
const logger = createFrameworkLogger("CDNMiddleware");
|
|
8
|
+
|
|
9
|
+
export const cdn = (options: CDNOptions = {}): MiddlewareInterface => ({
|
|
10
|
+
name: "cdn",
|
|
11
|
+
version: "1.0.0",
|
|
12
|
+
metadata: {
|
|
13
|
+
name: "cdn",
|
|
14
|
+
version: "1.0.0",
|
|
15
|
+
description: "Built-in CDN middleware with pluggable provider adapters",
|
|
16
|
+
author: "MoroJS Team",
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
install: async (hooks: any, middlewareOptions: any = {}) => {
|
|
20
|
+
logger.debug("Installing CDN middleware", "Installation");
|
|
21
|
+
|
|
22
|
+
// Initialize CDN adapter
|
|
23
|
+
let cdnAdapter: CDNAdapter | null = null;
|
|
24
|
+
|
|
25
|
+
if (
|
|
26
|
+
options.adapter &&
|
|
27
|
+
typeof options.adapter === "object" &&
|
|
28
|
+
"purge" in options.adapter
|
|
29
|
+
) {
|
|
30
|
+
cdnAdapter = options.adapter as CDNAdapter;
|
|
31
|
+
} else if (typeof options.adapter === "string") {
|
|
32
|
+
cdnAdapter = createCDNAdapter(options.adapter, options.adapterOptions);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!cdnAdapter) {
|
|
36
|
+
logger.warn(
|
|
37
|
+
"No CDN adapter configured, CDN features will be disabled",
|
|
38
|
+
"Installation",
|
|
39
|
+
);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
hooks.before("request", async (context: HookContext) => {
|
|
44
|
+
const req = context.request as any;
|
|
45
|
+
const res = context.response as any;
|
|
46
|
+
|
|
47
|
+
// Set CDN headers on all responses
|
|
48
|
+
if (cdnAdapter) {
|
|
49
|
+
cdnAdapter.setHeaders(res);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Add CDN methods to response
|
|
53
|
+
res.purgeCDN = async (urls?: string[]) => {
|
|
54
|
+
if (!cdnAdapter) {
|
|
55
|
+
logger.warn(
|
|
56
|
+
"CDN purge requested but no adapter configured",
|
|
57
|
+
"CDNPurge",
|
|
58
|
+
);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const urlsToPurge = urls || [req.path];
|
|
64
|
+
await cdnAdapter.purge(urlsToPurge);
|
|
65
|
+
logger.info(
|
|
66
|
+
`CDN cache purged: ${urlsToPurge.join(", ")}`,
|
|
67
|
+
"CDNPurge",
|
|
68
|
+
);
|
|
69
|
+
} catch (error) {
|
|
70
|
+
logger.error("CDN purge failed", "CDNError", { error, urls });
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
res.prefetchCDN = async (urls: string[]) => {
|
|
76
|
+
if (!cdnAdapter || !cdnAdapter.prefetch) {
|
|
77
|
+
logger.warn(
|
|
78
|
+
"CDN prefetch requested but not supported by adapter",
|
|
79
|
+
"CDNPrefetch",
|
|
80
|
+
);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
await cdnAdapter.prefetch(urls);
|
|
86
|
+
logger.info(
|
|
87
|
+
`CDN prefetch requested: ${urls.join(", ")}`,
|
|
88
|
+
"CDNPrefetch",
|
|
89
|
+
);
|
|
90
|
+
} catch (error) {
|
|
91
|
+
logger.error("CDN prefetch failed", "CDNError", { error, urls });
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
res.getCDNStats = async () => {
|
|
96
|
+
if (!cdnAdapter || !cdnAdapter.getStats) {
|
|
97
|
+
logger.warn(
|
|
98
|
+
"CDN stats requested but not supported by adapter",
|
|
99
|
+
"CDNStats",
|
|
100
|
+
);
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
const stats = await cdnAdapter.getStats();
|
|
106
|
+
logger.debug("CDN stats retrieved", "CDNStats");
|
|
107
|
+
return stats;
|
|
108
|
+
} catch (error) {
|
|
109
|
+
logger.error("CDN stats retrieval failed", "CDNError", { error });
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Auto-invalidation on certain patterns
|
|
116
|
+
if (options.autoInvalidate && options.invalidationPatterns) {
|
|
117
|
+
hooks.after("response", async (context: HookContext) => {
|
|
118
|
+
const req = context.request as any;
|
|
119
|
+
const res = context.response as any;
|
|
120
|
+
|
|
121
|
+
// Check if this request matches invalidation patterns
|
|
122
|
+
const shouldInvalidate = options.invalidationPatterns?.some(
|
|
123
|
+
(pattern) => {
|
|
124
|
+
const regex = new RegExp(pattern);
|
|
125
|
+
return regex.test(req.path);
|
|
126
|
+
},
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
if (shouldInvalidate && cdnAdapter) {
|
|
130
|
+
try {
|
|
131
|
+
await cdnAdapter.purge([req.path]);
|
|
132
|
+
logger.debug(
|
|
133
|
+
`Auto-invalidated CDN cache for: ${req.path}`,
|
|
134
|
+
"CDNAutoInvalidate",
|
|
135
|
+
);
|
|
136
|
+
} catch (error) {
|
|
137
|
+
logger.error("CDN auto-invalidation failed", "CDNError", {
|
|
138
|
+
error,
|
|
139
|
+
path: req.path,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
logger.info("CDN middleware installed", "Installation", {
|
|
147
|
+
adapter: typeof options.adapter === "string" ? options.adapter : "custom",
|
|
148
|
+
autoInvalidate: !!options.autoInvalidate,
|
|
149
|
+
});
|
|
150
|
+
},
|
|
151
|
+
});
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// Cookie Middleware
|
|
2
|
+
import { MiddlewareInterface, HookContext } from "../../../types/hooks";
|
|
3
|
+
import { createFrameworkLogger } from "../../logger";
|
|
4
|
+
|
|
5
|
+
const logger = createFrameworkLogger("CookieMiddleware");
|
|
6
|
+
|
|
7
|
+
export interface CookieOptions {
|
|
8
|
+
maxAge?: number;
|
|
9
|
+
expires?: Date;
|
|
10
|
+
httpOnly?: boolean;
|
|
11
|
+
secure?: boolean;
|
|
12
|
+
sameSite?: "strict" | "lax" | "none";
|
|
13
|
+
domain?: string;
|
|
14
|
+
path?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const cookie = (
|
|
18
|
+
options: {
|
|
19
|
+
secret?: string;
|
|
20
|
+
signed?: boolean;
|
|
21
|
+
} = {},
|
|
22
|
+
): MiddlewareInterface => ({
|
|
23
|
+
name: "cookie",
|
|
24
|
+
version: "1.0.0",
|
|
25
|
+
metadata: {
|
|
26
|
+
name: "cookie",
|
|
27
|
+
version: "1.0.0",
|
|
28
|
+
description: "Cookie parsing and setting middleware with security features",
|
|
29
|
+
author: "MoroJS Team",
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
install: async (hooks: any, options: any = {}) => {
|
|
33
|
+
logger.debug("Installing cookie middleware", "Installation");
|
|
34
|
+
|
|
35
|
+
hooks.before("request", async (context: HookContext) => {
|
|
36
|
+
const req = context.request as any;
|
|
37
|
+
const res = context.response as any;
|
|
38
|
+
|
|
39
|
+
// Parse cookies from request
|
|
40
|
+
req.cookies = parseCookies(req.headers.cookie || "");
|
|
41
|
+
|
|
42
|
+
// Add cookie methods to response
|
|
43
|
+
res.cookie = (
|
|
44
|
+
name: string,
|
|
45
|
+
value: string,
|
|
46
|
+
options: CookieOptions = {},
|
|
47
|
+
) => {
|
|
48
|
+
const cookieValue = encodeURIComponent(value);
|
|
49
|
+
let cookieString = `${name}=${cookieValue}`;
|
|
50
|
+
|
|
51
|
+
if (options.maxAge) cookieString += `; Max-Age=${options.maxAge}`;
|
|
52
|
+
if (options.expires)
|
|
53
|
+
cookieString += `; Expires=${options.expires.toUTCString()}`;
|
|
54
|
+
if (options.httpOnly) cookieString += "; HttpOnly";
|
|
55
|
+
if (options.secure) cookieString += "; Secure";
|
|
56
|
+
if (options.sameSite) cookieString += `; SameSite=${options.sameSite}`;
|
|
57
|
+
if (options.domain) cookieString += `; Domain=${options.domain}`;
|
|
58
|
+
if (options.path) cookieString += `; Path=${options.path}`;
|
|
59
|
+
|
|
60
|
+
const existingCookies = res.getHeader("Set-Cookie") || [];
|
|
61
|
+
const cookies = Array.isArray(existingCookies)
|
|
62
|
+
? [...existingCookies]
|
|
63
|
+
: [existingCookies as string];
|
|
64
|
+
cookies.push(cookieString);
|
|
65
|
+
res.setHeader("Set-Cookie", cookies);
|
|
66
|
+
|
|
67
|
+
return res;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
res.clearCookie = (name: string, options: CookieOptions = {}) => {
|
|
71
|
+
const clearOptions = { ...options, expires: new Date(0), maxAge: 0 };
|
|
72
|
+
return res.cookie(name, "", clearOptions);
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
function parseCookies(cookieHeader: string): Record<string, string> {
|
|
79
|
+
const cookies: Record<string, string> = {};
|
|
80
|
+
if (!cookieHeader) return cookies;
|
|
81
|
+
|
|
82
|
+
cookieHeader.split(";").forEach((cookie) => {
|
|
83
|
+
const [name, value] = cookie.trim().split("=");
|
|
84
|
+
if (name && value) {
|
|
85
|
+
cookies[name] = decodeURIComponent(value);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
return cookies;
|
|
90
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// CORS Middleware
|
|
2
|
+
import { MiddlewareInterface, HookContext } from "../../../types/hooks";
|
|
3
|
+
import { createFrameworkLogger } from "../../logger";
|
|
4
|
+
|
|
5
|
+
const logger = createFrameworkLogger("CorsMiddleware");
|
|
6
|
+
|
|
7
|
+
export const cors = (options: any = {}): MiddlewareInterface => ({
|
|
8
|
+
name: "cors",
|
|
9
|
+
version: "1.0.0",
|
|
10
|
+
metadata: {
|
|
11
|
+
name: "cors",
|
|
12
|
+
version: "1.0.0",
|
|
13
|
+
description: "Cross-Origin Resource Sharing middleware",
|
|
14
|
+
author: "MoroJS Team",
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
install: async (hooks: any, options: any = {}) => {
|
|
18
|
+
logger.debug("Installing CORS middleware", "Installation", { options });
|
|
19
|
+
|
|
20
|
+
hooks.before("request", async (context: HookContext) => {
|
|
21
|
+
const response = context.response as any;
|
|
22
|
+
|
|
23
|
+
response.setHeader("Access-Control-Allow-Origin", options.origin || "*");
|
|
24
|
+
response.setHeader(
|
|
25
|
+
"Access-Control-Allow-Methods",
|
|
26
|
+
options.methods || "GET,POST,PUT,DELETE,OPTIONS",
|
|
27
|
+
);
|
|
28
|
+
response.setHeader(
|
|
29
|
+
"Access-Control-Allow-Headers",
|
|
30
|
+
options.headers || "Content-Type,Authorization",
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
if (options.credentials) {
|
|
34
|
+
response.setHeader("Access-Control-Allow-Credentials", "true");
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
},
|
|
38
|
+
});
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// Content Security Policy Middleware
|
|
2
|
+
import { MiddlewareInterface, HookContext } from "../../../types/hooks";
|
|
3
|
+
import { createFrameworkLogger } from "../../logger";
|
|
4
|
+
|
|
5
|
+
const logger = createFrameworkLogger("CSPMiddleware");
|
|
6
|
+
|
|
7
|
+
export const csp = (
|
|
8
|
+
options: {
|
|
9
|
+
directives?: {
|
|
10
|
+
defaultSrc?: string[];
|
|
11
|
+
scriptSrc?: string[];
|
|
12
|
+
styleSrc?: string[];
|
|
13
|
+
imgSrc?: string[];
|
|
14
|
+
connectSrc?: string[];
|
|
15
|
+
fontSrc?: string[];
|
|
16
|
+
objectSrc?: string[];
|
|
17
|
+
mediaSrc?: string[];
|
|
18
|
+
frameSrc?: string[];
|
|
19
|
+
childSrc?: string[];
|
|
20
|
+
workerSrc?: string[];
|
|
21
|
+
formAction?: string[];
|
|
22
|
+
upgradeInsecureRequests?: boolean;
|
|
23
|
+
blockAllMixedContent?: boolean;
|
|
24
|
+
};
|
|
25
|
+
reportOnly?: boolean;
|
|
26
|
+
reportUri?: string;
|
|
27
|
+
nonce?: boolean;
|
|
28
|
+
} = {},
|
|
29
|
+
): MiddlewareInterface => ({
|
|
30
|
+
name: "csp",
|
|
31
|
+
version: "1.0.0",
|
|
32
|
+
metadata: {
|
|
33
|
+
name: "csp",
|
|
34
|
+
version: "1.0.0",
|
|
35
|
+
description:
|
|
36
|
+
"Content Security Policy middleware with nonce support and violation reporting",
|
|
37
|
+
author: "MoroJS Team",
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
install: async (hooks: any, middlewareOptions: any = {}) => {
|
|
41
|
+
logger.debug("Installing CSP middleware", "Installation");
|
|
42
|
+
|
|
43
|
+
hooks.before("request", async (context: HookContext) => {
|
|
44
|
+
const req = context.request as any;
|
|
45
|
+
const res = context.response as any;
|
|
46
|
+
|
|
47
|
+
const directives = options.directives || {
|
|
48
|
+
defaultSrc: ["'self'"],
|
|
49
|
+
scriptSrc: ["'self'"],
|
|
50
|
+
styleSrc: ["'self'", "'unsafe-inline'"],
|
|
51
|
+
imgSrc: ["'self'", "data:", "https:"],
|
|
52
|
+
connectSrc: ["'self'"],
|
|
53
|
+
fontSrc: ["'self'"],
|
|
54
|
+
objectSrc: ["'none'"],
|
|
55
|
+
mediaSrc: ["'self'"],
|
|
56
|
+
frameSrc: ["'none'"],
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Generate nonce if requested
|
|
60
|
+
let nonce: string | undefined;
|
|
61
|
+
if (options.nonce) {
|
|
62
|
+
const crypto = require("crypto");
|
|
63
|
+
nonce = crypto.randomBytes(16).toString("base64");
|
|
64
|
+
req.cspNonce = nonce;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Build CSP header value
|
|
68
|
+
const cspParts: string[] = [];
|
|
69
|
+
|
|
70
|
+
for (const [directive, sources] of Object.entries(directives)) {
|
|
71
|
+
if (directive === "upgradeInsecureRequests" && sources === true) {
|
|
72
|
+
cspParts.push("upgrade-insecure-requests");
|
|
73
|
+
} else if (directive === "blockAllMixedContent" && sources === true) {
|
|
74
|
+
cspParts.push("block-all-mixed-content");
|
|
75
|
+
} else if (Array.isArray(sources)) {
|
|
76
|
+
let sourceList = sources.join(" ");
|
|
77
|
+
|
|
78
|
+
// Add nonce to script-src and style-src if enabled
|
|
79
|
+
if (
|
|
80
|
+
nonce &&
|
|
81
|
+
(directive === "scriptSrc" || directive === "styleSrc")
|
|
82
|
+
) {
|
|
83
|
+
sourceList += ` 'nonce-${nonce}'`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Convert camelCase to kebab-case
|
|
87
|
+
const kebabDirective = directive
|
|
88
|
+
.replace(/([A-Z])/g, "-$1")
|
|
89
|
+
.toLowerCase();
|
|
90
|
+
cspParts.push(`${kebabDirective} ${sourceList}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Add report-uri if specified
|
|
95
|
+
if (options.reportUri) {
|
|
96
|
+
cspParts.push(`report-uri ${options.reportUri}`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const cspValue = cspParts.join("; ");
|
|
100
|
+
const headerName = options.reportOnly
|
|
101
|
+
? "Content-Security-Policy-Report-Only"
|
|
102
|
+
: "Content-Security-Policy";
|
|
103
|
+
|
|
104
|
+
res.setHeader(headerName, cspValue);
|
|
105
|
+
});
|
|
106
|
+
},
|
|
107
|
+
});
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// CSRF Protection Middleware
|
|
2
|
+
import { MiddlewareInterface, HookContext } from "../../../types/hooks";
|
|
3
|
+
import { createFrameworkLogger } from "../../logger";
|
|
4
|
+
|
|
5
|
+
const logger = createFrameworkLogger("CSRFMiddleware");
|
|
6
|
+
|
|
7
|
+
export const csrf = (
|
|
8
|
+
options: {
|
|
9
|
+
secret?: string;
|
|
10
|
+
tokenLength?: number;
|
|
11
|
+
cookieName?: string;
|
|
12
|
+
headerName?: string;
|
|
13
|
+
ignoreMethods?: string[];
|
|
14
|
+
sameSite?: boolean;
|
|
15
|
+
} = {},
|
|
16
|
+
): MiddlewareInterface => ({
|
|
17
|
+
name: "csrf",
|
|
18
|
+
version: "1.0.0",
|
|
19
|
+
metadata: {
|
|
20
|
+
name: "csrf",
|
|
21
|
+
version: "1.0.0",
|
|
22
|
+
description:
|
|
23
|
+
"CSRF protection middleware with token generation and validation",
|
|
24
|
+
author: "MoroJS Team",
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
install: async (hooks: any, middlewareOptions: any = {}) => {
|
|
28
|
+
logger.debug("Installing CSRF middleware", "Installation");
|
|
29
|
+
|
|
30
|
+
const secret = options.secret || "moro-csrf-secret";
|
|
31
|
+
const tokenLength = options.tokenLength || 32;
|
|
32
|
+
const cookieName = options.cookieName || "_csrf";
|
|
33
|
+
const headerName = options.headerName || "x-csrf-token";
|
|
34
|
+
const ignoreMethods = options.ignoreMethods || ["GET", "HEAD", "OPTIONS"];
|
|
35
|
+
|
|
36
|
+
const generateToken = () => {
|
|
37
|
+
const crypto = require("crypto");
|
|
38
|
+
return crypto.randomBytes(tokenLength).toString("hex");
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const verifyToken = (token: string, sessionToken: string) => {
|
|
42
|
+
return token && sessionToken && token === sessionToken;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
hooks.before("request", async (context: HookContext) => {
|
|
46
|
+
const req = context.request as any;
|
|
47
|
+
const res = context.response as any;
|
|
48
|
+
|
|
49
|
+
// Add CSRF token generation method
|
|
50
|
+
req.csrfToken = () => {
|
|
51
|
+
if (!req._csrfToken) {
|
|
52
|
+
req._csrfToken = generateToken();
|
|
53
|
+
// Set token in cookie
|
|
54
|
+
res.cookie(cookieName, req._csrfToken, {
|
|
55
|
+
httpOnly: true,
|
|
56
|
+
sameSite: options.sameSite !== false ? "strict" : undefined,
|
|
57
|
+
secure:
|
|
58
|
+
req.headers["x-forwarded-proto"] === "https" ||
|
|
59
|
+
(req.socket as any).encrypted,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
return req._csrfToken;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// Skip verification for safe methods
|
|
66
|
+
if (ignoreMethods.includes(req.method!)) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Get token from header or body
|
|
71
|
+
const token =
|
|
72
|
+
req.headers[headerName] ||
|
|
73
|
+
(req.body && req.body._csrf) ||
|
|
74
|
+
(req.query && req.query._csrf);
|
|
75
|
+
|
|
76
|
+
// Get session token from cookie
|
|
77
|
+
const sessionToken = req.cookies?.[cookieName];
|
|
78
|
+
|
|
79
|
+
if (!verifyToken(token as string, sessionToken || "")) {
|
|
80
|
+
const error = new Error("Invalid CSRF token");
|
|
81
|
+
(error as any).status = 403;
|
|
82
|
+
(error as any).code = "CSRF_TOKEN_MISMATCH";
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
},
|
|
87
|
+
});
|