@futdevpro/nts-dynamo 1.15.19 → 1.15.20

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.
@@ -29,11 +29,11 @@
29
29
  details: Config-vezérelt log rendszer, ahol egy boolean flag-gel be lehet kapcsolni a file-ba történő log írást, és további config opciókkal lehet állítani a maximum tárolt log mennyiséget (file size / rotation / retention) és egyéb log kezelési paramétereket. Implementálva (2026-05-17): DyNTS_FileLog_Service (singleton) — process.stdout/stderr.write monkey-patch, per-session log fájl, sync appendFileSync (crash-safe), config: file_log.{enabled, logDir, filenamePrefix, maxFileSizeMb, maxFiles, retentionDays, stripAnsi, includeStdout, includeStderr}. Rotation size-limitre, retention by count + age (ami előbb teljesül). Silent failure (file IO sosem blokkolja a szervert). Spec: 11 spec / 0 fail. Full suite: 1133/0. Smoke verified.
30
30
 
31
31
  - [FEATURE] (BL-20260420-002) Admin API key alapú authentikációs réteg (env-ből)
32
- status: pending
32
+ status: done
33
33
  priority: high
34
34
  source: user
35
35
  area: backend
36
- details: Új admin authentikációs mechanizmus bevezetése, ahol az admin API key-t environment variable-ből olvassuk (pl. DYNTS_ADMIN_API_KEY), és ezzel lehet védeni az érzékeny / admin endpointokat. Alaposan át kell tervezni, hogy hogyan integrálódjon a meglévő DyNTS auth rendszerekbe (controller / endpoint params szinten reusable guard / middleware), opcionálisan alkalmazható legyen meglévő endpointokra is (pl. errors controller endpointjaira). Előfeltétele a BL-20260420-003 log lekérő endpointnak
36
+ details: Új admin authentikációs mechanizmus bevezetése, ahol az admin API key-t environment variable-ből olvassuk (pl. DYNTS_ADMIN_API_KEY), és ezzel lehet védeni az érzékeny / admin endpointokat. Implementálva (2026-05-17): DyNTS_AdminApiKey_AuthService (singleton) — preProcess `.verify(req, res)` fn opt-in átadható `DyNTS_Endpoint_Params.preProcesses`-be vagy `DyNTS_getLogsRoutingModule({ authPreProcess })`-be. Default env var DYNTS_ADMIN_API_KEY, header x-admin-api-key + Authorization Bearer fallback. Konfig: `configure({ envVarName, headerName, allowAuthorizationBearer })`. Timing-safe compare (crypto.timingSafeEqual + length-mismatch dummy compare). Hibák: 500 ha env nincs, 401 ha header hiányzik/rossz. Env minden híváskor olvasott (nem cache-elt). 15/15 spec + 1148/0 full suite + smoke verifikálva. Foundational a BL-003 (log fetch) és BL-004 (errors retrofit) entry-knek.
37
37
 
38
38
  - [FEATURE] (BL-20260420-003) Server log file-ok lekérése admin endpointon keresztül
