@futdevpro/nts-dynamo 1.15.131 → 1.15.134
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/.dynamo/logs/cicd-pipeline/output.log +1713 -1595
- package/.dynamo/logs/cicd-pipeline/status.json +28 -28
- package/__documentations/2026-08-08-fr292-app-version-header.md +38 -0
- package/build/_collections/app-version-header.util.d.ts +42 -0
- package/build/_collections/app-version-header.util.d.ts.map +1 -0
- package/build/_collections/app-version-header.util.js +67 -0
- package/build/_collections/app-version-header.util.js.map +1 -0
- package/build/_models/interfaces/app-version-header-settings.interface.d.ts +17 -0
- package/build/_models/interfaces/app-version-header-settings.interface.d.ts.map +1 -0
- package/build/_models/interfaces/app-version-header-settings.interface.js +3 -0
- package/build/_models/interfaces/app-version-header-settings.interface.js.map +1 -0
- package/build/_models/interfaces/global-settings.interface.d.ts +7 -0
- package/build/_models/interfaces/global-settings.interface.d.ts.map +1 -1
- package/build/_modules/usage/usage.controller.d.ts.map +1 -1
- package/build/_modules/usage/usage.controller.js +14 -4
- package/build/_modules/usage/usage.controller.js.map +1 -1
- package/build/_services/base/data.service.d.ts.map +1 -1
- package/build/_services/base/data.service.js +4 -1
- package/build/_services/base/data.service.js.map +1 -1
- package/build/_services/server/app.server.d.ts +9 -0
- package/build/_services/server/app.server.d.ts.map +1 -1
- package/build/_services/server/app.server.js +21 -0
- package/build/_services/server/app.server.js.map +1 -1
- package/build/index.d.ts +2 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +2 -0
- package/build/index.js.map +1 -1
- package/package.json +2 -2
- package/src/_collections/app-version-header.util.spec.ts +108 -0
- package/src/_collections/app-version-header.util.ts +74 -0
- package/src/_models/interfaces/app-version-header-settings.interface.ts +17 -0
- package/src/_models/interfaces/global-settings.interface.ts +8 -0
- package/src/_modules/usage/usage.controller.spec.ts +25 -0
- package/src/_modules/usage/usage.controller.ts +37 -23
- package/src/_services/base/data.service.spec.ts +55 -0
- package/src/_services/base/data.service.ts +4 -1
- package/src/_services/server/app.server.ts +26 -0
- package/src/index.ts +2 -0
package/build/index.js
CHANGED
|
@@ -48,9 +48,11 @@ tslib_1.__exportStar(require("./_models/interfaces/signed-url-verify-result.inte
|
|
|
48
48
|
tslib_1.__exportStar(require("./_models/interfaces/static-client-settings.interface"), exports);
|
|
49
49
|
tslib_1.__exportStar(require("./_models/interfaces/cors-settings.interface"), exports);
|
|
50
50
|
tslib_1.__exportStar(require("./_models/interfaces/security-headers-settings.interface"), exports);
|
|
51
|
+
tslib_1.__exportStar(require("./_models/interfaces/app-version-header-settings.interface"), exports);
|
|
51
52
|
tslib_1.__exportStar(require("./_models/interfaces/db-query-options.interface"), exports);
|
|
52
53
|
tslib_1.__exportStar(require("./_models/interfaces/migration-entry.interface"), exports);
|
|
53
54
|
tslib_1.__exportStar(require("./_collections/security-headers.util"), exports);
|
|
55
|
+
tslib_1.__exportStar(require("./_collections/app-version-header.util"), exports);
|
|
54
56
|
tslib_1.__exportStar(require("./_collections/signed-url.util"), exports);
|
|
55
57
|
tslib_1.__exportStar(require("./_collections/client-safe-error.util"), exports);
|
|
56
58
|
tslib_1.__exportStar(require("./_collections/load-shed.util"), exports);
|
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;;;;;;;;;;;IAsBI;;;AAEJ,cAAc;AACd,sEAA4C;AAC5C,wFAA8D;AAC9D,8FAAoE;AACpE,sFAA4D;AAC5D,mFAAyD;AACzD,uFAA6D;AAC7D,+EAAqD;AAGrD,QAAQ;AACR,wEAA8C;AAC9C,8EAAoD;AACpD,8EAAoD;AACpD,uEAA6C;AAG7C,6CAA6C;AAE7C,mEAAmE;AACnE,gGAAsE;AACtE,8FAAoE;AACpE,iGAAuE;AACvE,yFAA+D;AAC/D,iGAAuE;AACvE,kGAAwE;AACxE,gGAAsE;AACtE,uFAA6D;AAC7D,mGAAyE;AACzE,0FAAgE;AAChE,yFAA+D;AAC/D,+EAAqD;AACrD,yEAA+C;AAC/C,gFAAsD;AACtD,wEAA8C;AAC9C,mFAAyD;AACzD,kFAAwD;AACxD,4EAAkD;AAClD,+EAAqD;AAErD,wBAAwB;AACxB,iGAAuE;AACvE,yGAA8E;AAC9E,4FAAkE;AAClE,qGAA2E;AAC3E,iGAAuE;AACvE,+FAAqE;AACrE,gGAAsE;AAEtE,eAAe;AACf,yEAA+C;AAE/C,qBAAqB;AACrB,4FAAkE;AAGlE,WAAW;AACX,gBAAgB;AAChB,uEAA6C;AAC7C,wEAA8C;AAE9C,yEAA+C;AAC/C,6FAAmE;AACnE,0EAAgD;AAChD,gFAAsD;AAEtD,sFAA4D;AAC5D,oFAA0D;AAE1D,gBAAgB;AAChB,4EAAkD;AAClD,gFAAsD;AACtD,wEAA8C;AAC9C,sEAA4C;AAC5C,kFAAwD;AACxD,6EAAmD;AAEnD,kBAAkB;AAClB,wEAA8C;AAE9C,iBAAiB;AACjB,+EAAqD;AACrD,mFAAyD;AAEzD,kBAAkB;AAClB,4EAAkD"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;;;;;;;;;;;IAsBI;;;AAEJ,cAAc;AACd,sEAA4C;AAC5C,wFAA8D;AAC9D,8FAAoE;AACpE,sFAA4D;AAC5D,mFAAyD;AACzD,uFAA6D;AAC7D,+EAAqD;AAGrD,QAAQ;AACR,wEAA8C;AAC9C,8EAAoD;AACpD,8EAAoD;AACpD,uEAA6C;AAG7C,6CAA6C;AAE7C,mEAAmE;AACnE,gGAAsE;AACtE,8FAAoE;AACpE,iGAAuE;AACvE,yFAA+D;AAC/D,iGAAuE;AACvE,kGAAwE;AACxE,gGAAsE;AACtE,uFAA6D;AAC7D,mGAAyE;AACzE,qGAA2E;AAC3E,0FAAgE;AAChE,yFAA+D;AAC/D,+EAAqD;AACrD,iFAAuD;AACvD,yEAA+C;AAC/C,gFAAsD;AACtD,wEAA8C;AAC9C,mFAAyD;AACzD,kFAAwD;AACxD,4EAAkD;AAClD,+EAAqD;AAErD,wBAAwB;AACxB,iGAAuE;AACvE,yGAA8E;AAC9E,4FAAkE;AAClE,qGAA2E;AAC3E,iGAAuE;AACvE,+FAAqE;AACrE,gGAAsE;AAEtE,eAAe;AACf,yEAA+C;AAE/C,qBAAqB;AACrB,4FAAkE;AAGlE,WAAW;AACX,gBAAgB;AAChB,uEAA6C;AAC7C,wEAA8C;AAE9C,yEAA+C;AAC/C,6FAAmE;AACnE,0EAAgD;AAChD,gFAAsD;AAEtD,sFAA4D;AAC5D,oFAA0D;AAE1D,gBAAgB;AAChB,4EAAkD;AAClD,gFAAsD;AACtD,wEAA8C;AAC9C,sEAA4C;AAC5C,kFAAwD;AACxD,6EAAmD;AAEnD,kBAAkB;AAClB,wEAA8C;AAE9C,iBAAiB;AACjB,+EAAqD;AACrD,mFAAyD;AAEzD,kBAAkB;AAClB,4EAAkD"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@futdevpro/nts-dynamo",
|
|
3
|
-
"version": "01.15.
|
|
3
|
+
"version": "01.15.134",
|
|
4
4
|
"description": "Dynamic NodeTS (NodeJS-Typescript), MongoDB Backend System Framework by Future Development Program Ltd.",
|
|
5
5
|
"DyBu_settings": {
|
|
6
6
|
"packageType": "server-package",
|
|
@@ -378,7 +378,7 @@
|
|
|
378
378
|
"@anthropic-ai/sdk": "~0.39.0",
|
|
379
379
|
"@modelcontextprotocol/sdk": "~1.29.0",
|
|
380
380
|
"better-sqlite3": "~12.4.1",
|
|
381
|
-
"@futdevpro/fsm-dynamo": "1.16.
|
|
381
|
+
"@futdevpro/fsm-dynamo": "1.16.37",
|
|
382
382
|
"@types/express": "4.17.21",
|
|
383
383
|
"@types/geoip-lite": "~1.4.1",
|
|
384
384
|
"@types/node": "~24.1.0",
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DYNTS_APP_VERSION_HEADER,
|
|
3
|
+
DyNTS_createAppVersionHeaderMiddleware,
|
|
4
|
+
DyNTS_resolveExposeHeaders,
|
|
5
|
+
} from './app-version-header.util';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* FR-292 — a deployolt app-verzió minden válaszon.
|
|
9
|
+
*
|
|
10
|
+
* A garanciák, amiket ez a spec ŐRIZ:
|
|
11
|
+
* - ISMERETLEN verziónál NINCS header (a `unknown-version` hamis „új verzió"-riasztást adna),
|
|
12
|
+
* - a verzió a hívás pillanatában olvasódik (az app-params KÉSŐBB tölti be — egy induláskor
|
|
13
|
+
* befagyasztott érték örökre `unknown` maradna),
|
|
14
|
+
* - a CORS expose-lista BŐVÜL, nem felülíródik (különben más headerek olvashatatlanná válnának),
|
|
15
|
+
* - a bővítés IDEMPOTENS (kétszeri felszerelés nem duplikál).
|
|
16
|
+
*/
|
|
17
|
+
describe('| FR-292 app-version response header', () => {
|
|
18
|
+
|
|
19
|
+
/** Minimal Express-válasz stub — csak a header-tárolás kell hozzá. */
|
|
20
|
+
function makeRes(): { headers: Record<string, string>; setHeader: Function; getHeader: Function } {
|
|
21
|
+
const headers: Record<string, string> = {};
|
|
22
|
+
return {
|
|
23
|
+
headers: headers,
|
|
24
|
+
setHeader: (name: string, value: string): void => { headers[name] = value; },
|
|
25
|
+
getHeader: (name: string): string | undefined => headers[name],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
describe('| DyNTS_resolveExposeHeaders', () => {
|
|
30
|
+
|
|
31
|
+
it('| ures listabol a header neve lesz', () => {
|
|
32
|
+
expect(DyNTS_resolveExposeHeaders(undefined, 'X-App-Version')).toBe('X-App-Version');
|
|
33
|
+
expect(DyNTS_resolveExposeHeaders('', 'X-App-Version')).toBe('X-App-Version');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
it('| a MEGLEVO listat megtartja es hozzafuz (nem felulir)', () => {
|
|
38
|
+
expect(DyNTS_resolveExposeHeaders('Content-Length, Content-Type', 'X-App-Version'))
|
|
39
|
+
.toBe('Content-Length, Content-Type, X-App-Version');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
it('| idempotens — ha mar benne van, nem duplikal (kis-nagybetu fuggetlenul)', () => {
|
|
44
|
+
expect(DyNTS_resolveExposeHeaders('Content-Type, X-App-Version', 'X-App-Version'))
|
|
45
|
+
.toBe('Content-Type, X-App-Version');
|
|
46
|
+
expect(DyNTS_resolveExposeHeaders('content-type, x-app-version', 'X-App-Version'))
|
|
47
|
+
.toBe('content-type, x-app-version');
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
describe('| DyNTS_createAppVersionHeaderMiddleware', () => {
|
|
53
|
+
|
|
54
|
+
it('| kiirja a verziot ES exposeolja a CORS-nak', () => {
|
|
55
|
+
const res = makeRes();
|
|
56
|
+
let nextCalled: boolean = false;
|
|
57
|
+
DyNTS_createAppVersionHeaderMiddleware((): string => '01.15.460')(
|
|
58
|
+
{} as never, res as never, ((): void => { nextCalled = true; }) as never,
|
|
59
|
+
);
|
|
60
|
+
expect(res.headers[DYNTS_APP_VERSION_HEADER]).toBe('01.15.460');
|
|
61
|
+
expect(res.headers['Access-Control-Expose-Headers']).toContain(DYNTS_APP_VERSION_HEADER);
|
|
62
|
+
expect(nextCalled).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
it('| ISMERETLEN verzional nem ir headert — de a lancot TOVABBENGEDI', () => {
|
|
67
|
+
for (const version of [undefined, '', 'unknown-version']) {
|
|
68
|
+
const res = makeRes();
|
|
69
|
+
let nextCalled: boolean = false;
|
|
70
|
+
DyNTS_createAppVersionHeaderMiddleware((): string | undefined => version)(
|
|
71
|
+
{} as never, res as never, ((): void => { nextCalled = true; }) as never,
|
|
72
|
+
);
|
|
73
|
+
expect(res.headers[DYNTS_APP_VERSION_HEADER]).withContext(String(version)).toBeUndefined();
|
|
74
|
+
expect(nextCalled).withContext(String(version)).toBe(true);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* A verziót a HÍVÁSKOR kell olvasni: a `systemVersion` az app-params betöltésekor áll be,
|
|
81
|
+
* ami a middleware felszerelése UTÁN történik. Ha a middleware befagyasztaná az értéket,
|
|
82
|
+
* a fejléc örökre hiányozna — pont a friss deployon.
|
|
83
|
+
*/
|
|
84
|
+
it('| a verziot HIVASKOR olvassa, nem felszereleskor', () => {
|
|
85
|
+
let current: string = 'unknown-version';
|
|
86
|
+
const middleware = DyNTS_createAppVersionHeaderMiddleware((): string => current);
|
|
87
|
+
|
|
88
|
+
const firstRes = makeRes();
|
|
89
|
+
middleware({} as never, firstRes as never, ((): void => undefined) as never);
|
|
90
|
+
expect(firstRes.headers[DYNTS_APP_VERSION_HEADER]).toBeUndefined();
|
|
91
|
+
|
|
92
|
+
current = '01.15.461'; // app-params betoltodott
|
|
93
|
+
const secondRes = makeRes();
|
|
94
|
+
middleware({} as never, secondRes as never, ((): void => undefined) as never);
|
|
95
|
+
expect(secondRes.headers[DYNTS_APP_VERSION_HEADER]).toBe('01.15.461');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
it('| egyedi header-nev is hasznalhato', () => {
|
|
100
|
+
const res = makeRes();
|
|
101
|
+
DyNTS_createAppVersionHeaderMiddleware((): string => '1.2.3', 'X-Custom-Version')(
|
|
102
|
+
{} as never, res as never, ((): void => undefined) as never,
|
|
103
|
+
);
|
|
104
|
+
expect(res.headers['X-Custom-Version']).toBe('1.2.3');
|
|
105
|
+
expect(res.headers['Access-Control-Expose-Headers']).toBe('X-Custom-Version');
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import * as Express from 'express';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* FR-292 (2026-08-08) — a DEPLOYOLT app-verzió kiírása MINDEN válasz headerébe.
|
|
5
|
+
*
|
|
6
|
+
* **Miért kell:** a böngészőben nyitva hagyott SPA a memóriájában tartja a bundle-t, és a
|
|
7
|
+
* deploy után is a RÉGI kódot futtatja, amíg a felhasználó újra nem tölt. Ez nem elméleti:
|
|
8
|
+
* egy órákig nyitva hagyott dashboard-fülön hiányzó gombokat / régi viselkedést mutat, és
|
|
9
|
+
* a felhasználó (jogosan) azt hiszi, hogy a funkció nincs kész.
|
|
10
|
+
*
|
|
11
|
+
* Az itteni megoldás **nem polling**: a szerver minden válaszra ráteszi a saját deployolt
|
|
12
|
+
* app-verzióját, a kliens pedig a MÁR MEGLÉVŐ hívásaiból olvassa ki (ngx oldalon a
|
|
13
|
+
* `DyNX_AppVersion_Interceptor`). Nulla extra kérés, nulla extra terhelés — a jelzés
|
|
14
|
+
* „ingyen" utazik azon, ami úgyis megy.
|
|
15
|
+
*
|
|
16
|
+
* **CORS:** cross-origin kliens csak akkor OLVASHATJA a headert, ha az szerepel az
|
|
17
|
+
* `Access-Control-Expose-Headers`-ben. Ezért a middleware a meglévő értékhez HOZZÁFŰZ
|
|
18
|
+
* (nem felülír) — ezért kell a CORS-mount UTÁN felszerelni.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/** A deployolt app-verziót hordozó response-header neve (a kliens ezt olvassa). */
|
|
22
|
+
export const DYNTS_APP_VERSION_HEADER: string = 'X-App-Version';
|
|
23
|
+
|
|
24
|
+
/** Az `Access-Control-Expose-Headers` header neve — hogy ne szórjuk el string-literálként. */
|
|
25
|
+
const EXPOSE_HEADERS_HEADER: string = 'Access-Control-Expose-Headers';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Kiszámolja az `Access-Control-Expose-Headers` ÚJ értékét úgy, hogy a meglévő listát
|
|
29
|
+
* megtartja és a verzió-headert csak akkor fűzi hozzá, ha még nincs benne (idempotens).
|
|
30
|
+
*
|
|
31
|
+
* Tiszta függvény: Express nélkül unit-tesztelhető.
|
|
32
|
+
*
|
|
33
|
+
* @param currentValue a válaszon MÁR beállított érték (a CORS-middleware-től), ha van
|
|
34
|
+
* @param headerName a hozzáfűzendő header neve
|
|
35
|
+
*/
|
|
36
|
+
export function DyNTS_resolveExposeHeaders(currentValue: string | undefined, headerName: string): string {
|
|
37
|
+
if (!currentValue) {
|
|
38
|
+
return headerName;
|
|
39
|
+
}
|
|
40
|
+
const alreadyExposed: boolean = currentValue
|
|
41
|
+
.split(',')
|
|
42
|
+
.some((entry: string): boolean => entry.trim().toLowerCase() === headerName.toLowerCase());
|
|
43
|
+
|
|
44
|
+
return alreadyExposed ? currentValue : `${currentValue}, ${headerName}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Express-middleware, ami minden válaszra ráteszi a deployolt app-verziót.
|
|
49
|
+
*
|
|
50
|
+
* A verziót FÜGGVÉNYKÉNT kapja (nem értékként), mert a `DyNTS_global_settings.systemVersion`
|
|
51
|
+
* az app-params betöltésekor áll be — egy induláskor kiolvasott érték `unknown-version`
|
|
52
|
+
* maradhatna örökre.
|
|
53
|
+
*
|
|
54
|
+
* @param getAppVersion a deployolt app-verziót visszaadó getter
|
|
55
|
+
* @param headerName felülírható header-név (default: `X-App-Version`)
|
|
56
|
+
*/
|
|
57
|
+
export function DyNTS_createAppVersionHeaderMiddleware(
|
|
58
|
+
getAppVersion: () => string | undefined,
|
|
59
|
+
headerName: string = DYNTS_APP_VERSION_HEADER,
|
|
60
|
+
): Express.RequestHandler {
|
|
61
|
+
return (_req: Express.Request, res: Express.Response, next: Express.NextFunction): void => {
|
|
62
|
+
const appVersion: string | undefined = getAppVersion();
|
|
63
|
+
// Ismeretlen verziónál NEM írunk headert: egy `unknown-version` jelzés rosszabb a
|
|
64
|
+
// semminél — a kliens „új verzió van" hamis riasztást adna belőle.
|
|
65
|
+
if (appVersion && appVersion !== 'unknown-version') {
|
|
66
|
+
res.setHeader(headerName, appVersion);
|
|
67
|
+
res.setHeader(
|
|
68
|
+
EXPOSE_HEADERS_HEADER,
|
|
69
|
+
DyNTS_resolveExposeHeaders(res.getHeader(EXPOSE_HEADERS_HEADER)?.toString(), headerName),
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
next();
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FR-292 (2026-08-08) — a deployolt app-verziót hordozó response-header beállításai.
|
|
3
|
+
*
|
|
4
|
+
* A szerver minden válaszra ráteszi a saját app-verzióját, a kliens (ngx oldalon a
|
|
5
|
+
* `DyNX_AppVersion_Interceptor`) pedig ebből veszi észre, hogy a betöltött bundle elavult.
|
|
6
|
+
* Nulla extra kérés: a jelzés a már meglévő API-forgalmon utazik.
|
|
7
|
+
*/
|
|
8
|
+
export interface DyNTS_AppVersionHeader_Settings {
|
|
9
|
+
/**
|
|
10
|
+
* Ki-/bekapcsolja a header-middleware-t. **Default: `true`** — informatív header, se PII,
|
|
11
|
+
* se belső részlet nincs benne, viszont nélküle minden app külön építené ugyanezt.
|
|
12
|
+
*/
|
|
13
|
+
enabled?: boolean;
|
|
14
|
+
|
|
15
|
+
/** Felülírható header-név. Default: `X-App-Version`. */
|
|
16
|
+
headerName?: string;
|
|
17
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { DyFM_EnvironmentFlag } from '@futdevpro/fsm-dynamo';
|
|
2
2
|
import { DyNTS_RouteSecurity } from '../../_enums/route-security.enum';
|
|
3
3
|
import { DyNTS_SecurityHeaders_Settings } from './security-headers-settings.interface';
|
|
4
|
+
import { DyNTS_AppVersionHeader_Settings } from './app-version-header-settings.interface';
|
|
4
5
|
import { DyNTS_SocketSecurity } from '../../_modules/socket/_enums/socket-security.enum';
|
|
5
6
|
import { DyNTS_GlobalLog_Settings } from './global-log-settings.interface';
|
|
6
7
|
import { DyNTS_Environment_Settings } from './environment-settings.interface';
|
|
@@ -170,6 +171,13 @@ export interface DyNTS_Global_Settings {
|
|
|
170
171
|
*/
|
|
171
172
|
securityHeaders?: DyNTS_SecurityHeaders_Settings;
|
|
172
173
|
|
|
174
|
+
/**
|
|
175
|
+
* FR-292 — a deployolt app-verzio kiirasa minden valasz headerebe (X-App-Version), hogy
|
|
176
|
+
* a bongeszoben NYITVA HAGYOTT SPA eszrevegye, ha kozben ujabb kliens-build ment ki.
|
|
177
|
+
* Default: enabled. Lasd {@link DyNTS_AppVersionHeader_Settings}.
|
|
178
|
+
*/
|
|
179
|
+
appVersionHeader?: DyNTS_AppVersionHeader_Settings;
|
|
180
|
+
|
|
173
181
|
/**
|
|
174
182
|
* FR-193 — bedrock OOM korai-figyelmeztető heap-watchdog ({@link DyNTS_MemoryGuard}).
|
|
175
183
|
* Ha `enabled`, a base `App` startup automatikusan feltelepíti. Periodikusan figyeli a
|
|
@@ -77,5 +77,30 @@ describe('| DyNTS_Usage_Controller', () => {
|
|
|
77
77
|
expect((getUsageDataEndpoint as any)?.tasks).toBeDefined();
|
|
78
78
|
expect((getUsageDataEndpoint as any)?.tasks.length).toBeGreaterThan(0);
|
|
79
79
|
});
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* BFR-FDPPAL-001 — optional-auth mód (`auth_settings.optional: true`): a `getAuthService()`
|
|
83
|
+
* `undefined`-ot ad; a setupEndpoints NEM halhat meg TypeError-ral (boot-crash / restart-loop),
|
|
84
|
+
* a védett getUsageData kimarad, a session-endpointok élnek.
|
|
85
|
+
*/
|
|
86
|
+
describe('| optional-auth mód — nincs auth service (BFR-FDPPAL-001)', () => {
|
|
87
|
+
beforeEach(() => {
|
|
88
|
+
(DyNTS_GlobalService.getAuthService as jasmine.Spy).and.returnValue(undefined);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('| setupEndpoints nem dob, a session-endpointok megvannak', () => {
|
|
92
|
+
expect(() => controller.setupEndpoints()).not.toThrow();
|
|
93
|
+
|
|
94
|
+
expect(controller.endpoints.find(ep => ep.name === 'newSession')).toBeDefined();
|
|
95
|
+
expect(controller.endpoints.find(ep => ep.name === 'updateUsage')).toBeDefined();
|
|
96
|
+
expect(controller.endpoints.find(ep => ep.name === 'closeSession')).toBeDefined();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('| a védett getUsageData endpoint KIMARAD (auth nélkül nem szolgálható ki)', () => {
|
|
100
|
+
controller.setupEndpoints();
|
|
101
|
+
|
|
102
|
+
expect(controller.endpoints.find(ep => ep.name === 'getUsageData')).toBeUndefined();
|
|
103
|
+
});
|
|
104
|
+
});
|
|
80
105
|
});
|
|
81
106
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
import { Request, Response } from 'express';
|
|
3
3
|
|
|
4
|
-
import { DyFM_HttpCallType, DyFM_RelativeDate } from '@futdevpro/fsm-dynamo';
|
|
4
|
+
import { DyFM_HttpCallType, DyFM_Log, DyFM_RelativeDate } from '@futdevpro/fsm-dynamo';
|
|
5
5
|
import {
|
|
6
6
|
DyFM_usageModule_settings, DyFM_UsageSession
|
|
7
7
|
} from '@futdevpro/fsm-dynamo/usage';
|
|
@@ -36,7 +36,8 @@ export class DyNTS_Usage_Controller extends DyNTS_Controller {
|
|
|
36
36
|
let username: string;
|
|
37
37
|
|
|
38
38
|
try {
|
|
39
|
-
|
|
39
|
+
// BFR-FDPPAL-001: optional-auth módban nincs auth service — a session issuer ilyenkor 'unknown'
|
|
40
|
+
username = this.authService?.getUsernameFromRequest(req) ?? 'unknown';
|
|
40
41
|
} catch {
|
|
41
42
|
username = 'unknown';
|
|
42
43
|
}
|
|
@@ -99,28 +100,41 @@ export class DyNTS_Usage_Controller extends DyNTS_Controller {
|
|
|
99
100
|
],
|
|
100
101
|
}),
|
|
101
102
|
|
|
102
|
-
new DyNTS_Endpoint_Params({
|
|
103
|
-
name: 'getUsageData',
|
|
104
|
-
type: DyFM_HttpCallType.get,
|
|
105
|
-
endpoint: DyFM_usageModule_settings.endPoints.getUsageData,
|
|
106
|
-
preProcesses: [ this.authService.authenticate_tokenPerm_accUsageData ],
|
|
107
|
-
tasks: [
|
|
108
|
-
async (req: Request, res: Response, issuer: string): Promise<void> => {
|
|
109
|
-
const range: DyFM_RelativeDate = req.params.range as DyFM_RelativeDate;
|
|
110
|
-
|
|
111
|
-
const usageService = new DyNTS_Usage_DataService({
|
|
112
|
-
issuer: issuer,
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
await usageService.getUsage(range);
|
|
116
|
-
|
|
117
|
-
res.send(
|
|
118
|
-
usageService.simplifiedDailyUsage
|
|
119
|
-
);
|
|
120
|
-
},
|
|
121
|
-
],
|
|
122
|
-
}),
|
|
123
103
|
];
|
|
104
|
+
|
|
105
|
+
// BFR-FDPPAL-001: optional-auth módban (`auth_settings.optional: true`) a `getAuthService()`
|
|
106
|
+
// `undefined`-ot ad — a védett usage-lekérdezést ilyenkor KIHAGYJUK (az usage-adat auth nélkül
|
|
107
|
+
// nem szolgálható ki), de a boot nem halhat meg TypeError-ral; a session-endpointok élnek.
|
|
108
|
+
if (this.authService) {
|
|
109
|
+
this.endpoints.push(
|
|
110
|
+
new DyNTS_Endpoint_Params({
|
|
111
|
+
name: 'getUsageData',
|
|
112
|
+
type: DyFM_HttpCallType.get,
|
|
113
|
+
endpoint: DyFM_usageModule_settings.endPoints.getUsageData,
|
|
114
|
+
preProcesses: [ this.authService.authenticate_tokenPerm_accUsageData ],
|
|
115
|
+
tasks: [
|
|
116
|
+
async (req: Request, res: Response, issuer: string): Promise<void> => {
|
|
117
|
+
const range: DyFM_RelativeDate = req.params.range as DyFM_RelativeDate;
|
|
118
|
+
|
|
119
|
+
const usageService = new DyNTS_Usage_DataService({
|
|
120
|
+
issuer: issuer,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
await usageService.getUsage(range);
|
|
124
|
+
|
|
125
|
+
res.send(
|
|
126
|
+
usageService.simplifiedDailyUsage
|
|
127
|
+
);
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
}),
|
|
131
|
+
);
|
|
132
|
+
} else {
|
|
133
|
+
DyFM_Log.warn(
|
|
134
|
+
'DyNTS_Usage_Controller: no auth service (optional-auth mode) - ' +
|
|
135
|
+
'the protected "getUsageData" endpoint is SKIPPED; session endpoints remain available.'
|
|
136
|
+
);
|
|
137
|
+
}
|
|
124
138
|
}
|
|
125
139
|
}
|
|
126
140
|
|
|
@@ -725,5 +725,60 @@ describe('| DyNTS_DataService', () => {
|
|
|
725
725
|
expect(r.changedFields).toEqual(['email']);
|
|
726
726
|
});
|
|
727
727
|
});
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* BFR-FDPTOKENSERVICE-011 — a validátor tömb-ága: egy OPCIONÁLIS `X[]`+`subObjectParams` mező
|
|
731
|
+
* `undefined` értéke (a normál „még nincs elem" eset) NEM szállhat el `forEach`-TypeError-ral.
|
|
732
|
+
* A required-hiányt a VP8-ág fogja — az `Array.isArray` őr nem gyengít semmit.
|
|
733
|
+
*/
|
|
734
|
+
describe('| validateForSave — opcionális tömb-mező subObjectParams-szal (BFR-FDPTOKENSERVICE-011)', (): void => {
|
|
735
|
+
class WithOptArr extends DyFM_Metadata {
|
|
736
|
+
name?: string;
|
|
737
|
+
invoices?: { num?: string }[];
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
const buildParams = (required: boolean): DyFM_DataModel_Params<WithOptArr> =>
|
|
741
|
+
new DyFM_DataModel_Params<WithOptArr>({
|
|
742
|
+
dataName: 'with_opt_arr',
|
|
743
|
+
properties: {
|
|
744
|
+
invoices: {
|
|
745
|
+
key: 'invoices',
|
|
746
|
+
type: DyFM_BasicProperty_Type.array,
|
|
747
|
+
required: required,
|
|
748
|
+
subObjectParams: {
|
|
749
|
+
num: { key: 'num', type: DyFM_BasicProperty_Type.string, required: true },
|
|
750
|
+
},
|
|
751
|
+
},
|
|
752
|
+
},
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
it('| undefined opcionális tömb → NEM dob (a mentés nem bukhat a hiányzó elemeken)', async (): Promise<void> => {
|
|
756
|
+
const optSvc = new DyNTS_DataService<WithOptArr>(
|
|
757
|
+
Object.assign(new WithOptArr(), { name: 'x' }), buildParams(false), 'test-issuer',
|
|
758
|
+
);
|
|
759
|
+
await expectAsync(optSvc.validateForSave()).toBeResolved();
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
it('| tényleges tömbnél a sub-validáció VÁLTOZATLANUL fut (required sub-mező hiánya → 400)', async (): Promise<void> => {
|
|
763
|
+
const optSvc = new DyNTS_DataService<WithOptArr>(
|
|
764
|
+
Object.assign(new WithOptArr(), { name: 'x', invoices: [ {} ] }), buildParams(false), 'test-issuer',
|
|
765
|
+
);
|
|
766
|
+
let thrown: any = null;
|
|
767
|
+
try { await optSvc.validateForSave(); } catch (e) { thrown = e; }
|
|
768
|
+
expect(thrown).not.toBeNull();
|
|
769
|
+
expect(DyFM_Error.getErrorStatus(thrown)).toBe(400);
|
|
770
|
+
});
|
|
771
|
+
|
|
772
|
+
it('| required tömb hiánya továbbra is validációs 400 (az őr nem gyengít; hiányra a VP1 required-ág fut)', async (): Promise<void> => {
|
|
773
|
+
const reqSvc = new DyNTS_DataService<WithOptArr>(
|
|
774
|
+
Object.assign(new WithOptArr(), { name: 'x' }), buildParams(true), 'test-issuer',
|
|
775
|
+
);
|
|
776
|
+
let thrown: any = null;
|
|
777
|
+
try { await reqSvc.validateForSave(); } catch (e) { thrown = e; }
|
|
778
|
+
expect(thrown).not.toBeNull();
|
|
779
|
+
expect(DyFM_Error.getErrorStatus(thrown)).toBe(400);
|
|
780
|
+
expect(DyFM_Error.getErrorCode(thrown)).toContain('DyNTS-DS0-VP');
|
|
781
|
+
});
|
|
782
|
+
});
|
|
728
783
|
});
|
|
729
784
|
|
|
@@ -1695,7 +1695,10 @@ export class DyNTS_DataService<T extends DyFM_Metadata> {
|
|
|
1695
1695
|
});
|
|
1696
1696
|
}
|
|
1697
1697
|
|
|
1698
|
-
|
|
1698
|
+
// BFR-FDPTOKENSERVICE-011: csak TÉNYLEGES tömbnél szállunk le a sub-objektumokba — egy
|
|
1699
|
+
// OPCIONÁLIS `X[]` mező `undefined` értéke (a normál „még nincs elem" eset) nem dönthet
|
|
1700
|
+
// `forEach`-TypeError-ba; a required-hiányt a fenti VP8-ág fogja, ez az őr nem gyengít.
|
|
1701
|
+
if (propertyParams.subObjectParams && Array.isArray(propertyValue)) {
|
|
1699
1702
|
propertyValue.forEach((item: any): void => {
|
|
1700
1703
|
this.validateDataByPropertyParams(item, propertyParams.subObjectParams, propertyParams);
|
|
1701
1704
|
});
|
|
@@ -34,6 +34,8 @@ import { DyNTS_ClientSafeError_Util } from '../../_collections/client-safe-error
|
|
|
34
34
|
import { DyNTS_getEnvironment_settings } from '../../_collections/get-environment-settings.util';
|
|
35
35
|
import { DyNTS_createSecurityHeadersMiddleware } from '../../_collections/security-headers.util';
|
|
36
36
|
import { DyNTS_createLoadShedMiddleware } from '../../_collections/load-shed.util';
|
|
37
|
+
import { DyNTS_createAppVersionHeaderMiddleware } from '../../_collections/app-version-header.util';
|
|
38
|
+
import { DyNTS_AppVersionHeader_Settings } from '../../_models/interfaces/app-version-header-settings.interface';
|
|
37
39
|
import { DyNTS_SecurityHeaders_Settings } from '../../_models/interfaces/security-headers-settings.interface';
|
|
38
40
|
import { startMongoReconnectGuard } from '../../_collections/mongo-reconnect-guard.util';
|
|
39
41
|
import { DyNTS_resolveStaticCacheControlHeader } from '../../_collections/static-cache-control.util';
|
|
@@ -1296,6 +1298,9 @@ export abstract class DyNTS_App extends DyNTS_SingletonService {
|
|
|
1296
1298
|
this.mountSecurityHeaders(this.openExpress);
|
|
1297
1299
|
// FR-041 — CORS allowlist enforcement. No-op if getCorsSettings() is not overridden.
|
|
1298
1300
|
this.mountCors(this.openExpress);
|
|
1301
|
+
// FR-292 — a deployolt app-verzio minden valaszon. A CORS UTAN kell, mert az
|
|
1302
|
+
// `Access-Control-Expose-Headers`-hoz HOZZAFUZ (kulonben a CORS felulirna).
|
|
1303
|
+
this.mountAppVersionHeader(this.openExpress);
|
|
1299
1304
|
// BFR-OVERSEER-009 — opt-in auto-load-shed 503 CRITICAL memória-nyomásnál (esszenciális utak kivételével).
|
|
1300
1305
|
if (DyNTS_global_settings.memoryGuard?.loadShedEnabled) {
|
|
1301
1306
|
this.openExpress.use(DyNTS_createLoadShedMiddleware());
|
|
@@ -1314,6 +1319,8 @@ export abstract class DyNTS_App extends DyNTS_SingletonService {
|
|
|
1314
1319
|
this.mountSecurityHeaders(this.secureExpress);
|
|
1315
1320
|
// FR-041 — CORS allowlist enforcement (same as open express).
|
|
1316
1321
|
this.mountCors(this.secureExpress);
|
|
1322
|
+
// FR-292 — a deployolt app-verzio minden valaszon (mint az open expressen).
|
|
1323
|
+
this.mountAppVersionHeader(this.secureExpress);
|
|
1317
1324
|
// BFR-OVERSEER-009 — opt-in auto-load-shed 503 (mint az open expressen).
|
|
1318
1325
|
if (DyNTS_global_settings.memoryGuard?.loadShedEnabled) {
|
|
1319
1326
|
this.secureExpress.use(DyNTS_createLoadShedMiddleware());
|
|
@@ -1682,6 +1689,25 @@ export abstract class DyNTS_App extends DyNTS_SingletonService {
|
|
|
1682
1689
|
));
|
|
1683
1690
|
}
|
|
1684
1691
|
|
|
1692
|
+
/**
|
|
1693
|
+
* FR-292 — a deployolt app-verziot minden valaszra rateszi (`X-App-Version`), hogy a
|
|
1694
|
+
* bongeszoben NYITVA HAGYOTT SPA eszrevegye, ha kozben ujabb kliens-build ment ki.
|
|
1695
|
+
*
|
|
1696
|
+
* Nem polling: a jelzes a MAR MENO API-hivasokon utazik. Az `enabled` default TRUE — egy
|
|
1697
|
+
* informativ response-header, ami se PII-t, se belso reszletet nem szivarogtat, viszont
|
|
1698
|
+
* enelkul minden app kulon-kulon epitene ugyanezt.
|
|
1699
|
+
*/
|
|
1700
|
+
protected mountAppVersionHeader(express: Express.Application): void {
|
|
1701
|
+
const settings: DyNTS_AppVersionHeader_Settings | undefined = DyNTS_global_settings.appVersionHeader;
|
|
1702
|
+
if (settings?.enabled === false) {
|
|
1703
|
+
return;
|
|
1704
|
+
}
|
|
1705
|
+
express.use(DyNTS_createAppVersionHeaderMiddleware(
|
|
1706
|
+
(): string => DyNTS_global_settings.systemVersion,
|
|
1707
|
+
settings?.headerName,
|
|
1708
|
+
));
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1685
1711
|
protected mountCors(express: Express.Application): void {
|
|
1686
1712
|
const settings: DyNTS_Cors_Settings | undefined = this.getCorsSettings?.();
|
|
1687
1713
|
if (!settings) {
|
package/src/index.ts
CHANGED
|
@@ -53,9 +53,11 @@ export * from './_models/interfaces/signed-url-verify-result.interface';
|
|
|
53
53
|
export * from './_models/interfaces/static-client-settings.interface';
|
|
54
54
|
export * from './_models/interfaces/cors-settings.interface';
|
|
55
55
|
export * from './_models/interfaces/security-headers-settings.interface';
|
|
56
|
+
export * from './_models/interfaces/app-version-header-settings.interface';
|
|
56
57
|
export * from './_models/interfaces/db-query-options.interface';
|
|
57
58
|
export * from './_models/interfaces/migration-entry.interface';
|
|
58
59
|
export * from './_collections/security-headers.util';
|
|
60
|
+
export * from './_collections/app-version-header.util';
|
|
59
61
|
export * from './_collections/signed-url.util';
|
|
60
62
|
export * from './_collections/client-safe-error.util';
|
|
61
63
|
export * from './_collections/load-shed.util';
|