@medyll/idae-api 0.137.0 → 0.139.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/README.md +61 -0
- package/dist/__tests__/README.md +583 -0
- package/dist/__tests__/fixtures/mockData.d.ts +219 -0
- package/dist/__tests__/fixtures/mockData.js +243 -0
- package/dist/__tests__/helpers/testUtils.d.ts +153 -0
- package/dist/__tests__/helpers/testUtils.js +328 -0
- package/dist/client/IdaeApiClient.d.ts +7 -0
- package/dist/client/IdaeApiClient.js +15 -2
- package/dist/client/IdaeApiClientCollection.d.ts +17 -9
- package/dist/client/IdaeApiClientCollection.js +32 -11
- package/dist/client/IdaeApiClientConfig.d.ts +2 -0
- package/dist/client/IdaeApiClientConfig.js +10 -2
- package/dist/client/IdaeApiClientRequest.js +30 -15
- package/dist/config/routeDefinitions.d.ts +8 -0
- package/dist/config/routeDefinitions.js +63 -15
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/openApi/redoc.html +12 -0
- package/dist/openApi/swagger-ui.html +23 -0
- package/dist/server/IdaeApi.d.ts +15 -0
- package/dist/server/IdaeApi.js +94 -22
- package/dist/server/engine/requestDatabaseManager.d.ts +1 -1
- package/dist/server/engine/requestDatabaseManager.js +6 -10
- package/dist/server/engine/routeManager.d.ts +1 -0
- package/dist/server/engine/routeManager.js +33 -17
- package/dist/server/middleware/README.md +46 -0
- package/dist/server/middleware/authMiddleware.d.ts +63 -0
- package/dist/server/middleware/authMiddleware.js +121 -12
- package/dist/server/middleware/authorizationMiddleware.d.ts +18 -0
- package/dist/server/middleware/authorizationMiddleware.js +38 -0
- package/dist/server/middleware/databaseMiddleware.d.ts +6 -1
- package/dist/server/middleware/databaseMiddleware.js +111 -6
- package/dist/server/middleware/docsMiddleware.d.ts +13 -0
- package/dist/server/middleware/docsMiddleware.js +30 -0
- package/dist/server/middleware/healthMiddleware.d.ts +15 -0
- package/dist/server/middleware/healthMiddleware.js +19 -0
- package/dist/server/middleware/mcpMiddleware.d.ts +10 -0
- package/dist/server/middleware/mcpMiddleware.js +14 -0
- package/dist/server/middleware/openApiMiddleware.d.ts +7 -0
- package/dist/server/middleware/openApiMiddleware.js +20 -0
- package/dist/server/middleware/tenantContextMiddleware.d.ts +25 -0
- package/dist/server/middleware/tenantContextMiddleware.js +31 -0
- package/dist/server/middleware/validationLayer.d.ts +8 -0
- package/dist/server/middleware/validationLayer.js +23 -0
- package/dist/server/middleware/validationMiddleware.d.ts +16 -0
- package/dist/server/middleware/validationMiddleware.js +30 -0
- package/package.json +18 -4
|
@@ -1,56 +1,104 @@
|
|
|
1
1
|
// packages\idae-api\src\lib\config\routeDefinitions.ts
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { createValidationMiddleware } from "../server/middleware/validationMiddleware.js";
|
|
4
|
+
const allowedQueryCommands = new Set([
|
|
5
|
+
"find",
|
|
6
|
+
"findById",
|
|
7
|
+
"findOne",
|
|
8
|
+
"create",
|
|
9
|
+
"update",
|
|
10
|
+
"updateWhere",
|
|
11
|
+
"deleteById",
|
|
12
|
+
"deleteWhere",
|
|
13
|
+
"transaction",
|
|
14
|
+
]);
|
|
2
15
|
export const routes = [
|
|
3
16
|
{
|
|
4
17
|
method: "get",
|
|
5
18
|
path: "/:collectionName",
|
|
19
|
+
validation: {
|
|
20
|
+
paramsSchema: z.object({ collectionName: z.string().min(1) }),
|
|
21
|
+
querySchema: z.record(z.any()).optional(),
|
|
22
|
+
},
|
|
6
23
|
handler: async (service, params, body, query) => service.find({ query }),
|
|
7
24
|
},
|
|
8
25
|
{
|
|
9
26
|
method: "get",
|
|
10
27
|
path: "/:collectionName/:id",
|
|
28
|
+
validation: {
|
|
29
|
+
paramsSchema: z.object({ collectionName: z.string().min(1), id: z.string().min(1) }),
|
|
30
|
+
},
|
|
11
31
|
handler: async (service, params) => service.findById(params.id),
|
|
12
32
|
},
|
|
13
33
|
{
|
|
14
34
|
method: "post",
|
|
15
35
|
path: "/:collectionName",
|
|
36
|
+
validation: {
|
|
37
|
+
paramsSchema: z.object({ collectionName: z.string().min(1) }),
|
|
38
|
+
bodySchema: z.record(z.any()),
|
|
39
|
+
},
|
|
16
40
|
handler: async (service, params, body) => service.create(body),
|
|
17
41
|
},
|
|
18
42
|
{
|
|
19
43
|
method: "put",
|
|
20
44
|
path: "/:collectionName/:id",
|
|
45
|
+
validation: {
|
|
46
|
+
paramsSchema: z.object({ collectionName: z.string().min(1), id: z.string().min(1) }),
|
|
47
|
+
bodySchema: z.record(z.any()),
|
|
48
|
+
},
|
|
21
49
|
handler: async (service, params, body) => service.update(params.id, body),
|
|
22
50
|
},
|
|
23
51
|
{
|
|
24
52
|
method: "delete",
|
|
25
53
|
path: "/:collectionName/:id",
|
|
54
|
+
validation: {
|
|
55
|
+
paramsSchema: z.object({ collectionName: z.string().min(1), id: z.string().min(1) }),
|
|
56
|
+
},
|
|
26
57
|
handler: async (service, params) => service.deleteById(params.id),
|
|
27
58
|
},
|
|
28
59
|
{
|
|
29
60
|
method: "delete",
|
|
30
61
|
path: "/:collectionName",
|
|
62
|
+
validation: {
|
|
63
|
+
paramsSchema: z.object({ collectionName: z.string().min(1) }),
|
|
64
|
+
},
|
|
31
65
|
handler: async (service, params) => service.deleteWhere(params),
|
|
32
66
|
},
|
|
33
67
|
{
|
|
34
|
-
method:
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
path: "/query/:collectionName/:command/:parameters?",
|
|
68
|
+
method: "post",
|
|
69
|
+
path: "/query/:collectionName/:command/:parameters",
|
|
70
|
+
requiresAuth: true,
|
|
71
|
+
validation: {
|
|
72
|
+
paramsSchema: z.object({
|
|
73
|
+
collectionName: z.string().min(1),
|
|
74
|
+
command: z.string().min(1),
|
|
75
|
+
parameters: z.string().optional(),
|
|
76
|
+
}),
|
|
77
|
+
bodySchema: z.record(z.any()).optional(),
|
|
78
|
+
},
|
|
46
79
|
handler: async (service, params, body) => {
|
|
47
|
-
|
|
48
|
-
|
|
80
|
+
const command = params.command;
|
|
81
|
+
if (!allowedQueryCommands.has(command)) {
|
|
82
|
+
const error = new Error("Query command not allowed");
|
|
83
|
+
error.status = 400;
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
const payload = body && typeof body === "object" ? body : {};
|
|
87
|
+
const method = service?.[command];
|
|
88
|
+
if (typeof method !== "function") {
|
|
89
|
+
const error = new Error("Query command not implemented");
|
|
90
|
+
error.status = 400;
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
return method({ query: payload });
|
|
49
94
|
},
|
|
50
95
|
},
|
|
51
96
|
{
|
|
52
97
|
method: ["dbs", "collections"], // default method is then GET or OPTIONS (set further)
|
|
53
|
-
path: "/methods/:methodName/:params
|
|
98
|
+
path: "/methods/:methodName/:params",
|
|
99
|
+
validation: {
|
|
100
|
+
paramsSchema: z.object({ methodName: z.string().min(1), params: z.string().optional() }),
|
|
101
|
+
},
|
|
54
102
|
handler: async (service, params) => { },
|
|
55
103
|
},
|
|
56
104
|
];
|
package/dist/index.d.ts
CHANGED
|
@@ -6,9 +6,19 @@ export * from './client/IdaeApiClientCollection.js';
|
|
|
6
6
|
export * from './client/IdaeApiClient.js';
|
|
7
7
|
export * from './@types/types.js';
|
|
8
8
|
export * from './server/services/AuthService.js';
|
|
9
|
+
export * from './server/middleware/validationMiddleware.js';
|
|
10
|
+
export * from './server/middleware/validationLayer.js';
|
|
11
|
+
export * from './server/middleware/tenantContextMiddleware.js';
|
|
12
|
+
export * from './server/middleware/openApiMiddleware.js';
|
|
13
|
+
export * from './server/middleware/mcpMiddleware.js';
|
|
14
|
+
export * from './server/middleware/healthMiddleware.js';
|
|
15
|
+
export * from './server/middleware/docsMiddleware.js';
|
|
9
16
|
export * from './server/middleware/databaseMiddleware.js';
|
|
17
|
+
export * from './server/middleware/authorizationMiddleware.js';
|
|
10
18
|
export * from './server/middleware/authMiddleware.js';
|
|
11
19
|
export * from './server/engine/types.js';
|
|
12
20
|
export * from './server/engine/routeManager.js';
|
|
13
21
|
export * from './server/engine/requestDatabaseManager.js';
|
|
14
22
|
export * from './server/engine/mongooseConnectionManager.js';
|
|
23
|
+
export * from './__tests__/helpers/testUtils.js';
|
|
24
|
+
export * from './__tests__/fixtures/mockData.js';
|
package/dist/index.js
CHANGED
|
@@ -7,9 +7,19 @@ export * from './client/IdaeApiClientCollection.js';
|
|
|
7
7
|
export * from './client/IdaeApiClient.js';
|
|
8
8
|
export * from './@types/types.js';
|
|
9
9
|
export * from './server/services/AuthService.js';
|
|
10
|
+
export * from './server/middleware/validationMiddleware.js';
|
|
11
|
+
export * from './server/middleware/validationLayer.js';
|
|
12
|
+
export * from './server/middleware/tenantContextMiddleware.js';
|
|
13
|
+
export * from './server/middleware/openApiMiddleware.js';
|
|
14
|
+
export * from './server/middleware/mcpMiddleware.js';
|
|
15
|
+
export * from './server/middleware/healthMiddleware.js';
|
|
16
|
+
export * from './server/middleware/docsMiddleware.js';
|
|
10
17
|
export * from './server/middleware/databaseMiddleware.js';
|
|
18
|
+
export * from './server/middleware/authorizationMiddleware.js';
|
|
11
19
|
export * from './server/middleware/authMiddleware.js';
|
|
12
20
|
export * from './server/engine/types.js';
|
|
13
21
|
export * from './server/engine/routeManager.js';
|
|
14
22
|
export * from './server/engine/requestDatabaseManager.js';
|
|
15
23
|
export * from './server/engine/mongooseConnectionManager.js';
|
|
24
|
+
export * from './__tests__/helpers/testUtils.js';
|
|
25
|
+
export * from './__tests__/fixtures/mockData.js';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!-- Minimal Redoc HTML, loads /openapi.json -->
|
|
2
|
+
<!DOCTYPE html>
|
|
3
|
+
<html lang="en">
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<title>Redoc</title>
|
|
7
|
+
<script src="https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js"></script>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<redoc spec-url="/openapi.json"></redoc>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<!-- Minimal Swagger UI HTML, loads /openapi.json -->
|
|
2
|
+
<!DOCTYPE html>
|
|
3
|
+
<html lang="en">
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<title>Swagger UI</title>
|
|
7
|
+
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist/swagger-ui.css">
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="swagger-ui"></div>
|
|
11
|
+
<script src="https://unpkg.com/swagger-ui-dist/swagger-ui-bundle.js"></script>
|
|
12
|
+
<script>
|
|
13
|
+
window.onload = function() {
|
|
14
|
+
window.ui = SwaggerUIBundle({
|
|
15
|
+
url: '/openapi.json',
|
|
16
|
+
dom_id: '#swagger-ui',
|
|
17
|
+
presets: [SwaggerUIBundle.presets.apis],
|
|
18
|
+
layout: "BaseLayout"
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
</script>
|
|
22
|
+
</body>
|
|
23
|
+
</html>
|
package/dist/server/IdaeApi.d.ts
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
|
+
import '../../app.js';
|
|
1
2
|
import { type IdaeDbOptions } from "@medyll/idae-db";
|
|
2
3
|
import express from "express";
|
|
4
|
+
import { type CorsOptions } from "cors";
|
|
3
5
|
import { type RouteDefinition } from "../config/routeDefinitions.js";
|
|
4
6
|
import { RouteManager } from "./engine/routeManager.js";
|
|
7
|
+
type RateLimitOptions = {
|
|
8
|
+
windowMs?: number;
|
|
9
|
+
max?: number;
|
|
10
|
+
standardHeaders?: boolean | "draft-7" | "draft-6" | "draft-8";
|
|
11
|
+
legacyHeaders?: boolean;
|
|
12
|
+
[key: string]: any;
|
|
13
|
+
};
|
|
5
14
|
interface IdaeApiOptions {
|
|
6
15
|
port?: number;
|
|
7
16
|
routes?: RouteDefinition[];
|
|
@@ -10,6 +19,12 @@ interface IdaeApiOptions {
|
|
|
10
19
|
jwtSecret?: string;
|
|
11
20
|
tokenExpiration?: string;
|
|
12
21
|
idaeDbOptions?: IdaeDbOptions;
|
|
22
|
+
useMemoryDb?: boolean;
|
|
23
|
+
cors?: boolean | CorsOptions;
|
|
24
|
+
rateLimit?: false | RateLimitOptions;
|
|
25
|
+
payloadLimit?: string;
|
|
26
|
+
enableCompression?: boolean;
|
|
27
|
+
trustProxy?: boolean | number | string;
|
|
13
28
|
}
|
|
14
29
|
declare class IdaeApi {
|
|
15
30
|
#private;
|
package/dist/server/IdaeApi.js
CHANGED
|
@@ -9,15 +9,32 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
9
9
|
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
10
10
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
11
|
};
|
|
12
|
-
var _a, _IdaeApi_instance, _IdaeApi_app, _IdaeApi_idaeApiOptions, _IdaeApi_routeManager, _IdaeApi_serverInstance, _IdaeApi_state, _IdaeApi_authMiddleware;
|
|
12
|
+
var _a, _IdaeApi_instance, _IdaeApi_app, _IdaeApi_idaeApiOptions, _IdaeApi_routeManager, _IdaeApi_serverInstance, _IdaeApi_state, _IdaeApi_authMiddleware, _IdaeApi_configured;
|
|
13
|
+
// Force l'import des types globaux (Express.Request.user)
|
|
14
|
+
import '../../app.js';
|
|
15
|
+
import { createValidationMiddleware } from "./middleware/validationMiddleware.js";
|
|
16
|
+
import { openApiJsonHandler } from "./middleware/openApiMiddleware.js";
|
|
17
|
+
import { swaggerUiHandler, redocHandler } from "./middleware/docsMiddleware.js";
|
|
18
|
+
import { tenantContextMiddleware } from "./middleware/tenantContextMiddleware.js";
|
|
13
19
|
// packages\idae-api\src\lib\server\IdaeApi.ts
|
|
14
20
|
import {} from "@medyll/idae-db";
|
|
15
21
|
import express, {} from "express";
|
|
22
|
+
import compression from "compression";
|
|
23
|
+
import cors, {} from "cors";
|
|
16
24
|
import { idaeDbMiddleware } from "./middleware/databaseMiddleware.js";
|
|
17
25
|
import { routes as defaultRoutes, } from "../config/routeDefinitions.js";
|
|
18
26
|
import { AuthMiddleWare } from "./middleware/authMiddleware.js";
|
|
27
|
+
import helmet from "helmet";
|
|
19
28
|
import { RouteManager } from "./engine/routeManager.js";
|
|
29
|
+
import rateLimit from "express-rate-limit";
|
|
20
30
|
import qs from "qs";
|
|
31
|
+
import { healthHandler, readinessHandler } from "./middleware/healthMiddleware.js";
|
|
32
|
+
class HttpError extends Error {
|
|
33
|
+
constructor(status, message) {
|
|
34
|
+
super(message);
|
|
35
|
+
this.status = status;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
21
38
|
class IdaeApi {
|
|
22
39
|
constructor() {
|
|
23
40
|
_IdaeApi_app.set(this, void 0);
|
|
@@ -26,6 +43,7 @@ class IdaeApi {
|
|
|
26
43
|
_IdaeApi_serverInstance.set(this, void 0);
|
|
27
44
|
_IdaeApi_state.set(this, "stopped");
|
|
28
45
|
_IdaeApi_authMiddleware.set(this, null);
|
|
46
|
+
_IdaeApi_configured.set(this, false);
|
|
29
47
|
__classPrivateFieldSet(this, _IdaeApi_app, express(), "f");
|
|
30
48
|
__classPrivateFieldSet(this, _IdaeApi_idaeApiOptions, {}, "f");
|
|
31
49
|
__classPrivateFieldSet(this, _IdaeApi_routeManager, RouteManager.getInstance(), "f");
|
|
@@ -37,8 +55,6 @@ class IdaeApi {
|
|
|
37
55
|
parameterLimit: 1000,
|
|
38
56
|
});
|
|
39
57
|
});
|
|
40
|
-
this.initializeAuth();
|
|
41
|
-
this.configureIdaeApi();
|
|
42
58
|
}
|
|
43
59
|
static getInstance() {
|
|
44
60
|
if (!__classPrivateFieldGet(_a, _a, "f", _IdaeApi_instance)) {
|
|
@@ -53,7 +69,14 @@ class IdaeApi {
|
|
|
53
69
|
return __classPrivateFieldGet(this, _IdaeApi_app, "f");
|
|
54
70
|
}
|
|
55
71
|
setOptions(options) {
|
|
72
|
+
if (__classPrivateFieldGet(this, _IdaeApi_state, "f") === "running") {
|
|
73
|
+
throw new Error("Cannot change options while server is running");
|
|
74
|
+
}
|
|
56
75
|
__classPrivateFieldSet(this, _IdaeApi_idaeApiOptions, { ...__classPrivateFieldGet(this, _IdaeApi_idaeApiOptions, "f"), ...options }, "f");
|
|
76
|
+
__classPrivateFieldGet(this, _IdaeApi_app, "f").locals.idaeDbOptions = __classPrivateFieldGet(this, _IdaeApi_idaeApiOptions, "f").idaeDbOptions;
|
|
77
|
+
__classPrivateFieldGet(this, _IdaeApi_app, "f").locals.useMemoryDb = __classPrivateFieldGet(this, _IdaeApi_idaeApiOptions, "f").useMemoryDb;
|
|
78
|
+
this.initializeAuth();
|
|
79
|
+
__classPrivateFieldSet(this, _IdaeApi_configured, false, "f");
|
|
57
80
|
}
|
|
58
81
|
initializeAuth() {
|
|
59
82
|
if (__classPrivateFieldGet(this, _IdaeApi_idaeApiOptions, "f").enableAuth &&
|
|
@@ -66,30 +89,71 @@ class IdaeApi {
|
|
|
66
89
|
this.configureMiddleware();
|
|
67
90
|
this.configureRoutes();
|
|
68
91
|
this.configureErrorHandling();
|
|
92
|
+
__classPrivateFieldSet(this, _IdaeApi_configured, true, "f");
|
|
69
93
|
}
|
|
70
94
|
configureMiddleware() {
|
|
71
|
-
__classPrivateFieldGet(this,
|
|
72
|
-
__classPrivateFieldGet(this,
|
|
73
|
-
__classPrivateFieldGet(this,
|
|
74
|
-
|
|
75
|
-
|
|
95
|
+
const jsonLimit = __classPrivateFieldGet(this, _IdaeApi_idaeApiOptions, "f").payloadLimit ?? "1mb";
|
|
96
|
+
const urlEncodedLimit = __classPrivateFieldGet(this, _IdaeApi_idaeApiOptions, "f").payloadLimit ?? "1mb";
|
|
97
|
+
if (__classPrivateFieldGet(this, _IdaeApi_idaeApiOptions, "f").trustProxy) {
|
|
98
|
+
__classPrivateFieldGet(this, _IdaeApi_app, "f").set("trust proxy", __classPrivateFieldGet(this, _IdaeApi_idaeApiOptions, "f").trustProxy);
|
|
99
|
+
}
|
|
100
|
+
if (__classPrivateFieldGet(this, _IdaeApi_idaeApiOptions, "f").enableCompression !== false) {
|
|
101
|
+
__classPrivateFieldGet(this, _IdaeApi_app, "f").use(compression());
|
|
76
102
|
}
|
|
103
|
+
if (__classPrivateFieldGet(this, _IdaeApi_idaeApiOptions, "f").cors !== false) {
|
|
104
|
+
const corsOptions = __classPrivateFieldGet(this, _IdaeApi_idaeApiOptions, "f").cors === true || __classPrivateFieldGet(this, _IdaeApi_idaeApiOptions, "f").cors === undefined
|
|
105
|
+
? {}
|
|
106
|
+
: __classPrivateFieldGet(this, _IdaeApi_idaeApiOptions, "f").cors;
|
|
107
|
+
__classPrivateFieldGet(this, _IdaeApi_app, "f").use(cors(corsOptions));
|
|
108
|
+
}
|
|
109
|
+
__classPrivateFieldGet(this, _IdaeApi_app, "f").use(helmet());
|
|
110
|
+
if (__classPrivateFieldGet(this, _IdaeApi_idaeApiOptions, "f").rateLimit !== false) {
|
|
111
|
+
const limiterOptions = __classPrivateFieldGet(this, _IdaeApi_idaeApiOptions, "f").rateLimit ?? {
|
|
112
|
+
windowMs: 60000,
|
|
113
|
+
max: 100,
|
|
114
|
+
standardHeaders: "draft-7",
|
|
115
|
+
legacyHeaders: false,
|
|
116
|
+
};
|
|
117
|
+
__classPrivateFieldGet(this, _IdaeApi_app, "f").use(rateLimit(limiterOptions));
|
|
118
|
+
}
|
|
119
|
+
__classPrivateFieldGet(this, _IdaeApi_app, "f").use(express.json({ limit: jsonLimit }));
|
|
120
|
+
__classPrivateFieldGet(this, _IdaeApi_app, "f").use(express.urlencoded({ extended: true, limit: urlEncodedLimit }));
|
|
121
|
+
__classPrivateFieldGet(this, _IdaeApi_app, "f").use("/:collectionName", idaeDbMiddleware);
|
|
122
|
+
// Do not inject tenant context middleware globally; it will be added per-route for protected routes
|
|
77
123
|
}
|
|
78
124
|
configureRoutes() {
|
|
125
|
+
// Health and readiness endpoints (always unprotected)
|
|
126
|
+
__classPrivateFieldGet(this, _IdaeApi_app, "f").get("/health", healthHandler);
|
|
127
|
+
__classPrivateFieldGet(this, _IdaeApi_app, "f").get("/ready", readinessHandler);
|
|
128
|
+
// OpenAPI and docs endpoints (always unprotected)
|
|
129
|
+
__classPrivateFieldGet(this, _IdaeApi_app, "f").get("/openapi.json", openApiJsonHandler);
|
|
130
|
+
__classPrivateFieldGet(this, _IdaeApi_app, "f").get("/docs", swaggerUiHandler);
|
|
131
|
+
__classPrivateFieldGet(this, _IdaeApi_app, "f").get("/redoc", redocHandler);
|
|
132
|
+
if (__classPrivateFieldGet(this, _IdaeApi_authMiddleware, "f")) {
|
|
133
|
+
__classPrivateFieldGet(this, _IdaeApi_authMiddleware, "f").configureAuthRoutes(__classPrivateFieldGet(this, _IdaeApi_app, "f"));
|
|
134
|
+
}
|
|
79
135
|
__classPrivateFieldGet(this, _IdaeApi_routeManager, "f").addRoutes(defaultRoutes);
|
|
80
136
|
if (__classPrivateFieldGet(this, _IdaeApi_idaeApiOptions, "f").routes) {
|
|
81
137
|
__classPrivateFieldGet(this, _IdaeApi_routeManager, "f").addRoutes(__classPrivateFieldGet(this, _IdaeApi_idaeApiOptions, "f").routes);
|
|
82
138
|
}
|
|
83
139
|
__classPrivateFieldGet(this, _IdaeApi_routeManager, "f").getRoutes().forEach(this.addRouteToExpress.bind(this));
|
|
84
|
-
if (__classPrivateFieldGet(this, _IdaeApi_authMiddleware, "f")) {
|
|
85
|
-
__classPrivateFieldGet(this, _IdaeApi_authMiddleware, "f").configureAuthRoutes(__classPrivateFieldGet(this, _IdaeApi_app, "f"));
|
|
86
|
-
}
|
|
87
140
|
}
|
|
88
141
|
// Add a route to Express
|
|
89
142
|
addRouteToExpress(route) {
|
|
90
143
|
const handlers = [];
|
|
144
|
+
// Add validation middleware if present
|
|
145
|
+
if (route.validation) {
|
|
146
|
+
handlers.push(createValidationMiddleware(route.validation));
|
|
147
|
+
}
|
|
148
|
+
// Add auth and tenant context middleware for protected routes
|
|
91
149
|
if (route.requiresAuth && __classPrivateFieldGet(this, _IdaeApi_authMiddleware, "f")) {
|
|
92
150
|
handlers.push(__classPrivateFieldGet(this, _IdaeApi_authMiddleware, "f").createMiddleware());
|
|
151
|
+
handlers.push(tenantContextMiddleware({ required: true }));
|
|
152
|
+
}
|
|
153
|
+
// Add RBAC/ABAC authorization middleware if specified
|
|
154
|
+
if (route.authorization) {
|
|
155
|
+
const { authorize } = require("./middleware/authorizationMiddleware.js");
|
|
156
|
+
handlers.push(authorize(route.authorization));
|
|
93
157
|
}
|
|
94
158
|
handlers.push(this.handleRequest(route.handler));
|
|
95
159
|
if (Array.isArray(route.method)) {
|
|
@@ -101,8 +165,15 @@ class IdaeApi {
|
|
|
101
165
|
}
|
|
102
166
|
configureErrorHandling() {
|
|
103
167
|
__classPrivateFieldGet(this, _IdaeApi_app, "f").use((err, req, res, next) => {
|
|
104
|
-
|
|
105
|
-
|
|
168
|
+
const status = err?.status && Number.isInteger(err.status)
|
|
169
|
+
? err.status
|
|
170
|
+
: 500;
|
|
171
|
+
const isServerError = status >= 500;
|
|
172
|
+
const message = isServerError ? "Internal Server Error" : err.message;
|
|
173
|
+
if (isServerError) {
|
|
174
|
+
console.error(err.stack ?? err.message);
|
|
175
|
+
}
|
|
176
|
+
res.status(status).json({ error: message });
|
|
106
177
|
});
|
|
107
178
|
}
|
|
108
179
|
start() {
|
|
@@ -110,7 +181,11 @@ class IdaeApi {
|
|
|
110
181
|
console.log("Server is already running.");
|
|
111
182
|
return;
|
|
112
183
|
}
|
|
113
|
-
|
|
184
|
+
if (!__classPrivateFieldGet(this, _IdaeApi_configured, "f")) {
|
|
185
|
+
this.initializeAuth();
|
|
186
|
+
this.configureIdaeApi();
|
|
187
|
+
}
|
|
188
|
+
const port = __classPrivateFieldGet(this, _IdaeApi_idaeApiOptions, "f").port ?? 3000;
|
|
114
189
|
__classPrivateFieldSet(this, _IdaeApi_serverInstance, __classPrivateFieldGet(this, _IdaeApi_app, "f").listen(port, () => {
|
|
115
190
|
console.log(`Server is running on port: ${port}`);
|
|
116
191
|
__classPrivateFieldSet(this, _IdaeApi_state, "running", "f");
|
|
@@ -157,18 +232,15 @@ class IdaeApi {
|
|
|
157
232
|
try {
|
|
158
233
|
const connectedCollection = req.connectedCollection;
|
|
159
234
|
if (!connectedCollection) {
|
|
160
|
-
throw new
|
|
235
|
+
throw new HttpError(500, "Database connection not established");
|
|
161
236
|
}
|
|
162
|
-
console.log("----------------------------------------------");
|
|
163
|
-
console.log("body", req.body);
|
|
164
|
-
console.log("params", req.params);
|
|
165
|
-
console.log("query", req.query);
|
|
166
|
-
console.log("dbName", req.dbName);
|
|
167
|
-
// const result = await action(databaseService, req.params, req.body);
|
|
168
237
|
const result = await action(connectedCollection, req.params, req.body, req.query.params ?? req.query);
|
|
169
238
|
res.json(result);
|
|
170
239
|
}
|
|
171
240
|
catch (error) {
|
|
241
|
+
if (!error?.status) {
|
|
242
|
+
error.status = 500;
|
|
243
|
+
}
|
|
172
244
|
next(error);
|
|
173
245
|
}
|
|
174
246
|
};
|
|
@@ -178,7 +250,7 @@ class IdaeApi {
|
|
|
178
250
|
return __classPrivateFieldGet(this, _IdaeApi_routeManager, "f");
|
|
179
251
|
}
|
|
180
252
|
}
|
|
181
|
-
_a = IdaeApi, _IdaeApi_app = new WeakMap(), _IdaeApi_idaeApiOptions = new WeakMap(), _IdaeApi_routeManager = new WeakMap(), _IdaeApi_serverInstance = new WeakMap(), _IdaeApi_state = new WeakMap(), _IdaeApi_authMiddleware = new WeakMap();
|
|
253
|
+
_a = IdaeApi, _IdaeApi_app = new WeakMap(), _IdaeApi_idaeApiOptions = new WeakMap(), _IdaeApi_routeManager = new WeakMap(), _IdaeApi_serverInstance = new WeakMap(), _IdaeApi_state = new WeakMap(), _IdaeApi_authMiddleware = new WeakMap(), _IdaeApi_configured = new WeakMap();
|
|
182
254
|
_IdaeApi_instance = { value: null };
|
|
183
255
|
// Export a single instance of ApiServer
|
|
184
256
|
const idaeApi = IdaeApi.getInstance();
|
|
@@ -19,4 +19,4 @@ declare class RequestDatabaseManager {
|
|
|
19
19
|
}
|
|
20
20
|
declare const requestDatabaseManager: RequestDatabaseManager;
|
|
21
21
|
export default requestDatabaseManager;
|
|
22
|
-
export { RequestDatabaseManager as DatabaseManager, requestDatabaseManager };
|
|
22
|
+
export { RequestDatabaseManager, RequestDatabaseManager as DatabaseManager, requestDatabaseManager };
|
|
@@ -18,23 +18,19 @@ class RequestDatabaseManager {
|
|
|
18
18
|
return RequestDatabaseManager.instance;
|
|
19
19
|
}
|
|
20
20
|
fromReq(req) {
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
const
|
|
21
|
+
const rawCollectionName = req.params?.collectionName ?? "default";
|
|
22
|
+
const [dbPart, ...rest] = rawCollectionName.split(".");
|
|
23
|
+
const dbName = rest.length > 0 ? dbPart || this.config.defaultDbName : this.config.defaultDbName;
|
|
24
|
+
const collectionName = rest.length > 0 ? rest.join(".") || "default" : rawCollectionName || "default";
|
|
24
25
|
const dbUri = `${this.config.connectionPrefix}${this.config.host}:${this.config.port}/${dbName}`;
|
|
25
26
|
return {
|
|
26
27
|
dbName,
|
|
27
|
-
collectionName
|
|
28
|
+
collectionName,
|
|
28
29
|
dbUri,
|
|
29
30
|
};
|
|
30
|
-
function getDbNameFromCollectionName(collectionName, defaultDbName) {
|
|
31
|
-
return collectionName.includes(".")
|
|
32
|
-
? collectionName.split(".")[0]
|
|
33
|
-
: defaultDbName;
|
|
34
|
-
}
|
|
35
31
|
}
|
|
36
32
|
async closeAllConnections() { }
|
|
37
33
|
}
|
|
38
34
|
const requestDatabaseManager = RequestDatabaseManager.getInstance();
|
|
39
35
|
export default requestDatabaseManager;
|
|
40
|
-
export { RequestDatabaseManager as DatabaseManager, requestDatabaseManager };
|
|
36
|
+
export { RequestDatabaseManager, RequestDatabaseManager as DatabaseManager, requestDatabaseManager };
|
|
@@ -3,12 +3,22 @@ export class RouteManager {
|
|
|
3
3
|
this.routes = [];
|
|
4
4
|
}
|
|
5
5
|
static getInstance() {
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
const instance = RouteManager.instance ?? new RouteManager();
|
|
7
|
+
// In test runs we want isolated state between cases
|
|
8
|
+
if (process.env.NODE_ENV === "test") {
|
|
9
|
+
instance.clearRoutes();
|
|
8
10
|
}
|
|
9
|
-
|
|
11
|
+
RouteManager.instance = instance;
|
|
12
|
+
return instance;
|
|
10
13
|
}
|
|
11
14
|
addRoute(route) {
|
|
15
|
+
// Remove any existing route with the same path/method to avoid stale handlers
|
|
16
|
+
this.routes = this.routes.filter((r) => r.path !== route.path ||
|
|
17
|
+
(Array.isArray(r.method)
|
|
18
|
+
? !Array.isArray(route.method) || !route.method.some((m) => r.method.includes(m))
|
|
19
|
+
: Array.isArray(route.method)
|
|
20
|
+
? !route.method.includes(r.method)
|
|
21
|
+
: r.method !== route.method));
|
|
12
22
|
this.routes.push({ ...route, disabled: route.disabled || false });
|
|
13
23
|
}
|
|
14
24
|
addRoutes(routes) {
|
|
@@ -18,21 +28,27 @@ export class RouteManager {
|
|
|
18
28
|
return this.routes.filter((route) => !route.disabled);
|
|
19
29
|
}
|
|
20
30
|
enableRoute(path, method) {
|
|
21
|
-
|
|
22
|
-
(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
31
|
+
this.routes.forEach((r) => {
|
|
32
|
+
if (r.path === path &&
|
|
33
|
+
(Array.isArray(r.method)
|
|
34
|
+
? r.method.includes(method)
|
|
35
|
+
: r.method === method)) {
|
|
36
|
+
r.disabled = false;
|
|
37
|
+
}
|
|
38
|
+
});
|
|
28
39
|
}
|
|
29
40
|
disableRoute(path, method) {
|
|
30
|
-
|
|
31
|
-
(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
41
|
+
this.routes.forEach((r) => {
|
|
42
|
+
if (r.path === path &&
|
|
43
|
+
(Array.isArray(r.method)
|
|
44
|
+
? r.method.includes(method)
|
|
45
|
+
: r.method === method)) {
|
|
46
|
+
r.disabled = true;
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
// Utility primarily for tests to reset state safely
|
|
51
|
+
clearRoutes() {
|
|
52
|
+
this.routes = [];
|
|
37
53
|
}
|
|
38
54
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Middleware Documentation
|
|
2
|
+
|
|
3
|
+
This document describes all middleware available in the `@medyll/idae-api` package, their purpose, usage, and integration order.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Middleware in this project is located in `src/lib/server/middleware/`. Each middleware serves a specific purpose (validation, authentication, multi-tenancy, docs, health, etc.) and is applied in a specific order in the Express app lifecycle.
|
|
8
|
+
|
|
9
|
+
## List of Middleware
|
|
10
|
+
|
|
11
|
+
- **authMiddleware**: Handles JWT authentication and attaches user context to requests.
|
|
12
|
+
- **authorizationMiddleware**: Enforces RBAC/ABAC policies per route.
|
|
13
|
+
- **databaseMiddleware**: Injects `req.idaeDb` and manages per-request DB/collection context.
|
|
14
|
+
- **tenantContextMiddleware**: Extracts tenant context from JWT/user and enforces multi-tenancy.
|
|
15
|
+
- **validationMiddleware**: Validates request bodies/queries using Zod schemas.
|
|
16
|
+
- **validationLayer**: (Advanced) Layered validation, supports Zod and future OpenAPI/ajv.
|
|
17
|
+
- **docsMiddleware**: Serves Swagger UI and Redoc API docs.
|
|
18
|
+
- **openApiMiddleware**: Serves OpenAPI JSON spec.
|
|
19
|
+
- **healthMiddleware**: Provides health and readiness endpoints.
|
|
20
|
+
- **mcpMiddleware**: Placeholder for MCP-specific logic (future extension).
|
|
21
|
+
|
|
22
|
+
## Middleware Application Order
|
|
23
|
+
|
|
24
|
+
The recommended order (see `IdaeApi.ts`):
|
|
25
|
+
1. Express body/URL parsers
|
|
26
|
+
2. `authMiddleware` (if enabled)
|
|
27
|
+
3. `tenantContextMiddleware` (if multi-tenancy enabled)
|
|
28
|
+
4. `databaseMiddleware`
|
|
29
|
+
5. `validationMiddleware`/`validationLayer`
|
|
30
|
+
6. Custom routes
|
|
31
|
+
7. `docsMiddleware`, `openApiMiddleware`, `healthMiddleware`
|
|
32
|
+
8. Error handler
|
|
33
|
+
|
|
34
|
+
## Usage Example
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
import { authMiddleware, tenantContextMiddleware, databaseMiddleware } from './middleware';
|
|
38
|
+
app.use(authMiddleware(...));
|
|
39
|
+
app.use(tenantContextMiddleware(...));
|
|
40
|
+
app.use(databaseMiddleware);
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Notes
|
|
44
|
+
- Always apply `authMiddleware` before `databaseMiddleware` if using per-user DB context.
|
|
45
|
+
- `tenantContextMiddleware` is required for strict multi-tenancy.
|
|
46
|
+
- See each middleware file for detailed JSDoc and options.
|