39
39
  status: ⏳ pending
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Config a `DyNTS_AdminApiKey_AuthService.configure(...)`-hoz.
3
+ *
4
+ * Minden mezo opcionalis — a default-ok megfelelnek a tipikus hasznalat-eset
5
+ * elvarasanak (DYNTS_ADMIN_API_KEY env var + x-admin-api-key header +
6
+ * Bearer fallback).
7
+ */
8
+ export interface DyNTS_AdminApiKey_Config {
9
+ /**
10
+ * Env var nev, ahonnan az admin API key olvasodik.
11
+ * Default: `DYNTS_ADMIN_API_KEY`.
12
+ *
13
+ * Override-olhato pl. multi-tenant deploy-okhoz vagy ha a host app
14
+ * mas konvenciot kovet (`MY_APP_ADMIN_KEY`, stb.).
15
+ */
16
+ envVarName?: string;
17
+ /**
18
+ * HTTP header nev (case-insensitive — Express normalizalja lowercase-re).
19
+ * Default: `x-admin-api-key`.
20
+ */
21
+ headerName?: string;
22
+ /**
23
+ * Engedi-e az `Authorization: Bearer <key>` fallback-et a primer header
24
+ * helyett. Default: `true`.
25
+ *
26
+ * Hasznos amikor a kliens egy generikus HTTP klienst hasznal ami csak
27
+ * az Authorization header-t allitja, vagy amikor proxy-k strippelik
28
+ * a custom header-eket.
29
+ */
30
+ allowAuthorizationBearer?: boolean;
31
+ }
32
+ //# sourceMappingURL=admin-api-key-config.interface.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-api-key-config.interface.d.ts","sourceRoot":"","sources":["../../../../src/_modules/admin-auth/_models/admin-api-key-config.interface.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,WAAW,wBAAwB;IACvC;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;;;;;OAOG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAC;CACpC"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=admin-api-key-config.interface.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-api-key-config.interface.js","sourceRoot":"","sources":["../../../../src/_modules/admin-auth/_models/admin-api-key-config.interface.ts"],"names":[],"mappings":""}
@@ -0,0 +1,90 @@
1
+ import { Request, Response } from 'express';
2
+ import { DyNTS_SingletonServiceBase } from '../../_services/base/singleton.service-base';
3
+ import { DyNTS_AdminApiKey_Config } from './_models/admin-api-key-config.interface';
4
+ /**
5
+ * Admin API key auth service — opt-in HTTP guard a meglevo `DyNTS_Endpoint_Params.preProcesses`
6
+ * mechanizmushoz. Egy env var-ban tarolt fix kulccsal valid-alja a bejovo kerest.
7
+ *
8
+ * **Hasznalat (host app):**
9
+ * ```ts
10
+ * const adminAuth = DyNTS_AdminApiKey_AuthService.getInstance();
11
+ * // opcionalis konfig:
12
+ * // adminAuth.configure({ envVarName: 'MY_KEY', headerName: 'x-my-key' });
13
+ *
14
+ * new DyNTS_Endpoint_Params({
15
+ * ...,
16
+ * preProcesses: [adminAuth.verify, ...other],
17
+ * });
18
+ *
19
+ * // vagy a logs routing module-on at
20
+ * DyNTS_getLogsRoutingModule({ authPreProcess: adminAuth.verify });
21
+ * ```
22
+ *
23
+ * **Viselkedes:**
24
+ * - env var beallitva ES helyes header → silent pass
25
+ * - env var beallitva, header hianyzik / rossz → 401 DyFM_Error
26
+ * - env var NINCS beallitva → 500 DyFM_Error (fail-closed; NEM silent allow)
27
+ *
28
+ * **Header lookup:**
29
+ * 1. `x-admin-api-key` (default canonical header)
30
+ * 2. `Authorization: Bearer <key>` (fallback ha `allowAuthorizationBearer === true`)
31
+ *
32
+ * **Timing-safe:** `crypto.timingSafeEqual` Buffer-konvertalassal. Length-mismatch
33
+ * eseten dummy compare-rel azonos idő, hogy a kulcs-hossz ne szivarogjon ki.
34
+ *
35
+ * **Env var read-on-each-call:** a `verify()` minden hivasnal olvassa az env-et,
36
+ * nem cache-eli. Igy a host az env-et utolagosan is allithatja (pl. config
37
+ * loader az auth.service.install() utan).
38
+ *
39
+ * **Singleton:** `getInstance()`-szel hivd. A `.verify` mezo binding-elve van
40
+ * `this`-re, igy direkt atadhato `preProcesses`-be ujracsomagolas nelkul.
41
+ */
42
+ export declare class DyNTS_AdminApiKey_AuthService extends DyNTS_SingletonServiceBase {
43
+ static getInstance(): DyNTS_AdminApiKey_AuthService;
44
+ private envVarName;
45
+ private headerName;
46
+ private allowAuthorizationBearer;
47
+ /**
48
+ * Konfig override. Hianyzo mezok a default-okat orzik.
49
+ * Hivhato barmikor — a `verify()` a friss config-ot olvassa.
50
+ */
51
+ configure(config: DyNTS_AdminApiKey_Config): void;
52
+ /**
53
+ * Aktualis konfig olvasasa (test/diagnosztika celokra).
54
+ */
55
+ getConfig(): Required<DyNTS_AdminApiKey_Config>;
56
+ /**
57
+ * Pre-process function — atadhato `DyNTS_Endpoint_Params.preProcesses`-be,
58
+ * vagy `DyNTS_getLogsRoutingModule({ authPreProcess: ... })`-be.
59
+ *
60
+ * Throws:
61
+ * - 500 ha az env var nincs beallitva (vagy ures string)
62
+ * - 401 ha a header hianyzik vagy nem egyezik
63
+ *
64
+ * A `req`/`res` parametereket NEM modositja (a kerest tovabb engedi a tovabbi
65
+ * preProcess-eknek; csak hiba eseten throw-ol).
66
+ */
67
+ readonly verify: (req: Request, _res: Response) => Promise<void>;
68
+ /**
69
+ * Header lookup — elobb a primer header, aztan opcionalisan az Authorization Bearer.
70
+ * Az ures string is "hianyzo"-nak szamit (a Buffer.from('') es timingSafeEqual
71
+ * konzisztencia miatt).
72
+ */
73
+ private extractKeyFromRequest;
74
+ /**
75
+ * Timing-safe compare ket string kozott. Length-mismatch eseten egy dummy
76
+ * compare-rel azonos idot kenyszeritunk (a kulcs-hossz nem szivaroghat ki
77
+ * timing-attackal).
78
+ *
79
+ * crypto.timingSafeEqual KOTELEZOEN azonos Buffer-hosszt var — kulonbozo
80
+ * hosszra throw-ol, ezert vizsgaljuk elobb a length-et es csak utana
81
+ * compare-elunk.
82
+ */
83
+ private timingSafeEquals;
84
+ /**
85
+ * Test-only: visszaallitja a default config-ot, hogy a specfajlok ne szivarogjak
86
+ * at egymas state-jet. Production code NE hivja.
87
+ */
88
+ _resetForTesting(): void;
89
+ }
90
+ //# sourceMappingURL=admin-api-key.auth-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-api-key.auth-service.d.ts","sourceRoot":"","sources":["../../../src/_modules/admin-auth/admin-api-key.auth-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAK5C,OAAO,EAAE,0BAA0B,EAAE,MAAM,6CAA6C,CAAC;AAGzF,OAAO,EAAE,wBAAwB,EAAE,MAAM,0CAA0C,CAAC;AAsBpF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,qBAAa,6BAA8B,SAAQ,0BAA0B;IAE3E,MAAM,CAAC,WAAW,IAAI,6BAA6B;IAInD,OAAO,CAAC,UAAU,CAAgC;IAClD,OAAO,CAAC,UAAU,CAA+B;IACjD,OAAO,CAAC,wBAAwB,CAAiC;IAGjE;;;OAGG;IACH,SAAS,CAAC,MAAM,EAAE,wBAAwB,GAAG,IAAI;IAajD;;OAEG;IACH,SAAS,IAAI,QAAQ,CAAC,wBAAwB,CAAC;IAQ/C;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,MAAM,QAAe,OAAO,QAAQ,QAAQ,KAAG,OAAO,CAAC,IAAI,CAAC,CAqCnE;IAGF;;;;OAIG;IACH,OAAO,CAAC,qBAAqB;IAuB7B;;;;;;;;OAQG;IACH,OAAO,CAAC,gBAAgB;IAexB;;;OAGG;IACH,gBAAgB,IAAI,IAAI;CAKzB"}
@@ -0,0 +1,195 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DyNTS_AdminApiKey_AuthService = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const crypto = tslib_1.__importStar(require("crypto"));
6
+ const fsm_dynamo_1 = require("@futdevpro/fsm-dynamo");
7
+ const singleton_service_base_1 = require("../../_services/base/singleton.service-base");
8
+ const global_settings_const_1 = require("../../_collections/global-settings.const");
9
+ /** Default env var nev az admin API key-hez. */
10
+ const DEFAULT_ENV_VAR_NAME = 'DYNTS_ADMIN_API_KEY';
11
+ /** Default header nev (Express lowercase-re normalizalja az osszes header-t). */
12
+ const DEFAULT_HEADER_NAME = 'x-admin-api-key';
13
+ /** Default Bearer fallback engedelyezve van. */
14
+ const DEFAULT_ALLOW_BEARER = true;
15
+ /** Service-nev az error-okhoz. */
16
+ const SERVICE_NAME = 'DyNTS_AdminApiKey_AuthService';
17
+ /** ErrorCode prefix — system shortcode + saját kod. */
18
+ const buildErrorCode = (subcode) => {
19
+ const sys = global_settings_const_1.DyNTS_global_settings.systemShortCodeName ?? 'DyNTS';
20
+ return `${sys}|DyNTS-AAK-${subcode}`;
21
+ };
22
+ /**
23
+ * Admin API key auth service — opt-in HTTP guard a meglevo `DyNTS_Endpoint_Params.preProcesses`
24
+ * mechanizmushoz. Egy env var-ban tarolt fix kulccsal valid-alja a bejovo kerest.
25
+ *
26
+ * **Hasznalat (host app):**
27
+ * ```ts
28
+ * const adminAuth = DyNTS_AdminApiKey_AuthService.getInstance();
29
+ * // opcionalis konfig:
30
+ * // adminAuth.configure({ envVarName: 'MY_KEY', headerName: 'x-my-key' });
31
+ *
32
+ * new DyNTS_Endpoint_Params({
33
+ * ...,
34
+ * preProcesses: [adminAuth.verify, ...other],
35
+ * });
36
+ *
37
+ * // vagy a logs routing module-on at
38
+ * DyNTS_getLogsRoutingModule({ authPreProcess: adminAuth.verify });
39
+ * ```
40
+ *
41
+ * **Viselkedes:**
42
+ * - env var beallitva ES helyes header → silent pass
43
+ * - env var beallitva, header hianyzik / rossz → 401 DyFM_Error
44
+ * - env var NINCS beallitva → 500 DyFM_Error (fail-closed; NEM silent allow)
45
+ *
46
+ * **Header lookup:**
47
+ * 1. `x-admin-api-key` (default canonical header)
48
+ * 2. `Authorization: Bearer <key>` (fallback ha `allowAuthorizationBearer === true`)
49
+ *
50
+ * **Timing-safe:** `crypto.timingSafeEqual` Buffer-konvertalassal. Length-mismatch
51
+ * eseten dummy compare-rel azonos idő, hogy a kulcs-hossz ne szivarogjon ki.
52
+ *
53
+ * **Env var read-on-each-call:** a `verify()` minden hivasnal olvassa az env-et,
54
+ * nem cache-eli. Igy a host az env-et utolagosan is allithatja (pl. config
55
+ * loader az auth.service.install() utan).
56
+ *
57
+ * **Singleton:** `getInstance()`-szel hivd. A `.verify` mezo binding-elve van
58
+ * `this`-re, igy direkt atadhato `preProcesses`-be ujracsomagolas nelkul.
59
+ */
60
+ class DyNTS_AdminApiKey_AuthService extends singleton_service_base_1.DyNTS_SingletonServiceBase {
61
+ static getInstance() {
62
+ return DyNTS_AdminApiKey_AuthService.getSingletonInstance();
63
+ }
64
+ envVarName = DEFAULT_ENV_VAR_NAME;
65
+ headerName = DEFAULT_HEADER_NAME;
66
+ allowAuthorizationBearer = DEFAULT_ALLOW_BEARER;
67
+ /**
68
+ * Konfig override. Hianyzo mezok a default-okat orzik.
69
+ * Hivhato barmikor — a `verify()` a friss config-ot olvassa.
70
+ */
71
+ configure(config) {
72
+ if (config.envVarName !== undefined) {
73
+ this.envVarName = config.envVarName;
74
+ }
75
+ if (config.headerName !== undefined) {
76
+ // Express lowercase-re normalizal — itt is lowercase-eljuk a konzisztenciaert
77
+ this.headerName = config.headerName.toLowerCase();
78
+ }
79
+ if (config.allowAuthorizationBearer !== undefined) {
80
+ this.allowAuthorizationBearer = config.allowAuthorizationBearer;
81
+ }
82
+ }
83
+ /**
84
+ * Aktualis konfig olvasasa (test/diagnosztika celokra).
85
+ */
86
+ getConfig() {
87
+ return {
88
+ envVarName: this.envVarName,
89
+ headerName: this.headerName,
90
+ allowAuthorizationBearer: this.allowAuthorizationBearer,
91
+ };
92
+ }
93
+ /**
94
+ * Pre-process function — atadhato `DyNTS_Endpoint_Params.preProcesses`-be,
95
+ * vagy `DyNTS_getLogsRoutingModule({ authPreProcess: ... })`-be.
96
+ *
97
+ * Throws:
98
+ * - 500 ha az env var nincs beallitva (vagy ures string)
99
+ * - 401 ha a header hianyzik vagy nem egyezik
100
+ *
101
+ * A `req`/`res` parametereket NEM modositja (a kerest tovabb engedi a tovabbi
102
+ * preProcess-eknek; csak hiba eseten throw-ol).
103
+ */
104
+ verify = async (req, _res) => {
105
+ const expectedKey = process.env[this.envVarName] ?? '';
106
+ if (expectedKey.length === 0) {
107
+ throw new fsm_dynamo_1.DyFM_Error({
108
+ status: 500,
109
+ errorCode: buildErrorCode('CONFIG'),
110
+ addECToUserMsg: true,
111
+ message: `Admin API key not configured: env var ${this.envVarName} is not set or empty`,
112
+ userMessage: 'Server configuration error',
113
+ issuerService: SERVICE_NAME,
114
+ });
115
+ }
116
+ const providedKey = this.extractKeyFromRequest(req);
117
+ if (providedKey === null) {
118
+ throw new fsm_dynamo_1.DyFM_Error({
119
+ status: 401,
120
+ errorCode: buildErrorCode('MISSING'),
121
+ addECToUserMsg: true,
122
+ message: `Admin API key required (expected header: ${this.headerName})`,
123
+ userMessage: 'Admin API key required',
124
+ issuerService: SERVICE_NAME,
125
+ });
126
+ }
127
+ if (!this.timingSafeEquals(providedKey, expectedKey)) {
128
+ throw new fsm_dynamo_1.DyFM_Error({
129
+ status: 401,
130
+ errorCode: buildErrorCode('INVALID'),
131
+ addECToUserMsg: true,
132
+ message: 'Admin API key invalid',
133
+ userMessage: 'Admin API key invalid',
134
+ issuerService: SERVICE_NAME,
135
+ });
136
+ }
137
+ // Silent pass — return resolved promise
138
+ };
139
+ /**
140
+ * Header lookup — elobb a primer header, aztan opcionalisan az Authorization Bearer.
141
+ * Az ures string is "hianyzo"-nak szamit (a Buffer.from('') es timingSafeEqual
142
+ * konzisztencia miatt).
143
+ */
144
+ extractKeyFromRequest(req) {
145
+ // Primer header
146
+ const primary = req.headers[this.headerName];
147
+ const primaryStr = Array.isArray(primary) ? primary[0] ?? '' : (typeof primary === 'string' ? primary : '');
148
+ if (primaryStr.length > 0) {
149
+ return primaryStr;
150
+ }
151
+ // Authorization Bearer fallback
152
+ if (this.allowAuthorizationBearer) {
153
+ const authHeader = req.headers['authorization'];
154
+ const authStr = Array.isArray(authHeader) ? authHeader[0] ?? '' : (typeof authHeader === 'string' ? authHeader : '');
155
+ if (authStr.toLowerCase().startsWith('bearer ')) {
156
+ const token = authStr.substring(7).trim();
157
+ if (token.length > 0) {
158
+ return token;
159
+ }
160
+ }
161
+ }
162
+ return null;
163
+ }
164
+ /**
165
+ * Timing-safe compare ket string kozott. Length-mismatch eseten egy dummy
166
+ * compare-rel azonos idot kenyszeritunk (a kulcs-hossz nem szivaroghat ki
167
+ * timing-attackal).
168
+ *
169
+ * crypto.timingSafeEqual KOTELEZOEN azonos Buffer-hosszt var — kulonbozo
170
+ * hosszra throw-ol, ezert vizsgaljuk elobb a length-et es csak utana
171
+ * compare-elunk.
172
+ */
173
+ timingSafeEquals(a, b) {
174
+ const aBuf = Buffer.from(a, 'utf-8');
175
+ const bBuf = Buffer.from(b, 'utf-8');
176
+ if (aBuf.length !== bBuf.length) {
177
+ // Dummy compare ugyanazzal a string-gel: konstans ideju mukodest biztosit
178
+ // mielott visszaternenk false-szal — igy a length-mismatch nem szivaroghat ki.
179
+ crypto.timingSafeEqual(bBuf, bBuf);
180
+ return false;
181
+ }
182
+ return crypto.timingSafeEqual(aBuf, bBuf);
183
+ }
184
+ /**
185
+ * Test-only: visszaallitja a default config-ot, hogy a specfajlok ne szivarogjak
186
+ * at egymas state-jet. Production code NE hivja.
187
+ */
188
+ _resetForTesting() {
189
+ this.envVarName = DEFAULT_ENV_VAR_NAME;
190
+ this.headerName = DEFAULT_HEADER_NAME;
191
+ this.allowAuthorizationBearer = DEFAULT_ALLOW_BEARER;
192
+ }
193
+ }
194
+ exports.DyNTS_AdminApiKey_AuthService = DyNTS_AdminApiKey_AuthService;
195
+ //# sourceMappingURL=admin-api-key.auth-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-api-key.auth-service.js","sourceRoot":"","sources":["../../../src/_modules/admin-auth/admin-api-key.auth-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export { DyNTS_AdminApiKey_AuthService } from './admin-api-key.auth-service';
2
+ export { DyNTS_AdminApiKey_Config } from './_models/admin-api-key-config.interface';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/_modules/admin-auth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,6BAA6B,EAAE,MAAM,8BAA8B,CAAC;AAC7E,OAAO,EAAE,wBAAwB,EAAE,MAAM,0CAA0C,CAAC"}
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DyNTS_AdminApiKey_AuthService = void 0;
4
+ var admin_api_key_auth_service_1 = require("./admin-api-key.auth-service");
5
+ Object.defineProperty(exports, "DyNTS_AdminApiKey_AuthService", { enumerable: true, get: function () { return admin_api_key_auth_service_1.DyNTS_AdminApiKey_AuthService; } });
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/_modules/admin-auth/index.ts"],"names":[],"mappings":";;;AAAA,2EAA6E;AAApE,2IAAA,6BAA6B,OAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@futdevpro/nts-dynamo",
3
- "version": "01.15.19",
3
+ "version": "01.15.20",
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",
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Config a `DyNTS_AdminApiKey_AuthService.configure(...)`-hoz.
3
+ *
4
+ * Minden mezo opcionalis — a default-ok megfelelnek a tipikus hasznalat-eset
5
+ * elvarasanak (DYNTS_ADMIN_API_KEY env var + x-admin-api-key header +
6
+ * Bearer fallback).
7
+ */
8
+ export interface DyNTS_AdminApiKey_Config {
9
+ /**
10
+ * Env var nev, ahonnan az admin API key olvasodik.
11
+ * Default: `DYNTS_ADMIN_API_KEY`.
12
+ *
13
+ * Override-olhato pl. multi-tenant deploy-okhoz vagy ha a host app
14
+ * mas konvenciot kovet (`MY_APP_ADMIN_KEY`, stb.).
15
+ */
16
+ envVarName?: string;
17
+
18
+ /**
19
+ * HTTP header nev (case-insensitive — Express normalizalja lowercase-re).
20
+ * Default: `x-admin-api-key`.
21
+ */
22
+ headerName?: string;
23
+
24
+ /**
25
+ * Engedi-e az `Authorization: Bearer <key>` fallback-et a primer header
26
+ * helyett. Default: `true`.
27
+ *
28
+ * Hasznos amikor a kliens egy generikus HTTP klienst hasznal ami csak
29
+ * az Authorization header-t allitja, vagy amikor proxy-k strippelik
30
+ * a custom header-eket.
31
+ */
32
+ allowAuthorizationBearer?: boolean;
33
+ }
@@ -0,0 +1,200 @@
1
+ import { Request, Response } from 'express';
2
+
3
+ import { DyFM_Error } from '@futdevpro/fsm-dynamo';
4
+
5
+ import { DyNTS_AdminApiKey_AuthService } from './admin-api-key.auth-service';
6
+
7
+
8
+ const TEST_KEY: string = 'super-secret-admin-key-1234567890';
9
+ const TEST_ENV_VAR: string = 'DYNTS_ADMIN_API_KEY';
10
+
11
+
12
+ /** Test-only — minimal Request mock-ot ad vissza adott headers-szel. */
13
+ const mockReq = (headers: Record<string, string | undefined> = {}): Request => {
14
+ return { headers: headers } as unknown as Request;
15
+ };
16
+
17
+ /** Test-only — minimal Response stub (verify nem irja, csak typing miatt kell). */
18
+ const mockRes = (): Response => {
19
+ return {} as unknown as Response;
20
+ };
21
+
22
+
23
+ describe('| DyNTS_AdminApiKey_AuthService', (): void => {
24
+ let svc: DyNTS_AdminApiKey_AuthService;
25
+ let originalEnv: string | undefined;
26
+
27
+ beforeEach((): void => {
28
+ svc = DyNTS_AdminApiKey_AuthService.getInstance();
29
+ svc._resetForTesting();
30
+ originalEnv = process.env[TEST_ENV_VAR];
31
+ });
32
+
33
+ afterEach((): void => {
34
+ svc._resetForTesting();
35
+ if (originalEnv === undefined) {
36
+ delete process.env[TEST_ENV_VAR];
37
+ } else {
38
+ process.env[TEST_ENV_VAR] = originalEnv;
39
+ }
40
+ });
41
+
42
+
43
+ describe('| verify() — config errors', (): void => {
44
+ it('| 500 ha az env var nincs beallitva', async (): Promise<void> => {
45
+ delete process.env[TEST_ENV_VAR];
46
+ let thrown: any = null;
47
+ try { await svc.verify(mockReq({}), mockRes()); } catch (e) { thrown = e; }
48
+ expect(thrown).not.toBeNull();
49
+ expect(DyFM_Error.getErrorStatus(thrown)).toBe(500);
50
+ expect(DyFM_Error.getErrorCode(thrown)).toContain('DyNTS-AAK-CONFIG');
51
+ });
52
+
53
+ it('| 500 ha az env var ures string', async (): Promise<void> => {
54
+ process.env[TEST_ENV_VAR] = '';
55
+ let thrown: any = null;
56
+ try { await svc.verify(mockReq({}), mockRes()); } catch (e) { thrown = e; }
57
+ expect(thrown).not.toBeNull();
58
+ expect(DyFM_Error.getErrorStatus(thrown)).toBe(500);
59
+ });
60
+ });
61
+
62
+ describe('| verify() — auth errors', (): void => {
63
+ beforeEach((): void => { process.env[TEST_ENV_VAR] = TEST_KEY; });
64
+
65
+ it('| 401 ha a header teljesen hianyzik', async (): Promise<void> => {
66
+ let thrown: any = null;
67
+ try { await svc.verify(mockReq({}), mockRes()); } catch (e) { thrown = e; }
68
+ expect(thrown).not.toBeNull();
69
+ expect(DyFM_Error.getErrorStatus(thrown)).toBe(401);
70
+ expect(DyFM_Error.getErrorCode(thrown)).toContain('DyNTS-AAK-MISSING');
71
+ });
72
+
73
+ it('| 401 ha a header rossz erteku', async (): Promise<void> => {
74
+ let thrown: any = null;
75
+ try {
76
+ await svc.verify(mockReq({ 'x-admin-api-key': 'wrong-key' }), mockRes());
77
+ } catch (e) { thrown = e; }
78
+ expect(thrown).not.toBeNull();
79
+ expect(DyFM_Error.getErrorStatus(thrown)).toBe(401);
80
+ expect(DyFM_Error.getErrorCode(thrown)).toContain('DyNTS-AAK-INVALID');
81
+ });
82
+
83
+ it('| 401 length-mismatch eseten is (timing-safe path)', async (): Promise<void> => {
84
+ let thrown: any = null;
85
+ try {
86
+ await svc.verify(mockReq({ 'x-admin-api-key': 'short' }), mockRes());
87
+ } catch (e) { thrown = e; }
88
+ expect(thrown).not.toBeNull();
89
+ expect(DyFM_Error.getErrorStatus(thrown)).toBe(401);
90
+ expect(DyFM_Error.getErrorCode(thrown)).toContain('DyNTS-AAK-INVALID');
91
+ });
92
+ });
93
+
94
+ describe('| verify() — happy paths', (): void => {
95
+ beforeEach((): void => { process.env[TEST_ENV_VAR] = TEST_KEY; });
96
+
97
+ it('| silent pass ha az x-admin-api-key header egyezik', async (): Promise<void> => {
98
+ await expectAsync(
99
+ svc.verify(mockReq({ 'x-admin-api-key': TEST_KEY }), mockRes()),
100
+ ).toBeResolved();
101
+ });
102
+
103
+ it('| silent pass Authorization Bearer fallback-bol (default engedelyezve)', async (): Promise<void> => {
104
+ await expectAsync(
105
+ svc.verify(mockReq({ authorization: `Bearer ${TEST_KEY}` }), mockRes()),
106
+ ).toBeResolved();
107
+ });
108
+
109
+ it('| silent pass Authorization Bearer kis-nagybetu case-insensitive', async (): Promise<void> => {
110
+ await expectAsync(
111
+ svc.verify(mockReq({ authorization: `bearer ${TEST_KEY}` }), mockRes()),
112
+ ).toBeResolved();
113
+ });
114
+ });
115
+
116
+ describe('| configure() — overrides', (): void => {
117
+ it('| custom envVarName-bol olvas', async (): Promise<void> => {
118
+ svc.configure({ envVarName: 'MY_CUSTOM_KEY' });
119
+ process.env['MY_CUSTOM_KEY'] = TEST_KEY;
120
+
121
+ await expectAsync(
122
+ svc.verify(mockReq({ 'x-admin-api-key': TEST_KEY }), mockRes()),
123
+ ).toBeResolved();
124
+
125
+ delete process.env['MY_CUSTOM_KEY'];
126
+ });
127
+
128
+ it('| custom headerName-rol olvas', async (): Promise<void> => {
129
+ process.env[TEST_ENV_VAR] = TEST_KEY;
130
+ svc.configure({ headerName: 'x-my-admin' });
131
+
132
+ // Default x-admin-api-key MOSTANTOL NEM mukodik
133
+ let thrown: any = null;
134
+ try {
135
+ await svc.verify(mockReq({ 'x-admin-api-key': TEST_KEY }), mockRes());
136
+ } catch (e) { thrown = e; }
137
+ expect(thrown).not.toBeNull();
138
+ expect(DyFM_Error.getErrorStatus(thrown)).toBe(401);
139
+
140
+ // Az uj header viszont igen
141
+ await expectAsync(
142
+ svc.verify(mockReq({ 'x-my-admin': TEST_KEY }), mockRes()),
143
+ ).toBeResolved();
144
+ });
145
+
146
+ it('| allowAuthorizationBearer=false letiltja a Bearer fallback-et', async (): Promise<void> => {
147
+ process.env[TEST_ENV_VAR] = TEST_KEY;
148
+ svc.configure({ allowAuthorizationBearer: false });
149
+
150
+ let thrown: any = null;
151
+ try {
152
+ await svc.verify(mockReq({ authorization: `Bearer ${TEST_KEY}` }), mockRes());
153
+ } catch (e) { thrown = e; }
154
+ expect(thrown).not.toBeNull();
155
+ expect(DyFM_Error.getErrorStatus(thrown)).toBe(401);
156
+ });
157
+
158
+ it('| configure() partial override — a tobbi mezo default marad', (): void => {
159
+ svc.configure({ headerName: 'x-only-header' });
160
+ const config = svc.getConfig();
161
+ expect(config.headerName).toBe('x-only-header');
162
+ expect(config.envVarName).toBe('DYNTS_ADMIN_API_KEY');
163
+ expect(config.allowAuthorizationBearer).toBe(true);
164
+ });
165
+
166
+ it('| configure() headerName lowercase-re normalizal', (): void => {
167
+ svc.configure({ headerName: 'X-Mixed-Case-Header' });
168
+ expect(svc.getConfig().headerName).toBe('x-mixed-case-header');
169
+ });
170
+ });
171
+
172
+ describe('| env-read-on-each-call (dynamic)', (): void => {
173
+ it('| env var update kozben hat: install-utani env-modositas ervenyes', async (): Promise<void> => {
174
+ delete process.env[TEST_ENV_VAR];
175
+ // Elso hivas: env hianyzik → 500
176
+ let thrown: any = null;
177
+ try { await svc.verify(mockReq({}), mockRes()); } catch (e) { thrown = e; }
178
+ expect(DyFM_Error.getErrorStatus(thrown)).toBe(500);
179
+
180
+ // Most allitsuk be az env-et
181
+ process.env[TEST_ENV_VAR] = TEST_KEY;
182
+
183
+ // Masodik hivas helyes header-rel → pass
184
+ await expectAsync(
185
+ svc.verify(mockReq({ 'x-admin-api-key': TEST_KEY }), mockRes()),
186
+ ).toBeResolved();
187
+ });
188
+ });
189
+
190
+ describe('| .verify binding', (): void => {
191
+ it('| verify atadhato preProcesses-be detached method-kent (this-binding megtarttva)', async (): Promise<void> => {
192
+ process.env[TEST_ENV_VAR] = TEST_KEY;
193
+ const detached: (req: Request, res: Response) => Promise<void> = svc.verify;
194
+ // NEM call-ban svc.verify(...) hanem szabad fuggveny-kent — bindelt this-re kell hagyatkozni
195
+ await expectAsync(
196
+ detached(mockReq({ 'x-admin-api-key': TEST_KEY }), mockRes()),
197
+ ).toBeResolved();
198
+ });
199
+ });
200
+ });
@@ -0,0 +1,220 @@
1
+ import { Request, Response } from 'express';
2
+ import * as crypto from 'crypto';
3
+
4
+ import { DyFM_Error } from '@futdevpro/fsm-dynamo';
5
+
6
+ import { DyNTS_SingletonServiceBase } from '../../_services/base/singleton.service-base';
7
+ import { DyNTS_global_settings } from '../../_collections/global-settings.const';
8
+
9
+ import { DyNTS_AdminApiKey_Config } from './_models/admin-api-key-config.interface';
10
+
11
+
12
+ /** Default env var nev az admin API key-hez. */
13
+ const DEFAULT_ENV_VAR_NAME: string = 'DYNTS_ADMIN_API_KEY';
14
+
15
+ /** Default header nev (Express lowercase-re normalizalja az osszes header-t). */
16
+ const DEFAULT_HEADER_NAME: string = 'x-admin-api-key';
17
+
18
+ /** Default Bearer fallback engedelyezve van. */
19
+ const DEFAULT_ALLOW_BEARER: boolean = true;
20
+
21
+ /** Service-nev az error-okhoz. */
22
+ const SERVICE_NAME: string = 'DyNTS_AdminApiKey_AuthService';
23
+
24
+ /** ErrorCode prefix — system shortcode + saját kod. */
25
+ const buildErrorCode = (subcode: string): string => {
26
+ const sys: string = DyNTS_global_settings.systemShortCodeName ?? 'DyNTS';
27
+ return `${sys}|DyNTS-AAK-${subcode}`;
28
+ };
29
+
30
+
31
+ /**
32
+ * Admin API key auth service — opt-in HTTP guard a meglevo `DyNTS_Endpoint_Params.preProcesses`
33
+ * mechanizmushoz. Egy env var-ban tarolt fix kulccsal valid-alja a bejovo kerest.
34
+ *
35
+ * **Hasznalat (host app):**
36
+ * ```ts
37
+ * const adminAuth = DyNTS_AdminApiKey_AuthService.getInstance();
38
+ * // opcionalis konfig:
39
+ * // adminAuth.configure({ envVarName: 'MY_KEY', headerName: 'x-my-key' });
40
+ *
41
+ * new DyNTS_Endpoint_Params({
42
+ * ...,
43
+ * preProcesses: [adminAuth.verify, ...other],
44
+ * });
45
+ *
46
+ * // vagy a logs routing module-on at
47
+ * DyNTS_getLogsRoutingModule({ authPreProcess: adminAuth.verify });
48
+ * ```
49
+ *
50
+ * **Viselkedes:**
51
+ * - env var beallitva ES helyes header → silent pass
52
+ * - env var beallitva, header hianyzik / rossz → 401 DyFM_Error
53
+ * - env var NINCS beallitva → 500 DyFM_Error (fail-closed; NEM silent allow)
54
+ *
55
+ * **Header lookup:**
56
+ * 1. `x-admin-api-key` (default canonical header)
57
+ * 2. `Authorization: Bearer <key>` (fallback ha `allowAuthorizationBearer === true`)
58
+ *
59
+ * **Timing-safe:** `crypto.timingSafeEqual` Buffer-konvertalassal. Length-mismatch
60
+ * eseten dummy compare-rel azonos idő, hogy a kulcs-hossz ne szivarogjon ki.
61
+ *
62
+ * **Env var read-on-each-call:** a `verify()` minden hivasnal olvassa az env-et,
63
+ * nem cache-eli. Igy a host az env-et utolagosan is allithatja (pl. config
64
+ * loader az auth.service.install() utan).
65
+ *
66
+ * **Singleton:** `getInstance()`-szel hivd. A `.verify` mezo binding-elve van
67
+ * `this`-re, igy direkt atadhato `preProcesses`-be ujracsomagolas nelkul.
68
+ */
69
+ export class DyNTS_AdminApiKey_AuthService extends DyNTS_SingletonServiceBase {
70
+
71
+ static getInstance(): DyNTS_AdminApiKey_AuthService {
72
+ return DyNTS_AdminApiKey_AuthService.getSingletonInstance() as DyNTS_AdminApiKey_AuthService;
73
+ }
74
+
75
+ private envVarName: string = DEFAULT_ENV_VAR_NAME;
76
+ private headerName: string = DEFAULT_HEADER_NAME;
77
+ private allowAuthorizationBearer: boolean = DEFAULT_ALLOW_BEARER;
78
+
79
+
80
+ /**
81
+ * Konfig override. Hianyzo mezok a default-okat orzik.
82
+ * Hivhato barmikor — a `verify()` a friss config-ot olvassa.
83
+ */
84
+ configure(config: DyNTS_AdminApiKey_Config): void {
85
+ if (config.envVarName !== undefined) {
86
+ this.envVarName = config.envVarName;
87
+ }
88
+ if (config.headerName !== undefined) {
89
+ // Express lowercase-re normalizal — itt is lowercase-eljuk a konzisztenciaert
90
+ this.headerName = config.headerName.toLowerCase();
91
+ }
92
+ if (config.allowAuthorizationBearer !== undefined) {
93
+ this.allowAuthorizationBearer = config.allowAuthorizationBearer;
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Aktualis konfig olvasasa (test/diagnosztika celokra).
99
+ */
100
+ getConfig(): Required<DyNTS_AdminApiKey_Config> {
101
+ return {
102
+ envVarName: this.envVarName,
103
+ headerName: this.headerName,
104
+ allowAuthorizationBearer: this.allowAuthorizationBearer,
105
+ };
106
+ }
107
+
108
+ /**
109
+ * Pre-process function — atadhato `DyNTS_Endpoint_Params.preProcesses`-be,
110
+ * vagy `DyNTS_getLogsRoutingModule({ authPreProcess: ... })`-be.
111
+ *
112
+ * Throws:
113
+ * - 500 ha az env var nincs beallitva (vagy ures string)
114
+ * - 401 ha a header hianyzik vagy nem egyezik
115
+ *
116
+ * A `req`/`res` parametereket NEM modositja (a kerest tovabb engedi a tovabbi
117
+ * preProcess-eknek; csak hiba eseten throw-ol).
118
+ */
119
+ readonly verify = async (req: Request, _res: Response): Promise<void> => {
120
+ const expectedKey: string = process.env[this.envVarName] ?? '';
121
+ if (expectedKey.length === 0) {
122
+ throw new DyFM_Error({
123
+ status: 500,
124
+ errorCode: buildErrorCode('CONFIG'),
125
+ addECToUserMsg: true,
126
+ message: `Admin API key not configured: env var ${this.envVarName} is not set or empty`,
127
+ userMessage: 'Server configuration error',
128
+ issuerService: SERVICE_NAME,
129
+ });
130
+ }
131
+
132
+ const providedKey: string | null = this.extractKeyFromRequest(req);
133
+ if (providedKey === null) {
134
+ throw new DyFM_Error({
135
+ status: 401,
136
+ errorCode: buildErrorCode('MISSING'),
137
+ addECToUserMsg: true,
138
+ message: `Admin API key required (expected header: ${this.headerName})`,
139
+ userMessage: 'Admin API key required',
140
+ issuerService: SERVICE_NAME,
141
+ });
142
+ }
143
+
144
+ if (!this.timingSafeEquals(providedKey, expectedKey)) {
145
+ throw new DyFM_Error({
146
+ status: 401,
147
+ errorCode: buildErrorCode('INVALID'),
148
+ addECToUserMsg: true,
149
+ message: 'Admin API key invalid',
150
+ userMessage: 'Admin API key invalid',
151
+ issuerService: SERVICE_NAME,
152
+ });
153
+ }
154
+
155
+ // Silent pass — return resolved promise
156
+ };
157
+
158
+
159
+ /**
160
+ * Header lookup — elobb a primer header, aztan opcionalisan az Authorization Bearer.
161
+ * Az ures string is "hianyzo"-nak szamit (a Buffer.from('') es timingSafeEqual
162
+ * konzisztencia miatt).
163
+ */
164
+ private extractKeyFromRequest(req: Request): string | null {
165
+ // Primer header
166
+ const primary: unknown = req.headers[this.headerName];
167
+ const primaryStr: string = Array.isArray(primary) ? primary[0] ?? '' : (typeof primary === 'string' ? primary : '');
168
+ if (primaryStr.length > 0) {
169
+ return primaryStr;
170
+ }
171
+
172
+ // Authorization Bearer fallback
173
+ if (this.allowAuthorizationBearer) {
174
+ const authHeader: unknown = req.headers['authorization'];
175
+ const authStr: string = Array.isArray(authHeader) ? authHeader[0] ?? '' : (typeof authHeader === 'string' ? authHeader : '');
176
+ if (authStr.toLowerCase().startsWith('bearer ')) {
177
+ const token: string = authStr.substring(7).trim();
178
+ if (token.length > 0) {
179
+ return token;
180
+ }
181
+ }
182
+ }
183
+
184
+ return null;
185
+ }
186
+
187
+ /**
188
+ * Timing-safe compare ket string kozott. Length-mismatch eseten egy dummy
189
+ * compare-rel azonos idot kenyszeritunk (a kulcs-hossz nem szivaroghat ki
190
+ * timing-attackal).
191
+ *
192
+ * crypto.timingSafeEqual KOTELEZOEN azonos Buffer-hosszt var — kulonbozo
193
+ * hosszra throw-ol, ezert vizsgaljuk elobb a length-et es csak utana
194
+ * compare-elunk.
195
+ */
196
+ private timingSafeEquals(a: string, b: string): boolean {
197
+ const aBuf: Buffer = Buffer.from(a, 'utf-8');
198
+ const bBuf: Buffer = Buffer.from(b, 'utf-8');
199
+
200
+ if (aBuf.length !== bBuf.length) {
201
+ // Dummy compare ugyanazzal a string-gel: konstans ideju mukodest biztosit
202
+ // mielott visszaternenk false-szal — igy a length-mismatch nem szivaroghat ki.
203
+ crypto.timingSafeEqual(bBuf, bBuf);
204
+ return false;
205
+ }
206
+
207
+ return crypto.timingSafeEqual(aBuf, bBuf);
208
+ }
209
+
210
+
211
+ /**
212
+ * Test-only: visszaallitja a default config-ot, hogy a specfajlok ne szivarogjak
213
+ * at egymas state-jet. Production code NE hivja.
214
+ */
215
+ _resetForTesting(): void {
216
+ this.envVarName = DEFAULT_ENV_VAR_NAME;
217
+ this.headerName = DEFAULT_HEADER_NAME;
218
+ this.allowAuthorizationBearer = DEFAULT_ALLOW_BEARER;
219
+ }
220
+ }
@@ -0,0 +1,2 @@
1
+ export { DyNTS_AdminApiKey_AuthService } from './admin-api-key.auth-service';
2
+ export { DyNTS_AdminApiKey_Config } from './_models/admin-api-key-config.interface';