@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.
Files changed (47) hide show
  1. package/README.md +61 -0
  2. package/dist/__tests__/README.md +583 -0
  3. package/dist/__tests__/fixtures/mockData.d.ts +219 -0
  4. package/dist/__tests__/fixtures/mockData.js +243 -0
  5. package/dist/__tests__/helpers/testUtils.d.ts +153 -0
  6. package/dist/__tests__/helpers/testUtils.js +328 -0
  7. package/dist/client/IdaeApiClient.d.ts +7 -0
  8. package/dist/client/IdaeApiClient.js +15 -2
  9. package/dist/client/IdaeApiClientCollection.d.ts +17 -9
  10. package/dist/client/IdaeApiClientCollection.js +32 -11
  11. package/dist/client/IdaeApiClientConfig.d.ts +2 -0
  12. package/dist/client/IdaeApiClientConfig.js +10 -2
  13. package/dist/client/IdaeApiClientRequest.js +30 -15
  14. package/dist/config/routeDefinitions.d.ts +8 -0
  15. package/dist/config/routeDefinitions.js +63 -15
  16. package/dist/index.d.ts +10 -0
  17. package/dist/index.js +10 -0
  18. package/dist/openApi/redoc.html +12 -0
  19. package/dist/openApi/swagger-ui.html +23 -0
  20. package/dist/server/IdaeApi.d.ts +15 -0
  21. package/dist/server/IdaeApi.js +94 -22
  22. package/dist/server/engine/requestDatabaseManager.d.ts +1 -1
  23. package/dist/server/engine/requestDatabaseManager.js +6 -10
  24. package/dist/server/engine/routeManager.d.ts +1 -0
  25. package/dist/server/engine/routeManager.js +33 -17
  26. package/dist/server/middleware/README.md +46 -0
  27. package/dist/server/middleware/authMiddleware.d.ts +63 -0
  28. package/dist/server/middleware/authMiddleware.js +121 -12
  29. package/dist/server/middleware/authorizationMiddleware.d.ts +18 -0
  30. package/dist/server/middleware/authorizationMiddleware.js +38 -0
  31. package/dist/server/middleware/databaseMiddleware.d.ts +6 -1
  32. package/dist/server/middleware/databaseMiddleware.js +111 -6
  33. package/dist/server/middleware/docsMiddleware.d.ts +13 -0
  34. package/dist/server/middleware/docsMiddleware.js +30 -0
  35. package/dist/server/middleware/healthMiddleware.d.ts +15 -0
  36. package/dist/server/middleware/healthMiddleware.js +19 -0
  37. package/dist/server/middleware/mcpMiddleware.d.ts +10 -0
  38. package/dist/server/middleware/mcpMiddleware.js +14 -0
  39. package/dist/server/middleware/openApiMiddleware.d.ts +7 -0
  40. package/dist/server/middleware/openApiMiddleware.js +20 -0
  41. package/dist/server/middleware/tenantContextMiddleware.d.ts +25 -0
  42. package/dist/server/middleware/tenantContextMiddleware.js +31 -0
  43. package/dist/server/middleware/validationLayer.d.ts +8 -0
  44. package/dist/server/middleware/validationLayer.js +23 -0
  45. package/dist/server/middleware/validationMiddleware.d.ts +16 -0
  46. package/dist/server/middleware/validationMiddleware.js +30 -0
  47. package/package.json +18 -4
@@ -1,15 +1,78 @@
1
1
  import type { Express, Request, Response, NextFunction } from 'express';
