@futdevpro/nts-dynamo 1.15.120 → 1.15.122
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 +1771 -1876
- package/.dynamo/logs/cicd-pipeline/status.json +40 -40
- package/build/_modules/ftp/_collections/ftp-url-signer.util.d.ts +37 -0
- package/build/_modules/ftp/_collections/ftp-url-signer.util.d.ts.map +1 -0
- package/build/_modules/ftp/_collections/ftp-url-signer.util.js +57 -0
- package/build/_modules/ftp/_collections/ftp-url-signer.util.js.map +1 -0
- package/build/_modules/ftp/_services/ftp-service.api-service.d.ts +63 -0
- package/build/_modules/ftp/_services/ftp-service.api-service.d.ts.map +1 -0
- package/build/_modules/ftp/_services/ftp-service.api-service.js +84 -0
- package/build/_modules/ftp/_services/ftp-service.api-service.js.map +1 -0
- package/build/_modules/ftp/index.d.ts +3 -0
- package/build/_modules/ftp/index.d.ts.map +1 -0
- package/build/_modules/ftp/index.js +8 -0
- package/build/_modules/ftp/index.js.map +1 -0
- package/package.json +11 -2
- package/src/_modules/ftp/_collections/ftp-url-signer.util.spec.ts +44 -0
- package/src/_modules/ftp/_collections/ftp-url-signer.util.ts +60 -0
- package/src/_modules/ftp/_services/ftp-service.api-service.ts +120 -0
- package/src/_modules/ftp/index.ts +7 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"pid":
|
|
3
|
-
"startedAt": "2026-07-
|
|
4
|
-
"startedAtMs":
|
|
2
|
+
"pid": 29648,
|
|
3
|
+
"startedAt": "2026-07-28T22:36:44.892Z",
|
|
4
|
+
"startedAtMs": 1785278204892,
|
|
5
5
|
"phase": "npm-publish",
|
|
6
6
|
"pipelineComplete": false,
|
|
7
7
|
"serverRunning": false,
|
|
@@ -10,83 +10,83 @@
|
|
|
10
10
|
"steps": {
|
|
11
11
|
"validate-secrets": {
|
|
12
12
|
"status": "ok",
|
|
13
|
-
"startedAt": "2026-07-
|
|
13
|
+
"startedAt": "2026-07-28T22:36:44.907Z",
|
|
14
14
|
"logLineStart": 2,
|
|
15
15
|
"logLineEnd": 8,
|
|
16
|
-
"durationSec": 0.
|
|
16
|
+
"durationSec": 0.2
|
|
17
17
|
},
|
|
18
18
|
"discord-start": {
|
|
19
19
|
"status": "ok",
|
|
20
|
-
"startedAt": "2026-07-
|
|
20
|
+
"startedAt": "2026-07-28T22:36:46.140Z",
|
|
21
21
|
"logLineStart": 9,
|
|
22
22
|
"logLineEnd": 14,
|
|
23
|
-
"durationSec":
|
|
23
|
+
"durationSec": 3.2
|
|
24
24
|
},
|
|
25
25
|
"pre-flight": {
|
|
26
26
|
"status": "ok",
|
|
27
|
-
"startedAt": "2026-07-
|
|
27
|
+
"startedAt": "2026-07-28T22:36:50.321Z",
|
|
28
28
|
"logLineStart": 15,
|
|
29
29
|
"logLineEnd": 17,
|
|
30
30
|
"durationSec": 0.6
|
|
31
31
|
},
|
|
32
32
|
"install": {
|
|
33
33
|
"status": "ok",
|
|
34
|
-
"startedAt": "2026-07-
|
|
34
|
+
"startedAt": "2026-07-28T22:36:51.905Z",
|
|
35
35
|
"logLineStart": 18,
|
|
36
|
-
"logLineEnd":
|
|
37
|
-
"durationSec":
|
|
36
|
+
"logLineEnd": 99,
|
|
37
|
+
"durationSec": 19.3
|
|
38
38
|
},
|
|
39
39
|
"check-dev-leftovers": {
|
|
40
40
|
"status": "ok",
|
|
41
|
-
"startedAt": "2026-07-
|
|
42
|
-
"logLineStart":
|
|
43
|
-
"logLineEnd":
|
|
44
|
-
"durationSec":
|
|
41
|
+
"startedAt": "2026-07-28T22:37:12.286Z",
|
|
42
|
+
"logLineStart": 100,
|
|
43
|
+
"logLineEnd": 107,
|
|
44
|
+
"durationSec": 3.9
|
|
45
45
|
},
|
|
46
46
|
"build": {
|
|
47
47
|
"status": "ok",
|
|
48
|
-
"startedAt": "2026-07-
|
|
49
|
-
"logLineStart":
|
|
50
|
-
"logLineEnd":
|
|
51
|
-
"durationSec":
|
|
48
|
+
"startedAt": "2026-07-28T22:37:17.221Z",
|
|
49
|
+
"logLineStart": 108,
|
|
50
|
+
"logLineEnd": 109,
|
|
51
|
+
"durationSec": 25.4
|
|
52
52
|
},
|
|
53
53
|
"test": {
|
|
54
54
|
"status": "ok",
|
|
55
|
-
"startedAt": "2026-07-
|
|
56
|
-
"logLineStart":
|
|
57
|
-
"logLineEnd":
|
|
58
|
-
"durationSec":
|
|
55
|
+
"startedAt": "2026-07-28T22:37:43.612Z",
|
|
56
|
+
"logLineStart": 110,
|
|
57
|
+
"logLineEnd": 2839,
|
|
58
|
+
"durationSec": 26.5,
|
|
59
59
|
"testSummary": {
|
|
60
|
-
"totalSpecs":
|
|
60
|
+
"totalSpecs": 1607,
|
|
61
61
|
"failures": 0,
|
|
62
|
-
"passed":
|
|
63
|
-
"testDurationSec":
|
|
62
|
+
"passed": 1607,
|
|
63
|
+
"testDurationSec": 7.214
|
|
64
64
|
},
|
|
65
65
|
"errors": [
|
|
66
66
|
{
|
|
67
|
-
"line":
|
|
68
|
-
"text": "
|
|
69
|
-
"details": "at
|
|
70
|
-
"detailLineEnd":
|
|
67
|
+
"line": 1627,
|
|
68
|
+
"text": "(checkDbReadiness) diagnostics collection failed (non-fatal) Error: boom",
|
|
69
|
+
"details": "at runNext (/tmp/pipeline-runs/f3e5c9c2-a328-4e84-88e1-5d2dd12df92d/node_modules/.pnpm/jasmine-core@5.10.0/node_modules/jasmine-core/lib/jasmine-core/jasmine.js:8355:16)\nat /tmp/pipeline-runs/f3e5c9c2-a328-4e84-88e1-5d2dd12df92d/node_modules/.pnpm/jasmine-core@5.10.0/node_modules/jasmine-core/lib/jasmine-core/jasmine.js:8361:11\nat /tmp/pipeline-runs/f3e5c9c2-a328-4e84-88e1-5d2dd12df92d/node_modules/.pnpm/jasmine-core@5.10.0/node_modules/jasmine-core/lib/jasmine-core/jasmine.js:8241:9\nat UserContext.<anonymous> (/tmp/pipeline-runs/f3e5c9c2-a328-4e84-88e1-5d2dd12df92d/build/_modules/server/server-status/server-status.control-service.spec.js:507:68)\nat QueueRunner.attempt (/tmp/pipeline-runs/f3e5c9c2-a328-4e84-88e1-5d2dd12df92d/node_modules/.pnpm/jasmine-core@5.10.0/node_modules/jasmine-core/lib/jasmine-core/jasmine.js:8414:40)\nat QueueRunner.run (/tmp/pipeline-runs/f3e5c9c2-a328-4e84-88e1-5d2dd12df92d/node_modules/.pnpm/jasmine-core@5.10.0/node_modules/jasmine-core/lib/jasmine-core/jasmine.js:8452:27)\nat runNext (/tmp/pipeline-runs/f3e5c9c2-a328-4e84-88e1-5d2dd12df92d/node_modules/.pnpm/jasmine-core@5.10.0/node_modules/jasmine-core/lib/jasmine-core/jasmine.js:8355:16)\nat /tmp/pipeline-runs/f3e5c9c2-a328-4e84-88e1-5d2dd12df92d/node_modules/.pnpm/jasmine-core@5.10.0/node_modules/jasmine-core/lib/jasmine-core/jasmine.js:8361:11\nat /tmp/pipeline-runs/f3e5c9c2-a328-4e84-88e1-5d2dd12df92d/node_modules/.pnpm/jasmine-core@5.10.0/node_modules/jasmine-core/lib/jasmine-core/jasmine.js:8241:9",
|
|
70
|
+
"detailLineEnd": 1633
|
|
71
71
|
},
|
|
72
72
|
{
|
|
73
|
-
"line":
|
|
74
|
-
"text": "(checkDbReadiness) diagnostics collection failed (non-fatal) Error: boom",
|
|
75
|
-
"details": "at runNext (/tmp/pipeline-runs/
|
|
76
|
-
"detailLineEnd":
|
|
73
|
+
"line": 1634,
|
|
74
|
+
"text": "...(checkDbReadiness) diagnostics collection failed (non-fatal) Error: diag boom",
|
|
75
|
+
"details": "at runNext (/tmp/pipeline-runs/f3e5c9c2-a328-4e84-88e1-5d2dd12df92d/node_modules/.pnpm/jasmine-core@5.10.0/node_modules/jasmine-core/lib/jasmine-core/jasmine.js:8355:16)\nat /tmp/pipeline-runs/f3e5c9c2-a328-4e84-88e1-5d2dd12df92d/node_modules/.pnpm/jasmine-core@5.10.0/node_modules/jasmine-core/lib/jasmine-core/jasmine.js:8361:11\nat /tmp/pipeline-runs/f3e5c9c2-a328-4e84-88e1-5d2dd12df92d/node_modules/.pnpm/jasmine-core@5.10.0/node_modules/jasmine-core/lib/jasmine-core/jasmine.js:8241:9\nat Object.get host [as host] (/tmp/pipeline-runs/f3e5c9c2-a328-4e84-88e1-5d2dd12df92d/build/_modules/server/server-status/server-status.control-service.spec.js:487:36)\nat TestServerStatusControlService.checkDbReadiness (/tmp/pipeline-runs/f3e5c9c2-a328-4e84-88e1-5d2dd12df92d/build/_modules/server/server-status/server-status.control-service.js:187:29)\nat async UserContext.<anonymous> (/tmp/pipeline-runs/f3e5c9c2-a328-4e84-88e1-5d2dd12df92d/build/_modules/server/server-status/server-status.control-service.spec.js:490:28)",
|
|
76
|
+
"detailLineEnd": 1637
|
|
77
77
|
},
|
|
78
78
|
{
|
|
79
|
-
"line":
|
|
80
|
-
"text": "
|
|
81
|
-
"details": "at
|
|
82
|
-
"detailLineEnd":
|
|
79
|
+
"line": 1811,
|
|
80
|
+
"text": "[dynts-migration] FAILED bad Error: boom",
|
|
81
|
+
"details": "at Object.run (/tmp/pipeline-runs/f3e5c9c2-a328-4e84-88e1-5d2dd12df92d/build/_services/core/migration-runner.service.spec.js:67:50)\nat DyNTS_Migration_Runner.runPending (/tmp/pipeline-runs/f3e5c9c2-a328-4e84-88e1-5d2dd12df92d/build/_services/core/migration-runner.service.js:52:49)\nat async UserContext.<anonymous> (/tmp/pipeline-runs/f3e5c9c2-a328-4e84-88e1-5d2dd12df92d/build/_services/core/migration-runner.service.spec.js:70:25)",
|
|
82
|
+
"detailLineEnd": 1814
|
|
83
83
|
}
|
|
84
84
|
]
|
|
85
85
|
},
|
|
86
86
|
"npm-publish": {
|
|
87
87
|
"status": "running",
|
|
88
|
-
"startedAt": "2026-07-
|
|
89
|
-
"logLineStart":
|
|
88
|
+
"startedAt": "2026-07-28T22:38:11.349Z",
|
|
89
|
+
"logLineStart": 2840,
|
|
90
90
|
"logLineEnd": null,
|
|
91
91
|
"durationSec": null
|
|
92
92
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,84 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { DyNTS_FtpUrlSigner_Util } from './_collections/ftp-url-signer.util';
|
|
2
|
+
export { DyNTS_FtpService_ApiService, DyNTS_FtpService_Config, DyNTS_FtpService_UploadFile_Input, DyNTS_FtpService_UploadFile_Response, } from './_services/ftp-service.api-service';
|
|
3
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/_modules/ftp/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,oCAAoC,CAAC;AAC7E,OAAO,EACL,2BAA2B,EAC3B,uBAAuB,EACvB,iCAAiC,EACjC,oCAAoC,GACrC,MAAM,qCAAqC,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DyNTS_FtpService_ApiService = exports.DyNTS_FtpUrlSigner_Util = void 0;
|
|
4
|
+
var ftp_url_signer_util_1 = require("./_collections/ftp-url-signer.util");
|
|
5
|
+
Object.defineProperty(exports, "DyNTS_FtpUrlSigner_Util", { enumerable: true, get: function () { return ftp_url_signer_util_1.DyNTS_FtpUrlSigner_Util; } });
|
|
6
|
+
var ftp_service_api_service_1 = require("./_services/ftp-service.api-service");
|
|
7
|
+
Object.defineProperty(exports, "DyNTS_FtpService_ApiService", { enumerable: true, get: function () { return ftp_service_api_service_1.DyNTS_FtpService_ApiService; } });
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/_modules/ftp/index.ts"],"names":[],"mappings":";;;AAAA,0EAA6E;AAApE,8HAAA,uBAAuB,OAAA;AAChC,+EAK6C;AAJ3C,sIAAA,2BAA2B,OAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@futdevpro/nts-dynamo",
|
|
3
|
-
"version": "01.15.
|
|
3
|
+
"version": "01.15.122",
|
|
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",
|
|
@@ -271,6 +271,12 @@
|
|
|
271
271
|
"module": "./build/_modules/test-email-sink/index.js",
|
|
272
272
|
"types": "./build/_modules/test-email-sink/index.d.ts",
|
|
273
273
|
"typings": "./build/_modules/test-email-sink/index.d.ts"
|
|
274
|
+
},
|
|
275
|
+
"./ftp": {
|
|
276
|
+
"default": "./build/_modules/ftp/index.js",
|
|
277
|
+
"module": "./build/_modules/ftp/index.js",
|
|
278
|
+
"types": "./build/_modules/ftp/index.d.ts",
|
|
279
|
+
"typings": "./build/_modules/ftp/index.d.ts"
|
|
274
280
|
}
|
|
275
281
|
},
|
|
276
282
|
"typesVersions": {
|
|
@@ -367,6 +373,9 @@
|
|
|
367
373
|
],
|
|
368
374
|
"test-email-sink": [
|
|
369
375
|
"build/_modules/test-email-sink/index.d.ts"
|
|
376
|
+
],
|
|
377
|
+
"ftp": [
|
|
378
|
+
"./build/_modules/ftp/index.d.ts"
|
|
370
379
|
]
|
|
371
380
|
}
|
|
372
381
|
},
|
|
@@ -378,7 +387,7 @@
|
|
|
378
387
|
"@anthropic-ai/sdk": "~0.39.0",
|
|
379
388
|
"@modelcontextprotocol/sdk": "~1.29.0",
|
|
380
389
|
"better-sqlite3": "~12.4.1",
|
|
381
|
-
"@futdevpro/fsm-dynamo": "1.16.
|
|
390
|
+
"@futdevpro/fsm-dynamo": "1.16.27",
|
|
382
391
|
"@types/express": "4.17.21",
|
|
383
392
|
"@types/geoip-lite": "~1.4.1",
|
|
384
393
|
"@types/node": "~24.1.0",
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { DyNTS_FtpUrlSigner_Util } from './ftp-url-signer.util';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A generikus FTP signed-URL aláíró tesztjei (AAA). Igazolja: a normalizálás + a HMAC determinisztikus,
|
|
5
|
+
* a tamper/lejárat eltérő sig-et ad, üres titok → üres serve-URL (gate inaktív).
|
|
6
|
+
*/
|
|
7
|
+
describe('| DyNTS_FtpUrlSigner_Util', () => {
|
|
8
|
+
const SECRET: string = 'shared-ftp-signing-secret-123';
|
|
9
|
+
|
|
10
|
+
it('| normalizePath: leading-slash le + backslash→slash', () => {
|
|
11
|
+
expect(DyNTS_FtpUrlSigner_Util.normalizePath('/a\\b/c')).toBe('a/b/c');
|
|
12
|
+
expect(DyNTS_FtpUrlSigner_Util.normalizePath('///x/y')).toBe('x/y');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('| uploadTarget: accountId/folderPath/fileName', () => {
|
|
16
|
+
expect(DyNTS_FtpUrlSigner_Util.uploadTarget('acc', 'invoices/u1', 'inv.pdf')).toBe('acc/invoices/u1/inv.pdf');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('| sign: determinisztikus + a normalizált path-ra HMAC-el (a nyers és a normalizált egyezik)', () => {
|
|
20
|
+
const a: string = DyNTS_FtpUrlSigner_Util.sign('/inv/u1.pdf', 1000, SECRET);
|
|
21
|
+
const b: string = DyNTS_FtpUrlSigner_Util.sign('inv/u1.pdf', 1000, SECRET);
|
|
22
|
+
expect(a).toBe(b);
|
|
23
|
+
expect(a).toMatch(/^[0-9a-f]{64}$/);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('| sign: eltérő exp / eltérő secret → eltérő aláírás (tamper-evidens)', () => {
|
|
27
|
+
const base: string = DyNTS_FtpUrlSigner_Util.sign('inv/u1.pdf', 1000, SECRET);
|
|
28
|
+
expect(DyNTS_FtpUrlSigner_Util.sign('inv/u1.pdf', 2000, SECRET)).not.toBe(base);
|
|
29
|
+
expect(DyNTS_FtpUrlSigner_Util.sign('inv/u1.pdf', 1000, 'other')).not.toBe(base);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('| buildSignedServeUrl: exp=now+ttl + sig; a base trailing-slash levágva', () => {
|
|
33
|
+
const url: string = DyNTS_FtpUrlSigner_Util.buildSignedServeUrl({
|
|
34
|
+
serveBaseUrl: 'https://ftp.host/', ftpPath: '/inv/u1.pdf', ttlMs: 5000, secret: SECRET, nowMs: 1000,
|
|
35
|
+
});
|
|
36
|
+
expect(url).toBe(`https://ftp.host/inv/u1.pdf?exp=6000&sig=${DyNTS_FtpUrlSigner_Util.sign('inv/u1.pdf', 6000, SECRET)}`);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('| buildSignedServeUrl: ÜRES titok → üres string (gate inaktív)', () => {
|
|
40
|
+
expect(DyNTS_FtpUrlSigner_Util.buildSignedServeUrl({
|
|
41
|
+
serveBaseUrl: 'https://ftp.host', ftpPath: 'x.pdf', ttlMs: 5000, secret: '',
|
|
42
|
+
})).toBe('');
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import * as Crypto from 'crypto';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* FTP signed-URL ALÁÍRÓ (deep/generikus — `@futdevpro/nts-dynamo`).
|
|
5
|
+
*
|
|
6
|
+
* A böngésző-serve (`<img src>` / letöltő-link) nem tud Bearer-headert küldeni, ezért az
|
|
7
|
+
* `fdp-ftp-service` HTTP-kiszolgálása rövid-/közép-életű **aláírt URL**-lel auth-ol. Ez az util a
|
|
8
|
+
* megosztott titokkal HMAC-SHA256-tal aláírja a `path + exp`-et, és előállítja a serve-URL-t.
|
|
9
|
+
*
|
|
10
|
+
* ⚠️ Az algoritmusnak **BIT-RE egyeznie kell** az `fdp-ftp-service` `FtpUrlSignature_Util`-jával
|
|
11
|
+
* (kanonikus string: `<normalizedPath>\n<expMs>`; normalizálás: leading-slash le + `\`→`/`), különben
|
|
12
|
+
* az ftp elutasítja a signature-t.
|
|
13
|
+
*
|
|
14
|
+
* **PURE (ADR-004):** ez a modul NEM importál `fdp-templates`-et — a `secret`/`serveBaseUrl` a hívó-
|
|
15
|
+
* (consumer-) oldalról jön paraméterként. Így minden FDP-server újrahasználhatja, egyetlen példányban.
|
|
16
|
+
*/
|
|
17
|
+
export class DyNTS_FtpUrlSigner_Util {
|
|
18
|
+
|
|
19
|
+
/** Path normalizálás — pontosan az ftp-serve normalizálása (leading-slash le, `\`→`/`). */
|
|
20
|
+
static normalizePath(filePath: string): string {
|
|
21
|
+
return filePath.replace(/^\/+/, '').replace(/\\/g, '/');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Kanonikus UPLOAD-cél string: `accountId/folderPath/fileName`. A szerver-szerver (app→ftp) upload
|
|
26
|
+
* ezt írja alá → az ftp ugyanígy építi + validálja. Bit-azonos kell legyen a két oldalon.
|
|
27
|
+
*/
|
|
28
|
+
static uploadTarget(accountId: string, folderPath: string, fileName: string): string {
|
|
29
|
+
return `${accountId}/${folderPath}/${fileName}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** HMAC-SHA256 a `<normalizedPath>\n<expMs>`-en → hex (az ftp ezt validálja). */
|
|
33
|
+
static sign(filePath: string, expMs: number, secret: string): string {
|
|
34
|
+
const normalizedPath: string = DyNTS_FtpUrlSigner_Util.normalizePath(filePath);
|
|
35
|
+
return Crypto
|
|
36
|
+
.createHmac('sha256', secret)
|
|
37
|
+
.update(`${normalizedPath}\n${expMs}`, 'utf8')
|
|
38
|
+
.digest('hex');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Teljes aláírt serve-URL egy ftp-path-ból: `<serveBaseUrl>/<normalizedPath>?exp=<ms>&sig=<hex>`.
|
|
43
|
+
* Üres `secret` → üres string (a gate inaktív; a hívó dönt a fallback-ről). `nowMs` injektálható teszthez.
|
|
44
|
+
*/
|
|
45
|
+
static buildSignedServeUrl(params: {
|
|
46
|
+
serveBaseUrl: string;
|
|
47
|
+
ftpPath: string;
|
|
48
|
+
ttlMs: number;
|
|
49
|
+
secret: string;
|
|
50
|
+
nowMs?: number;
|
|
51
|
+
}): string {
|
|
52
|
+
if (!params.secret) { return ''; }
|
|
53
|
+
const now: number = typeof params.nowMs === 'number' ? params.nowMs : Date.now();
|
|
54
|
+
const expMs: number = now + params.ttlMs;
|
|
55
|
+
const normalizedPath: string = DyNTS_FtpUrlSigner_Util.normalizePath(params.ftpPath);
|
|
56
|
+
const sig: string = DyNTS_FtpUrlSigner_Util.sign(normalizedPath, expMs, params.secret);
|
|
57
|
+
const base: string = params.serveBaseUrl.replace(/\/+$/, '');
|
|
58
|
+
return `${base}/${normalizedPath}?exp=${expMs}&sig=${sig}`;
|
|
59
|
+
}
|
|
60
|
+
}
|