@futdevpro/nts-dynamo 1.15.82 → 1.15.83

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.
@@ -145,6 +145,32 @@ export abstract class DyNTS_Errors_Controller<
145
145
  return protectedNames.includes(endpointName) ? [cfg.authPreProcess] : [];
146
146
  }
147
147
 
148
+ /**
149
+ * FR-263 (2026-06-26) — LAZY, REQUEST-IDŐBEN kiértékelt guard-wrapper.
150
+ *
151
+ * GYÖKÉR-OK: a `setupEndpoints()` a controller ELSŐ konstrukciójakor fut, ami a built-in
152
+ * error-controllernél a host-app `configure()`-ja ELŐTT történhet (a framework korán példányosítja
153
+ * a singletont). Ilyenkor a `getPreProcessesFor()` még az ÜRES configot olvasta → a guard SOHA nem
154
+ * kötődött → az admin error-read endpoint-ok (`get-range`, `get-paged`, …) token nélkül **200**-at
155
+ * adtak vissza, kiszivárogtatva a belső error-logot (info-disclosure). A 39 másik (controller-szinten
156
+ * HARDCODE-olt) guard azért működött, mert nem függ a `configure()` időzítésétől.
157
+ *
158
+ * FIX: a `preProcesses` egy wrapper, ami a configot KÉRÉS-IDŐBEN olvassa (`getPreProcessesFor`), így
159
+ * a `configure()` SORREND-FÜGGETLEN — bármikor hívható az első kérés előtt. A 401-et a VALÓDI guard
160
+ * küldi; `res.headersSent`-re leállunk (nincs dupla-send), és nem-védett endpointnál no-op (átenged).
161
+ */
162
+ protected lazyPreProcessesFor(endpointName: string): ((req: Request, res: Response) => Promise<void>)[] {
163
+ return [
164
+ async (req: Request, res: Response): Promise<void> => {
165
+ const guards: ((req: Request, res: Response) => Promise<void>)[] = this.getPreProcessesFor(endpointName);
166
+ for (const guard of guards) {
167
+ if (res.headersSent) { return; }
168
+ await guard(req, res);
169
+ }
170
+ },
171
+ ];
172
+ }
173
+
148
174
  setupEndpoints(): void {
149
175
  /* if (!this.getErrorDataService) {
150
176
  throw new DyFM_Error({
@@ -165,7 +191,7 @@ export abstract class DyNTS_Errors_Controller<
165
191
  name: 'logError',
166
192
  type: DyFM_HttpCallType.post,
167
193
  endpoint: '/error/log',
168
- preProcesses: this.getPreProcessesFor('logError'),
194
+ preProcesses: this.lazyPreProcessesFor('logError'),
169
195
  tasks: [
170
196
  async (req: Request, res: Response, issuer: string = 'unknown-issuer'): Promise<void> => {
171
197
  const error_CS: T_Error_ControlService = this.getError_ControlService({
@@ -184,7 +210,7 @@ export abstract class DyNTS_Errors_Controller<
184
210
  name: 'newError',
185
211
  type: DyFM_HttpCallType.post,
186
212
  endpoint: '/error/new',
187
- preProcesses: this.getPreProcessesFor('newError'),
213
+ preProcesses: this.lazyPreProcessesFor('newError'),
188
214
  tasks: [
189
215
  async (req: Request, res: Response, issuer: string = 'unknown-issuer'): Promise<void> => {
190
216
  const error_CS: T_Error_ControlService = this.getError_ControlService({
@@ -204,7 +230,7 @@ export abstract class DyNTS_Errors_Controller<
204
230
  name: 'markErrorDone',
205
231
  type: DyFM_HttpCallType.get,
206
232
  endpoint: '/error/mark-done/:errorId',
207
- preProcesses: this.getPreProcessesFor('markErrorDone'),
233
+ preProcesses: this.lazyPreProcessesFor('markErrorDone'),
208
234
  tasks: [
209
235
  async (req: Request, res: Response, issuer: string = 'unknown-issuer'): Promise<void> => {
210
236
  const error_CS: T_Error_ControlService = this.getError_ControlService({
@@ -224,7 +250,7 @@ export abstract class DyNTS_Errors_Controller<
224
250
  name: 'markAllErrorDone',
225
251
  type: DyFM_HttpCallType.get,
226
252
  endpoint: '/error/mark-all-done',
227
- preProcesses: this.getPreProcessesFor('markAllErrorDone'),
253
+ preProcesses: this.lazyPreProcessesFor('markAllErrorDone'),
228
254
  tasks: [
229
255
  async (req: Request, res: Response, issuer: string = 'unknown-issuer'): Promise<void> => {
230
256
  const error_CS: T_Error_ControlService = this.getError_ControlService({
@@ -244,7 +270,7 @@ export abstract class DyNTS_Errors_Controller<
244
270
  name: 'recordFixAttempt',
245
271
  type: DyFM_HttpCallType.post,
246
272
  endpoint: '/error/:errorId/fix-attempt',
247
- preProcesses: this.getPreProcessesFor('recordFixAttempt'),
273
+ preProcesses: this.lazyPreProcessesFor('recordFixAttempt'),
248
274
  tasks: [
249
275
  async (req: Request, res: Response, issuer: string = 'unknown-issuer'): Promise<void> => {
250
276
  const errorId: string = req.params.errorId;
@@ -274,7 +300,7 @@ export abstract class DyNTS_Errors_Controller<
274
300
  name: 'resolveError',
275
301
  type: DyFM_HttpCallType.post,
276
302
  endpoint: '/error/:errorId/resolve',
277
- preProcesses: this.getPreProcessesFor('resolveError'),
303
+ preProcesses: this.lazyPreProcessesFor('resolveError'),
278
304
  tasks: [
279
305
  async (req: Request, res: Response, issuer: string = 'unknown-issuer'): Promise<void> => {
280
306
  const errorId: string = req.params.errorId;
@@ -321,7 +347,7 @@ export abstract class DyNTS_Errors_Controller<
321
347
  name: 'getErrors',
322
348
  type: DyFM_HttpCallType.get,
323
349
  endpoint: '/error/get-range/:range',
324
- preProcesses: this.getPreProcessesFor('getErrors'),
350
+ preProcesses: this.lazyPreProcessesFor('getErrors'),
325
351
  tasks: [
326
352
  async (req: Request, res: Response, issuer: string = 'unknown-issuer') : Promise<void> => {
327
353
  const error_CS: T_Error_ControlService = this.getError_ControlService({
@@ -342,7 +368,7 @@ export abstract class DyNTS_Errors_Controller<
342
368
  name: 'getErrorsPaged',
343
369
  type: DyFM_HttpCallType.get,
344
370
  endpoint: '/error/get-paged/:range/:pageSize/:pageIndex',
345
- preProcesses: this.getPreProcessesFor('getErrorsPaged'),
371
+ preProcesses: this.lazyPreProcessesFor('getErrorsPaged'),
346
372
  tasks: [
347
373
  async (req: Request, res: Response, issuer: string = 'unknown-issuer') : Promise<void> => {
348
374
  const error_CS: T_Error_ControlService = this.getError_ControlService({
@@ -374,7 +400,7 @@ export abstract class DyNTS_Errors_Controller<
374
400
  name: 'getErrorsByCategoryPaged',
375
401
  type: DyFM_HttpCallType.get,
376
402
  endpoint: '/error/get-by-category/:category/:range/:pageSize/:pageIndex',
377
- preProcesses: this.getPreProcessesFor('getErrorsByCategoryPaged'),
403
+ preProcesses: this.lazyPreProcessesFor('getErrorsByCategoryPaged'),
378
404
  tasks: [
379
405
  async (req: Request, res: Response, issuer: string = 'unknown-issuer'): Promise<void> => {
380
406
  const error_CS: T_Error_ControlService = this.getError_ControlService({ issuer });
@@ -403,7 +429,7 @@ export abstract class DyNTS_Errors_Controller<
403
429
  name: 'getErrorsByStatusPaged',
404
430
  type: DyFM_HttpCallType.get,
405
431
  endpoint: '/error/get-by-status/:status/:range/:pageSize/:pageIndex',
406
- preProcesses: this.getPreProcessesFor('getErrorsByStatusPaged'),
432
+ preProcesses: this.lazyPreProcessesFor('getErrorsByStatusPaged'),
407
433
  tasks: [
408
434
  async (req: Request, res: Response, issuer: string = 'unknown-issuer'): Promise<void> => {
409
435
  const error_CS: T_Error_ControlService = this.getError_ControlService({ issuer });
@@ -432,7 +458,7 @@ export abstract class DyNTS_Errors_Controller<
432
458
  name: 'getLastErrors',
433
459
  type: DyFM_HttpCallType.get,
434
460
  endpoint: '/error/get-last-paged/:range/:pageSize/:pageIndex',
435
- preProcesses: this.getPreProcessesFor('getLastErrors'),
461
+ preProcesses: this.lazyPreProcessesFor('getLastErrors'),
436
462
  tasks: [
437
463
  async (req: Request, res: Response, issuer: string = 'unknown-issuer') : Promise<void> => {
438
464
  const error_CS: T_Error_ControlService = this.getError_ControlService({
@@ -464,7 +490,7 @@ export abstract class DyNTS_Errors_Controller<
464
490
  name: 'searchErrors',
465
491
  type: DyFM_HttpCallType.get,
466
492
  endpoint: '/error/search',
467
- preProcesses: this.getPreProcessesFor('searchErrors'),
493
+ preProcesses: this.lazyPreProcessesFor('searchErrors'),
468
494
  tasks: [
469
495
  async (req: Request, res: Response, issuer: string = 'unknown-issuer') : Promise<void> => {
470
496
  const error_CS: T_Error_ControlService = this.getError_ControlService({
@@ -172,62 +172,69 @@ describe('| DyNTS_ServerStatus_Controller', () => {
172
172
  DyNTS_ServerStatus_Controller._resetAuthConfigForTesting();
173
173
  });
174
174
 
175
- const preCount = (controller: any, name: string): number => {
176
- const ep: any = controller.endpoints.find((e: any) => e.name === name);
177
- return (ep?.preProcesses ?? []).length;
175
+ // FR-263 a guard request-idoben fut (lazy wrapper). "Gated-e egy endpoint" = a wrapper
176
+ // meghivasa lefuttatja-e a configured authPreProcess-t. A counter-t a teszt-spy lepteti.
177
+ let guardCalls: number = 0;
178
+ const spyAuth = async (): Promise<void> => { guardCalls++; };
179
+ const runGuard = async (c: any, name: string): Promise<void> => {
180
+ const ep: any = c.endpoints.find((e: any) => e.name === name);
181
+ await ep?.preProcesses?.[0]?.({} as any, { headersSent: false } as any);
178
182
  };
183
+ beforeEach((): void => { guardCalls = 0; });
179
184
 
180
- it('| default: NO preProcesses on any endpoint (backwards compatible)', (): void => {
185
+ it('| default (no config): a wrapper jelen van, de egy keres NEM hiv guardot (nyitva marad)', async (): Promise<void> => {
181
186
  controller.setupEndpoints();
182
187
  for (const ep of controller.endpoints) {
183
- const pre: any = (ep as any).preProcesses;
184
- expect(pre === undefined || pre.length === 0).toBe(true);
188
+ await (ep as any).preProcesses?.[0]?.({} as any, { headersSent: false } as any); // no-op, nem dob
185
189
  }
190
+ expect(guardCalls).toBe(0);
186
191
  });
187
192
 
188
- it('| configure({ authPreProcess }) → csak getErrorStatistics gated, a status-vegpontok NEM', (): void => {
189
- const fakeAuth = async (): Promise<void> => { /* noop */ };
190
- DyNTS_ServerStatus_Controller.configure({ authPreProcess: fakeAuth });
193
+ it('| configure({ authPreProcess }) → CSAK getErrorStatistics-en fut a guard, a status-vegpontokon NEM', async (): Promise<void> => {
194
+ DyNTS_ServerStatus_Controller.configure({ authPreProcess: spyAuth });
191
195
  const c: any = new (TestServerStatusController as any)();
192
196
  c.setupEndpoints();
193
-
194
- // admin-adat → gated
195
- expect(preCount(c, 'getErrorStatistics')).toBe(1);
196
- // status-vegpontok (a kliens-indikator hivja, /health-tel adat-ekvivalens) → SOHA nem gated
197
- expect(preCount(c, 'getServerStatus')).toBe(0);
198
- expect(preCount(c, 'getServerHealth')).toBe(0);
199
- expect(preCount(c, 'getServerReadiness')).toBe(0);
200
- expect(preCount(c, 'getServerStatusForClient')).toBe(0);
197
+ await runGuard(c, 'getErrorStatistics');
198
+ expect(guardCalls).withContext('admin-adat → gated').toBe(1);
199
+ guardCalls = 0;
200
+ for (const name of ['getServerStatus', 'getServerHealth', 'getServerReadiness', 'getServerStatusForClient']) {
201
+ await runGuard(c, name);
202
+ }
203
+ expect(guardCalls).withContext('status-probak SOHA nem gated').toBe(0);
201
204
  });