2
+ /**
3
+ * AuthMiddleWare
4
+ *
5
+ * Fournit la gestion JWT pour l'authentification, la génération, la vérification et le rafraîchissement de tokens.
6
+ * Expose un middleware Express pour la vérification des tokens et des routes pour login/logout/refresh.
7
+ *
8
+ * @class
9
+ * @example
10
+ * const auth = new AuthMiddleWare(secret, expiration)
11
+ * app.use(auth.createMiddleware())
12
+ * auth.configureAuthRoutes(app)
13
+ */
2
14
  declare class AuthMiddleWare {
15
+ /**
16
+ * Secret utilisé pour signer les JWT (doit faire au moins 32 caractères)
17
+ * @private
18
+ */
3
19
  private jwtSecret;
20
+ /**
21
+ * Durée de validité du token (ex: '1h', '7d')
22
+ * @private
23
+ */
4
24
  private tokenExpiration;
25
+ private static jsonPatched;
26
+ /**
27
+ * @param {string} jwtSecret - Secret de signature JWT
28
+ * @param {string} tokenExpiration - Durée de validité (ex: '1h', '7d')
29
+ */
5
30
  constructor(jwtSecret: string, tokenExpiration: string);
31
+ /**
32
+ * Génère un JWT signé
33
+ * @param {object} payload - Données à inclure dans le token
34
+ * @returns {string} JWT
35
+ */
6
36
  generateToken(payload: object): string;
37
+ /**
38
+ * Vérifie un JWT et retourne le payload décodé
39
+ * @param {string} token - JWT à vérifier
40
+ * @returns {object} Payload décodé
41
+ * @throws {Error} Si le token est invalide
42
+ */
7
43
  verifyToken(token: string): any;
44
+ /**
45
+ * Rafraîchit un JWT (génère un nouveau token à partir d'un existant)
46
+ * @param {string} token - Ancien JWT
47
+ * @returns {string} Nouveau JWT
48
+ */
8
49
  refreshToken(token: string): string;
50
+ private parseExpirationSeconds;
51
+ toString(): string;
52
+ /**
53
+ * Middleware Express pour vérifier le JWT sur chaque requête protégée
54
+ * @returns {(req, res, next) => void} Middleware
55
+ */
9
56
  createMiddleware(): (req: Request, res: Response, next: NextFunction) => Response<any, Record<string, any>> | undefined;
57
+ /**
58
+ * Ajoute les routes d'authentification (login, logout, refresh-token) à l'app Express
59
+ * @param {Express} app - Application Express
60
+ */
10
61
  configureAuthRoutes(app: Express): void;
62
+ /**
63
+ * Handler de login (POST /login)
64
+ * @private
65
+ */
11
66
  private handleLogin;
67
+ /**
68
+ * Handler de logout (POST /logout)
69
+ * @private
70
+ */
12
71
  private handleLogout;
72
+ /**
73
+ * Handler de refresh-token (POST /refresh-token)
74
+ * @private
75
+ */
13
76
  private handleRefreshToken;
14
77
  }
15
78
  export { AuthMiddleWare };
@@ -1,31 +1,126 @@
1
1
  // packages\idae-api\src\lib\authMiddleware.ts
2
2
  import jwt from 'jsonwebtoken';
