@futdevpro/nts-dynamo 1.15.122 → 1.15.126

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 (35) hide show
  1. package/.dynamo/logs/cicd-pipeline/output.log +1896 -1880
  2. package/.dynamo/logs/cicd-pipeline/status.json +39 -39
  3. package/build/_collections/signed-url.util.d.ts +85 -0
  4. package/build/_collections/signed-url.util.d.ts.map +1 -0
  5. package/build/_collections/signed-url.util.js +146 -0
  6. package/build/_collections/signed-url.util.js.map +1 -0
  7. package/build/_models/interfaces/signed-url-verify-result.interface.d.ts +14 -0
  8. package/build/_models/interfaces/signed-url-verify-result.interface.d.ts.map +1 -0
  9. package/build/_models/interfaces/signed-url-verify-result.interface.js +3 -0
  10. package/build/_models/interfaces/signed-url-verify-result.interface.js.map +1 -0
  11. package/build/index.d.ts +2 -0
  12. package/build/index.d.ts.map +1 -1
  13. package/build/index.js +2 -0
  14. package/build/index.js.map +1 -1
  15. package/package.json +1 -10
  16. package/src/_collections/signed-url.util.spec.ts +198 -0
  17. package/src/_collections/signed-url.util.ts +170 -0
  18. package/src/_models/interfaces/signed-url-verify-result.interface.ts +13 -0
  19. package/src/index.ts +2 -0
  20. package/build/_modules/ftp/_collections/ftp-url-signer.util.d.ts +0 -37
  21. package/build/_modules/ftp/_collections/ftp-url-signer.util.d.ts.map +0 -1
  22. package/build/_modules/ftp/_collections/ftp-url-signer.util.js +0 -57
  23. package/build/_modules/ftp/_collections/ftp-url-signer.util.js.map +0 -1
  24. package/build/_modules/ftp/_services/ftp-service.api-service.d.ts +0 -63
  25. package/build/_modules/ftp/_services/ftp-service.api-service.d.ts.map +0 -1
  26. package/build/_modules/ftp/_services/ftp-service.api-service.js +0 -84
  27. package/build/_modules/ftp/_services/ftp-service.api-service.js.map +0 -1
  28. package/build/_modules/ftp/index.d.ts +0 -3
  29. package/build/_modules/ftp/index.d.ts.map +0 -1
  30. package/build/_modules/ftp/index.js +0 -8
  31. package/build/_modules/ftp/index.js.map +0 -1
  32. package/src/_modules/ftp/_collections/ftp-url-signer.util.spec.ts +0 -44
  33. package/src/_modules/ftp/_collections/ftp-url-signer.util.ts +0 -60
  34. package/src/_modules/ftp/_services/ftp-service.api-service.ts +0 -120
  35. package/src/_modules/ftp/index.ts +0 -7