202
205
 
203
- it('| a status-vegpontokat (getServerStatus is) explicit protectedEndpoints-lista SEM gate-eli', (): void => {
204
- const fakeAuth = async (): Promise<void> => { /* noop */ };
206
+ it('| a status-vegpontokat explicit protectedEndpoints-lista SEM gate-eli (PUBLIC_PROBE kizart)', async (): Promise<void> => {
205
207
  DyNTS_ServerStatus_Controller.configure({
206
- authPreProcess: fakeAuth,
208
+ authPreProcess: spyAuth,
207
209
  protectedEndpoints: [ 'getServerHealth', 'getServerReadiness', 'getServerStatusForClient', 'getServerStatus', 'getErrorStatistics' ],
208
210
  });
209
211
  const c: any = new (TestServerStatusController as any)();
210
212
  c.setupEndpoints();
211
-
212
- expect(preCount(c, 'getServerStatus')).toBe(0);
213
- expect(preCount(c, 'getServerHealth')).toBe(0);
214
- expect(preCount(c, 'getServerReadiness')).toBe(0);
215
- expect(preCount(c, 'getServerStatusForClient')).toBe(0);
216
- // a valodi admin-adat viszont gated
217
- expect(preCount(c, 'getErrorStatistics')).toBe(1);
213
+ for (const name of ['getServerStatus', 'getServerHealth', 'getServerReadiness', 'getServerStatusForClient']) {
214
+ await runGuard(c, name);
215
+ }
216
+ expect(guardCalls).withContext('status-probak kizartak meg explicit listan is').toBe(0);
217
+ await runGuard(c, 'getErrorStatistics');
218
+ expect(guardCalls).withContext('getErrorStatistics gated').toBe(1);
218
219
  });
219
220
 
220
- it('| protectedEndpoints subset → csak a megadott (nem-proba) neveken aktiv', (): void => {
221
- const fakeAuth = async (): Promise<void> => { /* noop */ };
222
- DyNTS_ServerStatus_Controller.configure({
223
- authPreProcess: fakeAuth,
224
- protectedEndpoints: [ 'getErrorStatistics' ],
225
- });
221
+ it('| protectedEndpoints subset → getErrorStatistics gated, getServerStatus NEM', async (): Promise<void> => {
222
+ DyNTS_ServerStatus_Controller.configure({ authPreProcess: spyAuth, protectedEndpoints: [ 'getErrorStatistics' ] });
226
223
  const c: any = new (TestServerStatusController as any)();
227
224
  c.setupEndpoints();
225
+ await runGuard(c, 'getErrorStatistics');
226
+ expect(guardCalls).toBe(1);
227
+ guardCalls = 0;
228
+ await runGuard(c, 'getServerStatus');
229
+ expect(guardCalls).toBe(0);
230
+ });
228
231
 
229
- expect(preCount(c, 'getErrorStatistics')).toBe(1);
230
- expect(preCount(c, 'getServerStatus')).toBe(0);
232
+ it('| FR-263 ORDER-INDEPENDENCE: setupEndpoints() ELOTT epitett endpoint is enforce-olja a guardot', async (): Promise<void> => {
233
+ const c: any = new (TestServerStatusController as any)();
234
+ c.setupEndpoints(); // config NELKUL (korai framework-konstrukcio szimulacioja)
235
+ DyNTS_ServerStatus_Controller.configure({ authPreProcess: spyAuth }); // configure UTANA
236
+ await runGuard(c, 'getErrorStatistics');
237
+ expect(guardCalls).withContext('guard runs despite configure() AFTER setupEndpoints').toBe(1);
231
238
  });
232
239
 
233
240
  it('| getAuthConfig() returns the active config', (): void => {
@@ -151,6 +151,25 @@ export abstract class DyNTS_ServerStatus_Controller<
151
151
  return protectedNames.includes(endpointName) ? [cfg.authPreProcess] : [];
152
152
  }
153
153
 
154
+ /**
155
+ * FR-263 (2026-06-26) — LAZY, REQUEST-IDŐBEN kiértékelt guard-wrapper (lásd a DyNTS_Errors_Controller
156
+ * azonos fixét). A `setupEndpoints()` a host-app `configure()`-ja ELŐTT futhat (korai singleton-
157
+ * konstrukció) → a guard ürès configgal kötődött → a `getServerStatus`/`getErrorStatistics` admin-adat
158
+ * token nélkül 200-at adott (info-disclosure). A wrapper a configot KÉRÉS-IDŐBEN olvassa → sorrend-
159
+ * független. A 401-et a valódi guard küldi; `headersSent`-re leállunk; nem-védett endpointnál no-op.
160
+ */
161
+ protected lazyPreProcessesFor(endpointName: string): ((req: Request, res: Response) => Promise<void>)[] {
162
+ return [
163
+ async (req: Request, res: Response): Promise<void> => {
164
+ const guards: ((req: Request, res: Response) => Promise<void>)[] = this.getPreProcessesFor(endpointName);
165
+ for (const guard of guards) {
166
+ if (res.headersSent) { return; }
167
+ await guard(req, res);
168
+ }
169
+ },
170
+ ];
171
+ }
172
+
154
173
  setupEndpoints(): void {
155
174
  /* if (!this.getServerService) {
156
175
  throw new DyFM_Error({
@@ -173,7 +192,7 @@ export abstract class DyNTS_ServerStatus_Controller<
173
192
  name: 'getServerStatus',
174
193
  type: DyFM_HttpCallType.get,
175
194
  endpoint: '/status',
176
- preProcesses: this.getPreProcessesFor('getServerStatus'),
195
+ preProcesses: this.lazyPreProcessesFor('getServerStatus'),
177
196
  tasks: [
178
197
  async (req: Request, res: Response, issuer: string): Promise<void> => {
179
198
  res.send(
@@ -187,7 +206,7 @@ export abstract class DyNTS_ServerStatus_Controller<
187
206
  name: 'getServerHealth',
188
207
  type: DyFM_HttpCallType.get,
189
208
  endpoint: '/health',
190
- preProcesses: this.getPreProcessesFor('getServerHealth'),
209
+ preProcesses: this.lazyPreProcessesFor('getServerHealth'),
191
210
  tasks: [
192
211
  async (req: Request, res: Response, issuer: string): Promise<void> => {
193
212
  res.send(
@@ -203,7 +222,7 @@ export abstract class DyNTS_ServerStatus_Controller<
203
222
  name: 'getServerReadiness',
204
223
  type: DyFM_HttpCallType.get,
205
224
  endpoint: '/readiness',
206
- preProcesses: this.getPreProcessesFor('getServerReadiness'),
225
+ preProcesses: this.lazyPreProcessesFor('getServerReadiness'),
207
226
  tasks: [
208
227
  async (req: Request, res: Response, issuer: string): Promise<void> => {
209
228
  const readiness: DyNTS_DbReadiness = await this.server_CS.checkDbReadiness();
@@ -217,7 +236,7 @@ export abstract class DyNTS_ServerStatus_Controller<
217
236
  name: 'getServerStatusForClient',
218
237
  type: DyFM_HttpCallType.get,
219
238
  endpoint: '/status/:version',
220
- preProcesses: this.getPreProcessesFor('getServerStatusForClient'),
239
+ preProcesses: this.lazyPreProcessesFor('getServerStatusForClient'),
221
240
  tasks: [
222
241
  async (req: Request, res: Response, issuer: string): Promise<void> => {
223
242
 
@@ -232,7 +251,7 @@ export abstract class DyNTS_ServerStatus_Controller<
232
251
  name: 'getErrorStatistics',
233
252
  type: DyFM_HttpCallType.get,
234
253
  endpoint: '/statistics/error/:range',
235
- preProcesses: this.getPreProcessesFor('getErrorStatistics'),
254
+ preProcesses: this.lazyPreProcessesFor('getErrorStatistics'),
236
255
  tasks: [
237
256
  async (req: Request, res: Response, issuer: string) : Promise<void> => {
238
257