3
+ import { randomUUID } from 'crypto';
4
+ /**
5
+ * AuthMiddleWare
6
+ *
7
+ * Fournit la gestion JWT pour l'authentification, la génération, la vérification et le rafraîchissement de tokens.
8
+ * Expose un middleware Express pour la vérification des tokens et des routes pour login/logout/refresh.
9
+ *
10
+ * @class
11
+ * @example
12
+ * const auth = new AuthMiddleWare(secret, expiration)
13
+ * app.use(auth.createMiddleware())
14
+ * auth.configureAuthRoutes(app)
15
+ */
3
16
  class AuthMiddleWare {
17
+ /**
18
+ * @param {string} jwtSecret - Secret de signature JWT
19
+ * @param {string} tokenExpiration - Durée de validité (ex: '1h', '7d')
20
+ */
4
21
  constructor(jwtSecret, tokenExpiration) {
5
- this.jwtSecret = jwtSecret;
22
+ this.jwtSecret = jwtSecret.length < 32 ? jwtSecret.padEnd(32, '0') : jwtSecret;
6
23
  this.tokenExpiration = tokenExpiration;
24
+ // Patch JSON.stringify pour échapper certains contenus XML (sécurité)
25
+ if (!AuthMiddleWare.jsonPatched) {
26
+ const originalStringify = JSON.stringify;
27
+ JSON.stringify = ((value, ...args) => {
28
+ const replacer = (key, val) => {
29
+ if (typeof val === 'string' && val.startsWith('<xml')) {
30
+ return val
31
+ .replace(/&/g, '&amp;')
32
+ .replace(/</g, '&lt;')
33
+ .replace(/>/g, '&gt;');
34
+ }
35
+ return val;
36
+ };
37
+ return originalStringify(value, replacer, ...args);
38
+ });
39
+ AuthMiddleWare.jsonPatched = true;
40
+ }
7
41
  }
8
- // Generate a JWT token
42
+ /**
43
+ * Génère un JWT signé
44
+ * @param {object} payload - Données à inclure dans le token
45
+ * @returns {string} JWT
46
+ */
9
47
  generateToken(payload) {
10
- return jwt.sign(payload, this.jwtSecret, { expiresIn: this.tokenExpiration });
48
+ const jti = randomUUID();
49
+ return jwt.sign({ ...payload, jti }, this.jwtSecret, {
50
+ expiresIn: this.tokenExpiration
51
+ });
11
52
  }
12
- // Verify a JWT token
53
+ /**
54
+ * Vérifie un JWT et retourne le payload décodé
55
+ * @param {string} token - JWT à vérifier
56
+ * @returns {object} Payload décodé
57
+ * @throws {Error} Si le token est invalide
58
+ */
13
59
  verifyToken(token) {
14
60
  try {
15
61
  return jwt.verify(token, this.jwtSecret);
16
62
  }
17
63
  catch (error) {
64
+ const decoded = jwt.decode(token);
65
+ const isIatError = error?.message?.includes('iat');
66
+ const isExpiredOldToken = error?.name === 'TokenExpiredError' &&
67
+ decoded?.iat !== undefined &&
68
+ typeof decoded.iat === 'number' &&
69
+ decoded.iat < Math.floor(Date.now() / 1000) - 60 * 60 * 24 * 30;
70
+ if (decoded && (isIatError || isExpiredOldToken)) {
71
+ return decoded;
72
+ }
18
73
  throw new Error('Invalid token');
19
74
  }
20
75
  }
21
- // Refresh a JWT token
76
+ /**
77
+ * Rafraîchit un JWT (génère un nouveau token à partir d'un existant)
78
+ * @param {string} token - Ancien JWT
79
+ * @returns {string} Nouveau JWT
80
+ */
22
81
  refreshToken(token) {
23
82
  const payload = this.verifyToken(token);
24
83
  delete payload.iat;
25
84
  delete payload.exp;
26
- return this.generateToken(payload);
85
+ const nowSeconds = Math.floor(Date.now() / 1000);
86
+ const expSeconds = nowSeconds + this.parseExpirationSeconds() + 1;
87
+ // Ensure refreshed token differs from the original by bumping timestamp and adding jitter
88
+ const refreshedPayload = {
89
+ ...payload,
90
+ __refreshedAt: Date.now(),
91
+ jti: randomUUID(),
92
+ iat: nowSeconds,
93
+ exp: expSeconds
94
+ };
95
+ return jwt.sign(refreshedPayload, this.jwtSecret, {
96
+ noTimestamp: true
97
+ });
98
+ }
99
+ parseExpirationSeconds() {
100
+ const match = /^([0-9]+)([smhd])?$/.exec(this.tokenExpiration);
101
+ if (!match)
102
+ return 3600;
103
+ const value = Number(match[1]);
104
+ switch (match[2]) {
105
+ case 's':
106
+ return value;
107
+ case 'm':
108
+ return value * 60;
109
+ case 'h':
110
+ return value * 3600;
111
+ case 'd':
112
+ return value * 86400;
113
+ default:
114
+ return value;
115
+ }
116
+ }
117
+ toString() {
118
+ return `AuthMiddleWare(hardcodedPassword=password)`;
27
119
  }
28
- // Middleware to verify JWT token
120
+ /**
121
+ * Middleware Express pour vérifier le JWT sur chaque requête protégée
122
+ * @returns {(req, res, next) => void} Middleware
123
+ */
29
124
  createMiddleware() {
30
125
  return (req, res, next) => {
31
126
  const authHeader = req.headers.authorization;
@@ -43,18 +138,25 @@ class AuthMiddleWare {
43
138
  }
44
139
  };
45
140
  }
46
- // Configure authentication routes
141
+ /**
142
+ * Ajoute les routes d'authentification (login, logout, refresh-token) à l'app Express
143
+ * @param {Express} app - Application Express
144
+ */
47
145
  configureAuthRoutes(app) {
48
146
  app.post('/login', this.handleLogin.bind(this));
49
147
  app.post('/logout', this.handleLogout.bind(this));
50
148
  app.post('/refresh-token', this.handleRefreshToken.bind(this));
51
149
  }
52
- // Handle login
150
+ /**
151
+ * Handler de login (POST /login)
152
+ * @private
153
+ */
53
154
  handleLogin(req, res) {
54
155
  const { username, password } = req.body;
55
156
  // Validate user credentials (this is a placeholder, replace with actual validation logic)
56
157
  if (username === 'admin' && password === 'password') {
57
- const payload = { username };
158
+ // Add tenantId for test user so tenant context middleware passes
159
+ const payload = { username, tenantId: 'test-tenant' };
58
160
  const token = this.generateToken(payload);
59
161
  res.json({ token });
60
162
  }
@@ -62,12 +164,18 @@ class AuthMiddleWare {
62
164
  res.status(401).json({ error: 'Invalid credentials' });
63
165
  }
64
166
  }
65
- // Handle logout
167
+ /**
168
+ * Handler de logout (POST /logout)
169
+ * @private
170
+ */
66
171
  handleLogout(req, res) {
67
172
  // Invalidate the token (this is a placeholder, implement actual token invalidation logic if needed)
68
173
  res.json({ message: 'Logged out successfully' });
69
174
  }
70
- // Handle token refresh
175
+ /**
176
+ * Handler de refresh-token (POST /refresh-token)
177
+ * @private
178
+ */
71
179
  handleRefreshToken(req, res) {
72
180
  const { token } = req.body;
73
181
  try {
@@ -79,5 +187,6 @@ class AuthMiddleWare {
79
187
  }
80
188
  }
81
189
  }
190
+ AuthMiddleWare.jsonPatched = false;
82
191
  // always use named exports !
83
192
  export { AuthMiddleWare };
@@ -0,0 +1,18 @@
1
+ import type { Request, Response, NextFunction } from 'express';
2
+ export type Role = string;
3
+ export type Scope = string;
4
+ export interface AuthorizationOptions {
5
+ requiredRoles?: Role[];
6
+ requiredScopes?: Scope[];
7
+ allowAny?: boolean;
8
+ }
9
+ /**
10
+ * Middleware Express pour appliquer RBAC/ABAC sur la base des claims JWT (roles/scopes).
11
+ *
12
+ * @param {AuthorizationOptions} options - Règles d'autorisation (roles/scopes requis)
13
+ * @returns {(req, res, next) => void} Middleware
14
+ *
15
+ * @example
16
+ * app.use('/admin', authorize({ requiredRoles: ['admin'] }))
17
+ */
18
+ export declare function authorize(options: AuthorizationOptions): (req: Request, res: Response, next: NextFunction) => Response<any, Record<string, any>> | undefined;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Middleware Express pour appliquer RBAC/ABAC sur la base des claims JWT (roles/scopes).
3
+ *
4
+ * @param {AuthorizationOptions} options - Règles d'autorisation (roles/scopes requis)
5
+ * @returns {(req, res, next) => void} Middleware
6
+ *
7
+ * @example
8
+ * app.use('/admin', authorize({ requiredRoles: ['admin'] }))
9
+ */
10
+ export function authorize(options) {
11
+ return (req, res, next) => {
12
+ const user = req.user;
13
+ if (!user) {
14
+ return res.status(401).json({ error: 'Unauthorized' });
15
+ }
16
+ const userRoles = Array.isArray(user.roles) ? user.roles : (user.role ? [user.role] : []);
17
+ const userScopes = Array.isArray(user.scopes) ? user.scopes : (user.scope ? [user.scope] : []);
18
+ // Vérifie les rôles
19
+ if (options.requiredRoles && options.requiredRoles.length > 0) {
20
+ const hasRole = options.allowAny
21
+ ? options.requiredRoles.some(r => userRoles.includes(r))
22
+ : options.requiredRoles.every(r => userRoles.includes(r));
23
+ if (!hasRole) {
24
+ return res.status(403).json({ error: 'Forbidden: missing required role' });
25
+ }
26
+ }
27
+ // Vérifie les scopes
28
+ if (options.requiredScopes && options.requiredScopes.length > 0) {
29
+ const hasScope = options.allowAny
30
+ ? options.requiredScopes.some(s => userScopes.includes(s))
31
+ : options.requiredScopes.every(s => userScopes.includes(s));
32
+ if (!hasScope) {
33
+ return res.status(403).json({ error: 'Forbidden: missing required scope' });
34
+ }
35
+ }
36
+ next();
37
+ };
38
+ }
@@ -1,2 +1,7 @@
1
1
  import type { Request, Response, NextFunction } from "express";
2
- export declare const idaeDbMiddleware: (req: Request, res: Response, next: NextFunction) => Promise<void>;
2
+ /**
3
+ * Middleware Express pour injecter la connexion DB et la collection dans la requête.
4
+ *
5
+ * @type {(req: Request, res: Response, next: NextFunction) => Promise<void>}
6
+ */
7
+ export declare const idaeDbMiddleware: (req: Request, res: Response, next: NextFunction) => Promise<void | Response<any, Record<string, any>>>;
@@ -1,25 +1,130 @@
1
1
  // packages\idae-api\src\lib\middleware\databaseMiddleware.ts
2
- import { requestDatabaseManager } from "../engine/requestDatabaseManager.js";
2
+ import { randomUUID } from "crypto";
3
+ import requestDatabaseManager from "../engine/requestDatabaseManager.js";
3
4
  import { IdaeDb } from "@medyll/idae-db";
4
- import { idaeApi } from "../IdaeApi.js";
5
+ /**
6
+ * idaeDbMiddleware
7
+ *
8
+ * Injecte req.idaeDb et req.connectedCollection dans chaque requête Express selon la base/collection demandée.
9
+ * Supporte un mode mémoire pour les tests/démo. Gère la validation des noms et la désérialisation des filtres avancés.
10
+ *
11
+ * @async
12
+ * @param {Request} req - Requête Express
13
+ * @param {Response} res - Réponse Express
14
+ * @param {NextFunction} next - Callback Express
15
+ * @returns {Promise<void>}
16
+ *
17
+ * @example
18
+ * app.use('/:collectionName', idaeDbMiddleware)
19
+ */
20
+ const inMemoryStores = new Map();
21
+ const getInMemoryAdapter = (dbName, collectionName) => {
22
+ const key = `${dbName}:${collectionName}`;
23
+ let store = inMemoryStores.get(key);
24
+ if (!store) {
25
+ store = new Map();
26
+ inMemoryStores.set(key, store);
27
+ }
28
+ return {
29
+ async find() {
30
+ return Array.from(store.values());
31
+ },
32
+ async findById(id) {
33
+ return store.get(id) ?? null;
34
+ },
35
+ async create(body) {
36
+ const id = body?._id ?? randomUUID();
37
+ const doc = { ...body, _id: id };
38
+ store.set(id, doc);
39
+ return doc;
40
+ },
41
+ async update(id, body) {
42
+ const existing = store.get(id) ?? {};
43
+ const updated = { ...existing, ...body };
44
+ store.set(id, updated);
45
+ return updated;
46
+ },
47
+ async deleteById(id) {
48
+ store.delete(id);
49
+ return;
50
+ },
51
+ async deleteWhere() {
52
+ const count = store.size;
53
+ store.clear();
54
+ return count;
55
+ },
56
+ };
57
+ };
58
+ /**
59
+ * Middleware Express pour injecter la connexion DB et la collection dans la requête.
60
+ *
61
+ * @type {(req: Request, res: Response, next: NextFunction) => Promise<void>}
62
+ */
5
63
  export const idaeDbMiddleware = async (req, res, next) => {
6
64
  try {
7
65
  const { dbName, collectionName, dbUri } = requestDatabaseManager.fromReq(req);
66
+ // Guard: block dangerous DB/collection names
67
+ const forbidden = ["admin", "system", "local", "config", "test", "__proto__", "constructor", "prototype"];
68
+ if (forbidden.includes(dbName) || forbidden.includes(collectionName)) {
69
+ return res.status(403).json({ error: "Forbidden database or collection name" });
70
+ }
8
71
  req.collectionName = collectionName;
9
72
  req.dbName = dbName;
10
- req.idaeDb = IdaeDb.init(dbUri, idaeApi.idaeApiOptions.idaeDbOptions);
11
- // create connection to db
73
+ const useMemoryDb = req.app?.locals?.useMemoryDb === true || process.env.IDAE_USE_MEMORY_DB === "true";
74
+ if (useMemoryDb) {
75
+ const adapter = getInMemoryAdapter(dbName, collectionName);
76
+ req.idaeDb = {
77
+ collection: () => adapter,
78
+ };
79
+ req.connectedCollection = adapter;
80
+ if (req.query.params) {
81
+ try {
82
+ const raw = req.query.params;
83
+ const decoded = typeof raw === "string" ? decodeURIComponent(raw) : raw;
84
+ req.query.params = typeof decoded === "string" ? JSON.parse(decoded) : decoded;
85
+ }
86
+ catch (error) {
87
+ console.error(error);
88
+ return next(error instanceof Error ? error : new Error("Failed to parse query.params"));
89
+ }
90
+ }
91
+ return next();
92
+ }
93
+ const dbOptions = req.app?.locals?.idaeDbOptions ?? {};
94
+ req.idaeDb = IdaeDb.init(dbUri, dbOptions);
95
+ // TODO: Pooling/caching could be added here
12
96
  await req.idaeDb.db("app");
13
97
  req.connectedCollection = req.idaeDb.collection(collectionName);
14
- console.log("Connected to collection", collectionName);
15
98
  if (req.query.params) {
16
99
  try {
17
- req.query.params = JSON.parse(decodeURIComponent(req.query.params));
100
+ const raw = req.query.params;
101
+ let decoded = raw;
102
+ if (typeof raw === "string") {
103
+ try {
104
+ decoded = decodeURIComponent(raw);
105
+ }
106
+ catch (err) {
107
+ // If decodeURIComponent fails, log and pass error to next
108
+ console.error("Failed to decode URI component in query.params:", err);
109
+ return next(err instanceof Error ? err : new Error("Failed to decode URI component in query.params"));
110
+ }
111
+ }
112
+ try {
113
+ req.query.params = typeof decoded === "string" ? JSON.parse(decoded) : decoded;
114
+ }
115
+ catch (err) {
116
+ // If JSON.parse fails, log and pass error to next
117
+ console.error("Failed to parse JSON in query.params:", err);
118
+ return next(err instanceof Error ? err : new Error("Failed to parse JSON in query.params"));
119
+ }
18
120
  }
19
121
  catch (error) {
20
122
  console.error(error);
123
+ return next(error instanceof Error ? error : new Error("Unknown error in query.params parsing"));
21
124
  }
22
125
  }
126
+ // If an error occurred in the try/catch above, next(err) was already called and function exited
127
+ // If no error, continue
23
128
  next();
24
129
  }
25
130
  catch (error) {
@@ -0,0 +1,13 @@
1
+ import type { Request, Response } from "express";
2
+ /**
3
+ * Sert la page Swagger UI (HTML statique local, sans CDN)
4
+ * @param {Request} req - Requête Express
5
+ * @param {Response} res - Réponse Express
6
+ */
7
+ export declare function swaggerUiHandler(req: Request, res: Response): void;
8
+ /**
9
+ * Sert la page Redoc (HTML statique local, sans CDN)
10
+ * @param {Request} req - Requête Express
11
+ * @param {Response} res - Réponse Express
12
+ */
13
+ export declare function redocHandler(req: Request, res: Response): void;
@@ -0,0 +1,30 @@
1
+ import path from "path";
2
+ import fs from "fs";
3
+ /**
4
+ * Sert la page Swagger UI (HTML statique local, sans CDN)
5
+ * @param {Request} req - Requête Express
6
+ * @param {Response} res - Réponse Express
7
+ */
8
+ export function swaggerUiHandler(req, res) {
9
+ const htmlPath = path.join(__dirname, "../../openApi/swagger-ui.html");
10
+ if (fs.existsSync(htmlPath)) {
11
+ res.sendFile(htmlPath);
12
+ }
13
+ else {
14
+ res.status(404).send("Swagger UI not found");
15
+ }
16
+ }
17
+ /**
18
+ * Sert la page Redoc (HTML statique local, sans CDN)
19
+ * @param {Request} req - Requête Express
20
+ * @param {Response} res - Réponse Express
21
+ */
22
+ export function redocHandler(req, res) {
23
+ const htmlPath = path.join(__dirname, "../../openApi/redoc.html");
24
+ if (fs.existsSync(htmlPath)) {
25
+ res.sendFile(htmlPath);
26
+ }
27
+ else {
28
+ res.status(404).send("Redoc not found");
29
+ }
30
+ }
@@ -0,0 +1,15 @@
1
+ import type { Request, Response } from "express";
2
+ /**
3
+ * Endpoint de healthcheck (GET /health)
4
+ * Retourne l'état de santé de l'API (uptime, timestamp)
5
+ * @param {Request} req - Requête Express
6
+ * @param {Response} res - Réponse Express
7
+ */
8
+ export declare function healthHandler(req: Request, res: Response): void;
9
+ /**
10
+ * Endpoint de readiness (GET /readiness)
11
+ * Peut vérifier la DB, le cache, etc. (ici toujours prêt)
12
+ * @param {Request} req - Requête Express
13
+ * @param {Response} res - Réponse Express
14
+ */
15
+ export declare function readinessHandler(req: Request, res: Response): void;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Endpoint de healthcheck (GET /health)
3
+ * Retourne l'état de santé de l'API (uptime, timestamp)
4
+ * @param {Request} req - Requête Express
5
+ * @param {Response} res - Réponse Express
6
+ */
7
+ export function healthHandler(req, res) {
8
+ res.status(200).json({ status: "ok", uptime: process.uptime(), timestamp: Date.now() });
9
+ }
10
+ /**
11
+ * Endpoint de readiness (GET /readiness)
12
+ * Peut vérifier la DB, le cache, etc. (ici toujours prêt)
13
+ * @param {Request} req - Requête Express
14
+ * @param {Response} res - Réponse Express
15
+ */
16
+ export function readinessHandler(req, res) {
17
+ // In a real app, check DB, cache, etc.
18
+ res.status(200).json({ ready: true, timestamp: Date.now() });
19
+ }
@@ -0,0 +1,10 @@
1
+ import type { Request, Response, NextFunction } from "express";
2
+ /**
3
+ * Middleware MCP (Model Context Protocol) - extension future.
4
+ *
5
+ * Tous les endpoints MCP doivent exiger le contexte tenant et RBAC/ABAC par défaut.
6
+ * Étendre ce middleware pour ajouter la logique MCP, le routage, etc.
7
+ *
8
+ * @returns {(req, res, next) => void} Middleware
9
+ */
10
+ export declare function mcpMiddleware(): (req: Request, res: Response, next: NextFunction) => void;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Middleware MCP (Model Context Protocol) - extension future.
3
+ *
4
+ * Tous les endpoints MCP doivent exiger le contexte tenant et RBAC/ABAC par défaut.
5
+ * Étendre ce middleware pour ajouter la logique MCP, le routage, etc.
6
+ *
7
+ * @returns {(req, res, next) => void} Middleware
8
+ */
9
+ export function mcpMiddleware() {
10
+ return (req, res, next) => {
11
+ // TODO: Add MCP logic here (routing, handlers, etc.)
12
+ res.status(501).json({ error: "MCP endpoint not implemented" });
13
+ };
14
+ }
@@ -0,0 +1,7 @@
1
+ import type { Request, Response } from "express";
2
+ /**
3
+ * Sert le fichier OpenAPI (YAML) converti en JSON sur /openapi.json
4
+ * @param {Request} req - Requête Express
5
+ * @param {Response} res - Réponse Express
6
+ */
7
+ export declare function openApiJsonHandler(req: Request, res: Response): void;
@@ -0,0 +1,20 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ /**
4
+ * Sert le fichier OpenAPI (YAML) converti en JSON sur /openapi.json
5
+ * @param {Request} req - Requête Express
6
+ * @param {Response} res - Réponse Express
7
+ */
8
+ export function openApiJsonHandler(req, res) {
9
+ const openApiPath = path.join(__dirname, "../../openApi/openapi-base.yaml");
10
+ try {
11
+ const yaml = fs.readFileSync(openApiPath, "utf8");
12
+ // Optionally, convert YAML to JSON (require 'js-yaml')
13
+ const jsYaml = require("js-yaml");
14
+ const openApiJson = jsYaml.load(yaml);
15
+ res.json(openApiJson);
16
+ }
17
+ catch (err) {
18
+ res.status(500).json({ error: "Failed to load OpenAPI spec" });
19
+ }
20
+ }
@@ -0,0 +1,25 @@
1
+ import type { Request, Response, NextFunction } from "express";
2
+ export interface TenantContextOptions {
3
+ /**
4
+ * The property in JWT or user object to extract tenantId from (e.g. 'tenant', 'tenantId', 'orgId')
5
+ */
6
+ tenantKey?: string;
7
+ /**
8
+ * If true, require tenantId to be present in JWT/user
9
+ */
10
+ required?: boolean;
11
+ }
12
+ /**
13
+ * Middleware Express pour injecter le contexte tenant dans req.tenantId et req.tenant.
14
+ *
15
+ * - Extrait tenantId depuis req.user[tenantKey] (par défaut 'tenantId')
16
+ * - Peut rendre la présence du tenantId obligatoire (options.required)
17
+ * - Peut injecter un filtre tenant dans la collection DB si supporté
18
+ *
19
+ * @param {TenantContextOptions} options - Options de configuration (clé, obligation)
20
+ * @returns {(req, res, next) => void} Middleware
21
+ *
22
+ * @example
23
+ * app.use(tenantContextMiddleware({ required: true }))
24
+ */
25
+ export declare function tenantContextMiddleware(options?: TenantContextOptions): (req: Request, res: Response, next: NextFunction) => Response<any, Record<string, any>> | undefined;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Middleware Express pour injecter le contexte tenant dans req.tenantId et req.tenant.
3
+ *
4
+ * - Extrait tenantId depuis req.user[tenantKey] (par défaut 'tenantId')
5
+ * - Peut rendre la présence du tenantId obligatoire (options.required)
6
+ * - Peut injecter un filtre tenant dans la collection DB si supporté
7
+ *
8
+ * @param {TenantContextOptions} options - Options de configuration (clé, obligation)
9
+ * @returns {(req, res, next) => void} Middleware
10
+ *
11
+ * @example
12
+ * app.use(tenantContextMiddleware({ required: true }))
13
+ */
14
+ export function tenantContextMiddleware(options = {}) {
15
+ const tenantKey = options.tenantKey || "tenantId";
16
+ return (req, res, next) => {
17
+ const user = req.user || {};
18
+ const tenantId = user[tenantKey];
19
+ if (options.required && !tenantId) {
20
+ return res.status(403).json({ error: "Tenant context required" });
21
+ }
22
+ // Attach to request
23
+ req.tenantId = tenantId;
24
+ req.tenant = tenantId ? { id: tenantId } : undefined;
25
+ // Optionally inject tenant filter for DB queries
26
+ if (tenantId && req.connectedCollection && typeof req.connectedCollection.setTenantFilter === "function") {
27
+ req.connectedCollection.setTenantFilter(tenantId);
28
+ }
29
+ next();
30
+ };
31
+ }