@@ -0,0 +1,198 @@
1
+ import { DyFM_Error } from '@futdevpro/fsm-dynamo';
2
+
3
+ import { DyNTS_SignedUrl_Util } from './signed-url.util';
4
+
5
+ /**
6
+ * ALÁÍRT-URL primitív tesztjei (AAA).
7
+ *
8
+ * ⭐ **Az ARANY-VEKTOR az első teszt, és ez a legfontosabb:** a hex értéket a ma ÉLŐ algoritmusból
9
+ * generáltuk *(az `fdp-ftp-service` `FtpUrlSignature_Util` verify- és a `master-prompter`
10
+ * `FtpUrlSigner_Util` sign-oldala — bit-azonosak)*. Ha ez a teszt elpirosodik, az azt jelenti,
11
+ * hogy a bedrock util **elcsúszott** az élő rendszertől, és a **már kiadott URL-ek elromlanának**.
12
+ *
13
+ * ⚠️ `nowMs` **mindenhol injektálva** — a tesztben nincs `Date.now()`, tehát nincs flake.
14
+ */
15
+ describe('| DyNTS_SignedUrl_Util', () => {
16
+ const SECRET: string = 'golden-vector-secret';
17
+ const BACKSLASH: string = String.fromCharCode(92);
18
+
19
+ // --- ARANY-VEKTOR (az élő algoritmusból generálva) ---
20
+ const GOLDEN_PATH: string = '/accounts/acc-1/invoices/INV-2026-001.pdf';
21
+ const GOLDEN_EXP: number = 1767225600000;
22
+ const GOLDEN_HEX: string = 'a6c2d1ddf0fe4614e3b6130d4e22a9ef085377f73088d3f79aab548e8056c165';
23
+
24
+ it('1 | ARANY-VEKTOR — a mai algoritmus hexét adja (bitre)', () => {
25
+ const signature: string = DyNTS_SignedUrl_Util.sign(GOLDEN_PATH, GOLDEN_EXP, SECRET);
26
+
27
+ expect(signature).toBe(GOLDEN_HEX);
28
+ });
29
+
30
+ it('2 | normalizePath — vezető /-ek le, backslash → slash', () => {
31
+ expect(DyNTS_SignedUrl_Util.normalizePath('///a' + BACKSLASH + 'b/c')).toBe('a/b/c');
32
+ });
33
+
34
+ it('3 | buildCanonicalString — `<path>\\n<exp>`', () => {
35
+ expect(DyNTS_SignedUrl_Util.buildCanonicalString('a/b', 123)).toBe('a/b\n123');
36
+ });
37
+
38
+ it('4 | sign determinisztikus — kétszeri hívás azonos', () => {
39
+ const first: string = DyNTS_SignedUrl_Util.sign(GOLDEN_PATH, GOLDEN_EXP, SECRET);
40
+ const second: string = DyNTS_SignedUrl_Util.sign(GOLDEN_PATH, GOLDEN_EXP, SECRET);
41
+
42
+ expect(first).toBe(second);
43
+ });
44
+
45
+ it('5 | verify — érvényes aláírásra { valid: true }', () => {
46
+ const result = DyNTS_SignedUrl_Util.verify({
47
+ filePath: GOLDEN_PATH, expMs: GOLDEN_EXP, signature: GOLDEN_HEX,
48
+ secret: SECRET, nowMs: GOLDEN_EXP - 1000,
49
+ });
50
+
51
+ expect(result.valid).toBeTrue();
52
+ expect(result.reason).toBeUndefined();
53
+ });
54
+
55
+ it('6 | verify — LEJÁRT aláírásra { valid: false, reason: expired }', () => {
56
+ const result = DyNTS_SignedUrl_Util.verify({
57
+ filePath: GOLDEN_PATH, expMs: GOLDEN_EXP, signature: GOLDEN_HEX,
58
+ secret: SECRET, nowMs: GOLDEN_EXP + 1,
59
+ });
60
+
61
+ expect(result.valid).toBeFalse();
62
+ expect(result.reason).toBe('expired');
63
+ });
64
+
65
+ it('7 | verify — HAMIS aláírásra { valid: false, reason: signature-mismatch }', () => {
66
+ // ugyanolyan HOSSZÚ, de más tartalmú hex — a timing-safe ág fut le, nem a hossz-guard
67
+ const forged: string = 'b'.repeat(GOLDEN_HEX.length);
68
+
69
+ const result = DyNTS_SignedUrl_Util.verify({
70
+ filePath: GOLDEN_PATH, expMs: GOLDEN_EXP, signature: forged,
71
+ secret: SECRET, nowMs: GOLDEN_EXP - 1000,
72
+ });
73
+
74
+ expect(result.valid).toBeFalse();
75
+ expect(result.reason).toBe('signature-mismatch');
76
+ });
77
+
78
+ it('8 | verify — ELTÉRŐ HOSSZÚ aláírásra NEM dob (timingSafeEqual guard)', () => {
79
+ const shortSignature: string = 'abc';
80
+
81
+ // Act + Assert: a lényeg, hogy NEM dob — a `timingSafeEqual` eltérő hosszra dobna.
82
+ expect((): void => {
83
+ const result = DyNTS_SignedUrl_Util.verify({
84
+ filePath: GOLDEN_PATH, expMs: GOLDEN_EXP, signature: shortSignature,
85
+ secret: SECRET, nowMs: GOLDEN_EXP - 1000,
86
+ });
87
+
88
+ expect(result.valid).toBeFalse();
89
+ }).not.toThrow();
90
+ });
91
+
92
+ it('9 | verify — hiányzó exp/sig esetén { valid: false, reason: missing-params }', () => {
93
+ const noSignature = DyNTS_SignedUrl_Util.verify({
94
+ filePath: GOLDEN_PATH, expMs: GOLDEN_EXP, signature: undefined,
95
+ secret: SECRET, nowMs: GOLDEN_EXP - 1000,
96
+ });
97
+ const noExp = DyNTS_SignedUrl_Util.verify({
98
+ filePath: GOLDEN_PATH, expMs: undefined, signature: GOLDEN_HEX,
99
+ secret: SECRET, nowMs: GOLDEN_EXP - 1000,
100
+ });
101
+
102
+ expect(noSignature.reason).toBe('missing-params');
103
+ expect(noExp.reason).toBe('missing-params');
104
+ });
105
+
106
+ it('10 | verify — ÜRES secrettel SOHA nem true', () => {
107
+ const result = DyNTS_SignedUrl_Util.verify({
108
+ filePath: GOLDEN_PATH, expMs: GOLDEN_EXP, signature: GOLDEN_HEX,
109
+ secret: '', nowMs: GOLDEN_EXP - 1000,
110
+ });
111
+
112
+ expect(result.valid).toBeFalse();
113
+ expect(result.reason).toBe('missing-secret');
114
+ });
115
+
116
+ it('11 | sign — ÜRES secretre DOB (DyNTS-SURL-SIGN-NOSEC), nem ad csendben üres aláírást', () => {
117
+ let caught: DyFM_Error | undefined;
118
+
119
+ try {
120
+ DyNTS_SignedUrl_Util.sign(GOLDEN_PATH, GOLDEN_EXP, '');
121
+ } catch (error) {
122
+ caught = error as DyFM_Error;
123
+ }
124
+
125
+ expect(caught).toBeDefined();
126
+ expect(caught?._errorCode).toBe('DyNTS-SURL-SIGN-NOSEC');
127
+ });
128
+
129
+ it('12 | buildSignedUrl — base végi /-ek levágva, query helyes', () => {
130
+ const url: string = DyNTS_SignedUrl_Util.buildSignedUrl({
131
+ baseUrl: 'https://ftp.example.com///',
132
+ filePath: '/accounts/acc-1/f.pdf',
133
+ ttlMs: 60_000,
134
+ secret: SECRET,
135
+ nowMs: 1_000_000,
136
+ });
137
+
138
+ const expectedSignature: string =
139
+ DyNTS_SignedUrl_Util.sign('accounts/acc-1/f.pdf', 1_060_000, SECRET);
140
+ expect(url).toBe(
141
+ `https://ftp.example.com/accounts/acc-1/f.pdf?exp=1060000&sig=${expectedSignature}`,
142
+ );
143
+ });
144
+
145
+ it('13 | PATH-MANIPULÁCIÓ — aláírt path ≠ kért path → érvénytelen', () => {
146
+ const signature: string = DyNTS_SignedUrl_Util.sign('accounts/acc-1/f.pdf', GOLDEN_EXP, SECRET);
147
+
148
+ const result = DyNTS_SignedUrl_Util.verify({
149
+ filePath: 'accounts/acc-2/f.pdf', // MÁSIK account
150
+ expMs: GOLDEN_EXP, signature, secret: SECRET, nowMs: GOLDEN_EXP - 1000,
151
+ });
152
+
153
+ expect(result.valid).toBeFalse();
154
+ expect(result.reason).toBe('signature-mismatch');
155
+ });
156
+
157
+ it('14 | nowMs injektálás — determinisztikus lejárat-határ (nincs Date.now a tesztben)', () => {
158
+ const atBoundary = DyNTS_SignedUrl_Util.verify({
159
+ filePath: GOLDEN_PATH, expMs: GOLDEN_EXP, signature: GOLDEN_HEX,
160
+ secret: SECRET, nowMs: GOLDEN_EXP, // exp === now → MÉG érvényes
161
+ });
162
+ const afterBoundary = DyNTS_SignedUrl_Util.verify({
163
+ filePath: GOLDEN_PATH, expMs: GOLDEN_EXP, signature: GOLDEN_HEX,
164
+ secret: SECRET, nowMs: GOLDEN_EXP + 1, // egy ms-mal később → lejárt
165
+ });
166
+
167
+ expect(atBoundary.valid).toBeTrue();
168
+ expect(afterBoundary.reason).toBe('expired');
169
+ });
170
+
171
+ it('15 | buildSignedUrl — ÜRES/hibás baseUrl-re DOB (DyNTS-SURL-URL-BADBASE), nem épít félkész URL-t', () => {
172
+ let caught: DyFM_Error | undefined;
173
+
174
+ try {
175
+ DyNTS_SignedUrl_Util.buildSignedUrl({
176
+ baseUrl: ' ', filePath: 'a/b.pdf', ttlMs: 1000, secret: SECRET, nowMs: 1,
177
+ });
178
+ } catch (error) {
179
+ caught = error as DyFM_Error;
180
+ }
181
+
182
+ expect(caught).toBeDefined();
183
+ expect(caught?._errorCode).toBe('DyNTS-SURL-URL-BADBASE');
184
+ });
185
+
186
+ it('16 | sign — nem-pozitív/nem-szám expMs-re DOB (DyNTS-SURL-SIGN-BADEXP)', () => {
187
+ let caught: DyFM_Error | undefined;
188
+
189
+ try {
190
+ DyNTS_SignedUrl_Util.sign(GOLDEN_PATH, Number.NaN, SECRET);
191
+ } catch (error) {
192
+ caught = error as DyFM_Error;
193
+ }
194
+
195
+ expect(caught).toBeDefined();
196
+ expect(caught?._errorCode).toBe('DyNTS-SURL-SIGN-BADEXP');
197
+ });
198
+ });
@@ -0,0 +1,170 @@
1
+ import * as Crypto from 'crypto';
2
+
3
+ import { DyFM_Error } from '@futdevpro/fsm-dynamo';
4
+
5
+ import { DyNTS_SignedUrl_VerifyResult } from '../_models/interfaces/signed-url-verify-result.interface';
6
+
7
+ /**
8
+ * ALÁÍRT-URL primitív (HMAC-SHA256) — a böngésző-kiszolgálás (`<img src>`, nincs Bearer)
9
+ * auth-mechanizmusának generikus magja.
10
+ *
11
+ * A forrás-rendszer (ahol megvan a user auth-kontextusa) aláírja a `path + exp` kanonikus
12
+ * stringet a MEGOSZTOTT titokkal, és egy `…/<path>?exp=<ms>&sig=<hex>` URL-t ad ki; a kiszolgáló
13
+ * oldal ugyanezzel a titokkal validál. Az **aláírás maga a tamper-proof izoláció**: a path
14
+ * bármilyen módosítása érvényteleníti.
15
+ *
16
+ * ⚠️ **A SZERZŐDÉS BITRE KÖTÖTT — nem tervezési szabadság.** Ez az util a ma ÉLŐ algoritmust
17
+ * viszi tovább (az `fdp-ftp-service` verify- és a `master-prompter` sign-oldaláról mérve),
18
+ * hogy a **már kiadott URL-ek** ne romoljanak el:
19
+ *
20
+ * | Elem | Érték |
21
+ * |---|---|
22
+ * | Algoritmus | `HMAC-SHA256(secret, canonicalString)` → **hex** |
23
+ * | Kanonikus string | `` `<normalizedPath>\n<expMs>` `` — csak path + lejárat |
24
+ * | `normalizePath` | vezető `/`-ek le (`/^\/+/`), `\` → `/` |
25
+ * | Query | `?exp=<ms>&sig=<hex>` |
26
+ * | Összevetés | `Crypto.timingSafeEqual`, **előtte hossz-guard** |
27
+ * | Lejárat | `expMs < now` → érvénytelen; **nincs** beépített max-TTL (a hívó dönt) |
28
+ *
29
+ * **Miért `dynamo-nts` és nem `fsm-dynamo`:** node-`crypto` kell (`createHmac` +
30
+ * **`timingSafeEqual`**); izomorf rétegben a timing-safe összevetés elveszne, és a sign/verify
31
+ * két csomagba vágása pont azt a bitre-egyezést veszélyeztetné, amiért ez az util készül.
32
+ *
33
+ * **Két tudatos eltérés a mai duplikátumoktól:**
34
+ * 1. `sign`/`buildSignedUrl` üres secretre **DOB** — a mai forrás-oldal üres stringet ad vissza,
35
+ * ami **csendes hiba**: a hívó észrevétlenül aláírás nélküli URL-t kaphat. A bedrock **hangos**;
36
+ * a fallback-döntés a hívóé, de **tudatosan**.
37
+ * 2. `verify` **SOHA nem dob** — strukturált `{ valid, reason }`-t ad, hogy a gate-ek egységes,
38
+ * szivárgás-mentes választ tudjanak adni.
39
+ */
40
+ export class DyNTS_SignedUrl_Util {
41
+
42
+ /**
43
+ * Path normalizálás az aláíráshoz/validáláshoz: vezető `/`-ek le, `\` → `/`.
44
+ * A kanonikus string első fele — a két oldalnak **bit-azonosan** kell normalizálnia.
45
+ */
46
+ static normalizePath(filePath: string): string {
47
+ return (filePath ?? '').replace(/^\/+/, '').replace(/\\/g, '/');
48
+ }
49
+
50
+ /**
51
+ * A HMAC bemenete: `` `<normalizedPath>\n<expMs>` ``.
52
+ * **Publikus**, hogy tesztelhető és auditálható legyen (az arany-vektor erre épül).
53
+ */
54
+ static buildCanonicalString(filePath: string, expMs: number): string {
55
+ return `${DyNTS_SignedUrl_Util.normalizePath(filePath)}\n${expMs}`;
56
+ }
57
+
58
+ /**
59
+ * HMAC-SHA256 hex digest a kanonikus stringen.
60
+ *
61
+ * @throws `DyNTS-SURL-SIGN-NOSEC` — üres secret *(⚠️ NEM ad csendben üres aláírást)*
62
+ * @throws `DyNTS-SURL-SIGN-BADEXP` — nem-szám / nem-pozitív `expMs`
63
+ */
64
+ static sign(filePath: string, expMs: number, secret: string): string {
65
+ DyNTS_SignedUrl_Util._assertSecret(secret, filePath);
66
+ DyNTS_SignedUrl_Util._assertExp(expMs, filePath);
67
+
68
+ return Crypto
69
+ .createHmac('sha256', secret)
70
+ .update(DyNTS_SignedUrl_Util.buildCanonicalString(filePath, expMs), 'utf8')
71
+ .digest('hex');
72
+ }
73
+
74
+ /**
75
+ * Timing-safe aláírás-ellenőrzés + lejárat. ⚠️ **SOHA nem dob** — a hívó gate-nek egységes,
76
+ * szivárgás-mentes választ kell tudnia adni.
77
+ *
78
+ * `nowMs` injektálható → determinisztikus teszt (nincs `Date.now()` a tesztben).
79
+ */
80
+ static verify(params: {
81
+ filePath: string;
82
+ expMs: number | undefined;
83
+ signature: string | undefined;
84
+ secret: string;
85
+ nowMs?: number;
86
+ }): DyNTS_SignedUrl_VerifyResult {
87
+ if (!params.secret) {
88
+ return { valid: false, reason: 'missing-secret' };
89
+ }
90
+ if (!params.signature || typeof params.expMs !== 'number' || !Number.isFinite(params.expMs)) {
91
+ return { valid: false, reason: 'missing-params' };
92
+ }
93
+
94
+ const now: number = typeof params.nowMs === 'number' ? params.nowMs : Date.now();
95
+ if (params.expMs < now) {
96
+ return { valid: false, reason: 'expired' };
97
+ }
98
+
99
+ const expected: string = DyNTS_SignedUrl_Util.sign(params.filePath, params.expMs, params.secret);
100
+
101
+ // ⚠️ A `timingSafeEqual` ELTÉRŐ HOSSZÚ bufferre DOB — ezért kell a hossz-guard.
102
+ // A hossz nem titok: a hex digest hossza fix.
103
+ const signatureBuffer: Buffer = Buffer.from(params.signature, 'utf8');
104
+ const expectedBuffer: Buffer = Buffer.from(expected, 'utf8');
105
+ if (signatureBuffer.length !== expectedBuffer.length) {
106
+ return { valid: false, reason: 'signature-mismatch' };
107
+ }
108
+ if (!Crypto.timingSafeEqual(signatureBuffer, expectedBuffer)) {
109
+ return { valid: false, reason: 'signature-mismatch' };
110
+ }
111
+
112
+ return { valid: true };
113
+ }
114
+
115
+ /**
116
+ * Teljes aláírt URL: `` `<base>/<normalizedPath>?exp=<ms>&sig=<hex>` `` — a `base` végi
117
+ * `/`-ei levágva.
118
+ *
119
+ * @throws `DyNTS-SURL-URL-BADBASE` — üres/hibás `baseUrl` *(⚠️ NEM épít félkész URL-t)*
120
+ * @throws `DyNTS-SURL-SIGN-NOSEC` — üres secret
121
+ */
122
+ static buildSignedUrl(params: {
123
+ baseUrl: string;
124
+ filePath: string;
125
+ ttlMs: number;
126
+ secret: string;
127
+ nowMs?: number;
128
+ }): string {
129
+ const base: string = (params.baseUrl ?? '').trim().replace(/\/+$/, '');
130
+ if (!base) {
131
+ throw new DyFM_Error({
132
+ status: 500,
133
+ errorCode: 'DyNTS-SURL-URL-BADBASE',
134
+ message: 'Signed URL: "baseUrl" is empty or invalid — refusing to build a half-formed URL.',
135
+ additionalContent: { filePath: params.filePath },
136
+ });
137
+ }
138
+
139
+ const now: number = typeof params.nowMs === 'number' ? params.nowMs : Date.now();
140
+ const expMs: number = now + params.ttlMs;
141
+ const normalizedPath: string = DyNTS_SignedUrl_Util.normalizePath(params.filePath);
142
+ const signature: string = DyNTS_SignedUrl_Util.sign(normalizedPath, expMs, params.secret);
143
+
144
+ return `${base}/${normalizedPath}?exp=${expMs}&sig=${signature}`;
145
+ }
146
+
147
+ /** Üres secret → hangos hiba. ⚠️ A titok SOHA nem kerül a hiba-kontextusba. */
148
+ private static _assertSecret(secret: string, filePath: string): void {
149
+ if (!secret) {
150
+ throw new DyFM_Error({
151
+ status: 500,
152
+ errorCode: 'DyNTS-SURL-SIGN-NOSEC',
153
+ message: 'Signed URL: signing secret is not configured — refusing to sign silently.',
154
+ additionalContent: { filePath: DyNTS_SignedUrl_Util.normalizePath(filePath) },
155
+ });
156
+ }
157
+ }
158
+
159
+ /** Nem-szám / nem-pozitív lejárat → hangos hiba (a csendes `NaN`-os aláírás rosszabb). */
160
+ private static _assertExp(expMs: number, filePath: string): void {
161
+ if (typeof expMs !== 'number' || !Number.isFinite(expMs) || expMs <= 0) {
162
+ throw new DyFM_Error({
163
+ status: 500,
164
+ errorCode: 'DyNTS-SURL-SIGN-BADEXP',
165
+ message: 'Signed URL: "expMs" must be a finite positive number (epoch millis).',
166
+ additionalContent: { filePath: DyNTS_SignedUrl_Util.normalizePath(filePath), expMs: `${expMs}` },
167
+ });
168
+ }
169
+ }
170
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Az aláírt-URL ellenőrzés (`DyNTS_SignedUrl_Util.verify`) strukturált eredménye.
3
+ *
4
+ * ⚠️ A `reason` **logba** való — a HTTP-válaszba **NEM** szivárogtatható: az elutasítás oka
5
+ * támadónak információ *(pl. „expired" vs. „signature-mismatch" megkülönböztetése)*.
6
+ *
7
+ * ⭐ **Miért strukturált eredmény és nem kivétel:** a `verify` SOHA nem dob, hogy a hívó gate-ek
8
+ * egységes, szivárgás-mentes választ tudjanak adni — a `reason` a **belső** diagnosztika.
9
+ */
10
+ export interface DyNTS_SignedUrl_VerifyResult {
11
+ valid: boolean;
12
+ reason?: 'missing-secret' | 'missing-params' | 'expired' | 'signature-mismatch';
13
+ }
package/src/index.ts CHANGED
@@ -49,12 +49,14 @@ export * from './_models/interfaces/environment-settings.interface';
49
49
  export * from './_models/interfaces/global-service-settings.interface';
50
50
  export * from './_models/interfaces/global-settings.interface';
51
51
  export * from './_models/interfaces/routing-module-settings.interface';
52
+ export * from './_models/interfaces/signed-url-verify-result.interface';
52
53
  export * from './_models/interfaces/static-client-settings.interface';
53
54
  export * from './_models/interfaces/cors-settings.interface';
54
55
  export * from './_models/interfaces/security-headers-settings.interface';
55
56
  export * from './_models/interfaces/db-query-options.interface';
56
57
  export * from './_models/interfaces/migration-entry.interface';
57
58
  export * from './_collections/security-headers.util';
59
+ export * from './_collections/signed-url.util';
58
60
  export * from './_collections/client-safe-error.util';
59
61
  export * from './_collections/load-shed.util';
60
62
  export * from './_services/core/event-loop-diag.service';
@@ -1,37 +0,0 @@
1
- /**
2
- * FTP signed-URL ALÁÍRÓ (deep/generikus — `@futdevpro/nts-dynamo`).
3
- *
4
- * A böngésző-serve (`<img src>` / letöltő-link) nem tud Bearer-headert küldeni, ezért az
5
- * `fdp-ftp-service` HTTP-kiszolgálása rövid-/közép-életű **aláírt URL**-lel auth-ol. Ez az util a
6
- * megosztott titokkal HMAC-SHA256-tal aláírja a `path + exp`-et, és előállítja a serve-URL-t.
7
- *
8
- * ⚠️ Az algoritmusnak **BIT-RE egyeznie kell** az `fdp-ftp-service` `FtpUrlSignature_Util`-jával
9
- * (kanonikus string: `<normalizedPath>\n<expMs>`; normalizálás: leading-slash le + `\`→`/`), különben
10
- * az ftp elutasítja a signature-t.
11
- *
12
- * **PURE (ADR-004):** ez a modul NEM importál `fdp-templates`-et — a `secret`/`serveBaseUrl` a hívó-
13
- * (consumer-) oldalról jön paraméterként. Így minden FDP-server újrahasználhatja, egyetlen példányban.
14
- */
15
- export declare class DyNTS_FtpUrlSigner_Util {
16
- /** Path normalizálás — pontosan az ftp-serve normalizálása (leading-slash le, `\`→`/`). */
17
- static normalizePath(filePath: string): string;
18
- /**
19
- * Kanonikus UPLOAD-cél string: `accountId/folderPath/fileName`. A szerver-szerver (app→ftp) upload
20
- * ezt írja alá → az ftp ugyanígy építi + validálja. Bit-azonos kell legyen a két oldalon.
21
- */
22
- static uploadTarget(accountId: string, folderPath: string, fileName: string): string;
23
- /** HMAC-SHA256 a `<normalizedPath>\n<expMs>`-en → hex (az ftp ezt validálja). */
24
- static sign(filePath: string, expMs: number, secret: string): string;
25
- /**
26
- * Teljes aláírt serve-URL egy ftp-path-ból: `<serveBaseUrl>/<normalizedPath>?exp=<ms>&sig=<hex>`.
27
- * Üres `secret` → üres string (a gate inaktív; a hívó dönt a fallback-ről). `nowMs` injektálható teszthez.
28
- */
29
- static buildSignedServeUrl(params: {
30
- serveBaseUrl: string;
31
- ftpPath: string;
32
- ttlMs: number;
33
- secret: string;
34
- nowMs?: number;
35
- }): string;
36
- }
37
- //# sourceMappingURL=ftp-url-signer.util.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ftp-url-signer.util.d.ts","sourceRoot":"","sources":["../../../../src/_modules/ftp/_collections/ftp-url-signer.util.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;GAaG;AACH,qBAAa,uBAAuB;IAElC,2FAA2F;IAC3F,MAAM,CAAC,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM;IAI9C;;;OAGG;IACH,MAAM,CAAC,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM;IAIpF,iFAAiF;IACjF,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM;IAQpE;;;OAGG;IACH,MAAM,CAAC,mBAAmB,CAAC,MAAM,EAAE;QACjC,YAAY,EAAE,MAAM,CAAC;QACrB,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,GAAG,MAAM;CASX"}
@@ -1,57 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DyNTS_FtpUrlSigner_Util = void 0;
4
- const tslib_1 = require("tslib");
5
- const Crypto = tslib_1.__importStar(require("crypto"));
6
- /**
7
- * FTP signed-URL ALÁÍRÓ (deep/generikus — `@futdevpro/nts-dynamo`).
8
- *
9
- * A böngésző-serve (`<img src>` / letöltő-link) nem tud Bearer-headert küldeni, ezért az
10
- * `fdp-ftp-service` HTTP-kiszolgálása rövid-/közép-életű **aláírt URL**-lel auth-ol. Ez az util a
11
- * megosztott titokkal HMAC-SHA256-tal aláírja a `path + exp`-et, és előállítja a serve-URL-t.
12
- *
13
- * ⚠️ Az algoritmusnak **BIT-RE egyeznie kell** az `fdp-ftp-service` `FtpUrlSignature_Util`-jával
14
- * (kanonikus string: `<normalizedPath>\n<expMs>`; normalizálás: leading-slash le + `\`→`/`), különben
15
- * az ftp elutasítja a signature-t.
16
- *
17
- * **PURE (ADR-004):** ez a modul NEM importál `fdp-templates`-et — a `secret`/`serveBaseUrl` a hívó-
18
- * (consumer-) oldalról jön paraméterként. Így minden FDP-server újrahasználhatja, egyetlen példányban.
19
- */
20
- class DyNTS_FtpUrlSigner_Util {
21
- /** Path normalizálás — pontosan az ftp-serve normalizálása (leading-slash le, `\`→`/`). */
22
- static normalizePath(filePath) {
23
- return filePath.replace(/^\/+/, '').replace(/\\/g, '/');
24
- }
25
- /**
26
- * Kanonikus UPLOAD-cél string: `accountId/folderPath/fileName`. A szerver-szerver (app→ftp) upload
27
- * ezt írja alá → az ftp ugyanígy építi + validálja. Bit-azonos kell legyen a két oldalon.
28
- */
29
- static uploadTarget(accountId, folderPath, fileName) {
30
- return `${accountId}/${folderPath}/${fileName}`;
31
- }
32
- /** HMAC-SHA256 a `<normalizedPath>\n<expMs>`-en → hex (az ftp ezt validálja). */
33
- static sign(filePath, expMs, secret) {
34
- const normalizedPath = DyNTS_FtpUrlSigner_Util.normalizePath(filePath);
35
- return Crypto
36
- .createHmac('sha256', secret)
37
- .update(`${normalizedPath}\n${expMs}`, 'utf8')
38
- .digest('hex');
39
- }
40
- /**
41
- * Teljes aláírt serve-URL egy ftp-path-ból: `<serveBaseUrl>/<normalizedPath>?exp=<ms>&sig=<hex>`.
42
- * Üres `secret` → üres string (a gate inaktív; a hívó dönt a fallback-ről). `nowMs` injektálható teszthez.
43
- */
44
- static buildSignedServeUrl(params) {
45
- if (!params.secret) {
46
- return '';
47
- }
48
- const now = typeof params.nowMs === 'number' ? params.nowMs : Date.now();
49
- const expMs = now + params.ttlMs;
50
- const normalizedPath = DyNTS_FtpUrlSigner_Util.normalizePath(params.ftpPath);
51
- const sig = DyNTS_FtpUrlSigner_Util.sign(normalizedPath, expMs, params.secret);
52
- const base = params.serveBaseUrl.replace(/\/+$/, '');
53
- return `${base}/${normalizedPath}?exp=${expMs}&sig=${sig}`;
54
- }
55
- }
56
- exports.DyNTS_FtpUrlSigner_Util = DyNTS_FtpUrlSigner_Util;
57
- //# sourceMappingURL=ftp-url-signer.util.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ftp-url-signer.util.js","sourceRoot":"","sources":["../../../../src/_modules/ftp/_collections/ftp-url-signer.util.ts"],"names":[],"mappings":";;;;AAAA,uDAAiC;AAEjC;;;;;;;;;;;;;GAaG;AACH,MAAa,uBAAuB;IAElC,2FAA2F;IAC3F,MAAM,CAAC,aAAa,CAAC,QAAgB;QACnC,OAAO,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IAC1D,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,YAAY,CAAC,SAAiB,EAAE,UAAkB,EAAE,QAAgB;QACzE,OAAO,GAAG,SAAS,IAAI,UAAU,IAAI,QAAQ,EAAE,CAAC;IAClD,CAAC;IAED,iFAAiF;IACjF,MAAM,CAAC,IAAI,CAAC,QAAgB,EAAE,KAAa,EAAE,MAAc;QACzD,MAAM,cAAc,GAAW,uBAAuB,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QAC/E,OAAO,MAAM;aACV,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC;aAC5B,MAAM,CAAC,GAAG,cAAc,KAAK,KAAK,EAAE,EAAE,MAAM,CAAC;aAC7C,MAAM,CAAC,KAAK,CAAC,CAAC;IACnB,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,mBAAmB,CAAC,MAM1B;QACC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;YAAC,OAAO,EAAE,CAAC;QAAC,CAAC;QAClC,MAAM,GAAG,GAAW,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;QACjF,MAAM,KAAK,GAAW,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC;QACzC,MAAM,cAAc,GAAW,uBAAuB,CAAC,aAAa,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACrF,MAAM,GAAG,GAAW,uBAAuB,CAAC,IAAI,CAAC,cAAc,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;QACvF,MAAM,IAAI,GAAW,MAAM,CAAC,YAAY,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QAC7D,OAAO,GAAG,IAAI,IAAI,cAAc,QAAQ,KAAK,QAAQ,GAAG,EAAE,CAAC;IAC7D,CAAC;CACF;AA3CD,0DA2CC"}
@@ -1,63 +0,0 @@
1
- /**
2
- * FTP-kliens KONFIG (a consumer adja át — PURE: nincs fdp-templates-függés).
3
- * A `fdp-ftp-service` URL-jei + a megosztott aláíró-titok a hívó-oldalról jönnek.
4
- */
5
- export interface DyNTS_FtpService_Config {
6
- /** az `fdp-ftp-service` API base-URL a szerver-szerver feltöltéshez (pl. `FDP_ftpServiceApiEnv_settings.baseUrl.fromBE`). */
7
- apiBaseUrl: string;
8
- /** a publikus serve-host az aláírt letöltő-URL-ekhez (pl. `FDP_ftpServiceApiEnv_settings.baseUrl.siteAddress`). */
9
- serveBaseUrl: string;
10
- /** a megosztott HMAC-titok (env-var `FTP_URL_SIGNING_SECRET`, MÁR létező). Üres → a serve-gate inaktív. */
11
- signingSecret: string;
12
- /** az upload-endpoint (default `/files/upload`). */
13
- uploadFileEndpoint?: string;
14
- }
15
- /** FTP-feltöltés bemenete. */
16
- export interface DyNTS_FtpService_UploadFile_Input {
17
- accountId: string;
18
- folderPath: string;
19
- fileName: string;
20
- fileBuffer: Buffer;
21
- }
22
- /** FTP-feltöltés válasza. */
23
- export interface DyNTS_FtpService_UploadFile_Response {
24
- filePath: string;
25
- ftpPath: string;
26
- }
27
- /**
28
- * FTP Service API kliens (deep/generikus — `@futdevpro/nts-dynamo`).
29
- * @description A szerver-oldali FTP-KLIENS: a meglévő `fdp-ftp-service` tároló-szervert HÍVJA — fájlt tölt
30
- * fel (signed upload), és aláírt serve-URL-t épít. **NEM tartalmaz FTP-szervert.** A korábban az
31
- * MP-be / token-service-be duplikált FTP-kliens EGYETLEN, flotta-közös kiváltása.
32
- *
33
- * **PURE (ADR-004):** a config (URL-ek + titok) a `DyNTS_FtpService_Config`-ból, a hívó adja át —
34
- * nincs `fdp-templates`-import. Nem singleton: minden consumer a saját config-jával példányosít.
35
- *
36
- * @example
37
- * ```ts
38
- * const ftp: DyNTS_FtpService_ApiService = new DyNTS_FtpService_ApiService({
39
- * apiBaseUrl: FDP_ftpServiceApiEnv_settings.baseUrl.fromBE,
40
- * serveBaseUrl: FDP_ftpServiceApiEnv_settings.baseUrl.siteAddress,
41
- * signingSecret: process.env.FTP_URL_SIGNING_SECRET || '',
42
- * });
43
- * const { ftpPath } = await ftp.uploadFile({ accountId, folderPath: 'invoices/<userId>', fileName, fileBuffer });
44
- * const url: string = ftp.buildSignedServeUrl(ftpPath);
45
- * ```
46
- */
47
- export declare class DyNTS_FtpService_ApiService {
48
- private readonly config;
49
- constructor(config: DyNTS_FtpService_Config);
50
- private get uploadFileEndpoint();
51
- /**
52
- * Fájl feltöltése az `fdp-ftp-service`-re (signed, szerver-szerver).
53
- * @description A kanonikus upload-célt + lejáratot HMAC-aláírjuk a megosztott titokkal; az ftp ezt
54
- * validálja (a signature a KONKRÉT account+path-hoz köt — más account-ra nem replay-elhető).
55
- */
56
- uploadFile(input: DyNTS_FtpService_UploadFile_Input): Promise<DyNTS_FtpService_UploadFile_Response>;
57
- /**
58
- * Aláírt serve-URL egy `ftpPath`-ból (böngésző-letöltéshez). Üres titok → üres string (gate inaktív).
59
- * @param ttlMs opcionális egyedi élettartam; default 30 nap.
60
- */
61
- buildSignedServeUrl(ftpPath: string, ttlMs?: number): string;
62
- }
63
- //# sourceMappingURL=ftp-service.api-service.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ftp-service.api-service.d.ts","sourceRoot":"","sources":["../../../../src/_modules/ftp/_services/ftp-service.api-service.ts"],"names":[],"mappings":"AAWA;;;GAGG;AACH,MAAM,WAAW,uBAAuB;IACtC,6HAA6H;IAC7H,UAAU,EAAE,MAAM,CAAC;IACnB,mHAAmH;IACnH,YAAY,EAAE,MAAM,CAAC;IACrB,2GAA2G;IAC3G,aAAa,EAAE,MAAM,CAAC;IACtB,oDAAoD;IACpD,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,8BAA8B;AAC9B,MAAM,WAAW,iCAAiC;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,6BAA6B;AAC7B,MAAM,WAAW,oCAAoC;IACnD,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,2BAA2B;IAEtC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA0B;gBAErC,MAAM,EAAE,uBAAuB;IAI3C,OAAO,KAAK,kBAAkB,GAE7B;IAED;;;;OAIG;IACG,UAAU,CAAC,KAAK,EAAE,iCAAiC,GAAG,OAAO,CAAC,oCAAoC,CAAC;IA8BzG;;;OAGG;IACH,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,GAAE,MAAgC,GAAG,MAAM;CAQtF"}
@@ -1,84 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DyNTS_FtpService_ApiService = void 0;
4
- const fsm_dynamo_1 = require("@futdevpro/fsm-dynamo");
5
- const api_service_1 = require("../../../_services/core/api.service");
6
- const api_call_params_control_model_1 = require("../../../_models/control-models/api-call-params.control-model");
7
- const ftp_url_signer_util_1 = require("../_collections/ftp-url-signer.util");
8
- /** A signed UPLOAD-aláírás érvényessége (10 perc) — a szerver-szerver app→ftp feltöltés szinkron, rövid ablak elég. */
9
- const FTP_SIGNED_UPLOAD_TTL_MS = 10 * 60 * 1000;
10
- /** A signed SERVE-URL alap-élettartama (30 nap) — a felhasználó később is letölti a fájlt. */
11
- const FTP_SIGNED_SERVE_TTL_MS = 30 * 24 * 60 * 60 * 1000;
12
- /**
13
- * FTP Service API kliens (deep/generikus — `@futdevpro/nts-dynamo`).
14
- * @description A szerver-oldali FTP-KLIENS: a meglévő `fdp-ftp-service` tároló-szervert HÍVJA — fájlt tölt
15
- * fel (signed upload), és aláírt serve-URL-t épít. **NEM tartalmaz FTP-szervert.** A korábban az
16
- * MP-be / token-service-be duplikált FTP-kliens EGYETLEN, flotta-közös kiváltása.
17
- *
18
- * **PURE (ADR-004):** a config (URL-ek + titok) a `DyNTS_FtpService_Config`-ból, a hívó adja át —
19
- * nincs `fdp-templates`-import. Nem singleton: minden consumer a saját config-jával példányosít.
20
- *
21
- * @example
22
- * ```ts
23
- * const ftp: DyNTS_FtpService_ApiService = new DyNTS_FtpService_ApiService({
24
- * apiBaseUrl: FDP_ftpServiceApiEnv_settings.baseUrl.fromBE,
25
- * serveBaseUrl: FDP_ftpServiceApiEnv_settings.baseUrl.siteAddress,
26
- * signingSecret: process.env.FTP_URL_SIGNING_SECRET || '',
27
- * });
28
- * const { ftpPath } = await ftp.uploadFile({ accountId, folderPath: 'invoices/<userId>', fileName, fileBuffer });
29
- * const url: string = ftp.buildSignedServeUrl(ftpPath);
30
- * ```
31
- */
32
- class DyNTS_FtpService_ApiService {
33
- config;
34
- constructor(config) {
35
- this.config = config;
36
- }
37
- get uploadFileEndpoint() {
38
- return this.config.uploadFileEndpoint ?? '/files/upload';
39
- }
40
- /**
41
- * Fájl feltöltése az `fdp-ftp-service`-re (signed, szerver-szerver).
42
- * @description A kanonikus upload-célt + lejáratot HMAC-aláírjuk a megosztott titokkal; az ftp ezt
43
- * validálja (a signature a KONKRÉT account+path-hoz köt — más account-ra nem replay-elhető).
44
- */
45
- async uploadFile(input) {
46
- const expMs = Date.now() + FTP_SIGNED_UPLOAD_TTL_MS;
47
- const uploadTarget = ftp_url_signer_util_1.DyNTS_FtpUrlSigner_Util.uploadTarget(input.accountId, input.folderPath, input.fileName);
48
- const sig = ftp_url_signer_util_1.DyNTS_FtpUrlSigner_Util.sign(uploadTarget, expMs, this.config.signingSecret);
49
- return await api_service_1.DyNTS_ApiService.startApiCall(new api_call_params_control_model_1.DyNTS_ApiCall_Params({
50
- name: 'uploadFile (DyNTS_FtpService)',
51
- type: fsm_dynamo_1.DyFM_HttpCallType.post,
52
- baseUrl: this.config.apiBaseUrl,
53
- endpoint: this.uploadFileEndpoint,
54
- httpOptions: new api_call_params_control_model_1.DyNTS_HttpOptions({
55
- headers: {
56
- 'Content-Type': 'application/json',
57
- },
58
- }),
59
- }), {
60
- body: {
61
- accountId: input.accountId,
62
- folderPath: input.folderPath,
63
- fileName: input.fileName,
64
- fileBuffer: input.fileBuffer.toString('base64'),
65
- exp: expMs,
66
- sig: sig,
67
- },
68
- });
69
- }
70
- /**
71
- * Aláírt serve-URL egy `ftpPath`-ból (böngésző-letöltéshez). Üres titok → üres string (gate inaktív).
72
- * @param ttlMs opcionális egyedi élettartam; default 30 nap.
73
- */
74
- buildSignedServeUrl(ftpPath, ttlMs = FTP_SIGNED_SERVE_TTL_MS) {
75
- return ftp_url_signer_util_1.DyNTS_FtpUrlSigner_Util.buildSignedServeUrl({
76
- serveBaseUrl: this.config.serveBaseUrl,
77
- ftpPath: ftpPath,
78
- ttlMs: ttlMs,
79
- secret: this.config.signingSecret,
80
- });
81
- }
82
- }
83
- exports.DyNTS_FtpService_ApiService = DyNTS_FtpService_ApiService;
84
- //# sourceMappingURL=ftp-service.api-service.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ftp-service.api-service.js","sourceRoot":"","sources":["../../../../src/_modules/ftp/_services/ftp-service.api-service.ts"],"names":[],"mappings":";;;AAAA,sDAA0D;AAE1D,qEAAuE;AACvE,iHAAwH;AACxH,6EAA8E;AAE9E,uHAAuH;AACvH,MAAM,wBAAwB,GAAW,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AACxD,8FAA8F;AAC9F,MAAM,uBAAuB,GAAW,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AA+BjE;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAa,2BAA2B;IAErB,MAAM,CAA0B;IAEjD,YAAY,MAA+B;QACzC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAED,IAAY,kBAAkB;QAC5B,OAAO,IAAI,CAAC,MAAM,CAAC,kBAAkB,IAAI,eAAe,CAAC;IAC3D,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,UAAU,CAAC,KAAwC;QACvD,MAAM,KAAK,GAAW,IAAI,CAAC,GAAG,EAAE,GAAG,wBAAwB,CAAC;QAC5D,MAAM,YAAY,GAAW,6CAAuB,CAAC,YAAY,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;QACrH,MAAM,GAAG,GAAW,6CAAuB,CAAC,IAAI,CAAC,YAAY,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;QAEjG,OAAO,MAAM,8BAAgB,CAAC,YAAY,CACxC,IAAI,oDAAoB,CAAC;YACvB,IAAI,EAAE,+BAA+B;YACrC,IAAI,EAAE,8BAAiB,CAAC,IAAI;YAC5B,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,UAAU;YAC/B,QAAQ,EAAE,IAAI,CAAC,kBAAkB;YACjC,WAAW,EAAE,IAAI,iDAAiB,CAAC;gBACjC,OAAO,EAAE;oBACP,cAAc,EAAE,kBAAkB;iBACnC;aACF,CAAC;SACH,CAAC,EACF;YACE,IAAI,EAAE;gBACJ,SAAS,EAAE,KAAK,CAAC,SAAS;gBAC1B,UAAU,EAAE,KAAK,CAAC,UAAU;gBAC5B,QAAQ,EAAE,KAAK,CAAC,QAAQ;gBACxB,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,QAAQ,CAAC;gBAC/C,GAAG,EAAE,KAAK;gBACV,GAAG,EAAE,GAAG;aACT;SACF,CACF,CAAC;IACJ,CAAC;IAED;;;OAGG;IACH,mBAAmB,CAAC,OAAe,EAAE,QAAgB,uBAAuB;QAC1E,OAAO,6CAAuB,CAAC,mBAAmB,CAAC;YACjD,YAAY,EAAE,IAAI,CAAC,MAAM,CAAC,YAAY;YACtC,OAAO,EAAE,OAAO;YAChB,KAAK,EAAE,KAAK;YACZ,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,aAAa;SAClC,CAAC,CAAC;IACL,CAAC;CACF;AA3DD,kEA2DC"}