@crowdin/app-project-module 2.3.0 → 2.5.0
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/out/modules/file-processing/handlers/custom-file-format.d.ts +2 -1
- package/out/modules/file-processing/handlers/custom-file-format.js +12 -9
- package/out/modules/file-processing/handlers/file-download.js +10 -2
- package/out/modules/file-processing/handlers/pre-post-process.js +7 -6
- package/out/modules/file-processing/handlers/translations-alignment.js +7 -2
- package/out/modules/file-processing/index.js +2 -0
- package/out/modules/file-processing/util/files.js +2 -1
- package/out/modules/file-processing/util/signed-url.d.ts +2 -0
- package/out/modules/file-processing/util/signed-url.js +27 -0
- package/out/static/ui/main.bundle.js +1632 -1563
- package/out/static/ui/main.bundle.js.map +1 -1
- package/package.json +5 -2
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Response } from 'express';
|
|
2
2
|
import { CrowdinClientRequest, FileStore } from '../../../types';
|
|
3
3
|
import { CustomFileFormatLogic } from '../types';
|
|
4
|
-
export default function handle({ baseUrl, folder, config, fileStore, }: {
|
|
4
|
+
export default function handle({ baseUrl, folder, config, fileStore, clientSecret, }: {
|
|
5
5
|
baseUrl: string;
|
|
6
6
|
folder: string;
|
|
7
7
|
config: CustomFileFormatLogic;
|
|
8
8
|
fileStore?: FileStore;
|
|
9
|
+
clientSecret: string;
|
|
9
10
|
}): (req: import("express").Request | CrowdinClientRequest, res: Response, next: Function) => void;
|
|
@@ -17,8 +17,9 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
17
17
|
const path_1 = __importDefault(require("path"));
|
|
18
18
|
const util_1 = require("../../../util");
|
|
19
19
|
const files_1 = require("../util/files");
|
|
20
|
+
const signed_url_1 = require("../util/signed-url");
|
|
20
21
|
const types_1 = require("../types");
|
|
21
|
-
function handle({ baseUrl, folder, config, fileStore, }) {
|
|
22
|
+
function handle({ baseUrl, folder, config, fileStore, clientSecret, }) {
|
|
22
23
|
if (!fileStore) {
|
|
23
24
|
if (!fs_1.default.existsSync(path_1.default.join(folder, 'custom-file-format'))) {
|
|
24
25
|
fs_1.default.mkdirSync(path_1.default.join(folder, 'custom-file-format'), { recursive: true });
|
|
@@ -56,6 +57,7 @@ function handle({ baseUrl, folder, config, fileStore, }) {
|
|
|
56
57
|
projectId: req.crowdinContext.jwtPayload.context.project_id,
|
|
57
58
|
file,
|
|
58
59
|
fileStore,
|
|
60
|
+
clientSecret,
|
|
59
61
|
});
|
|
60
62
|
response = buildFileResult.response;
|
|
61
63
|
error = buildFileResult.error;
|
|
@@ -71,6 +73,7 @@ function handle({ baseUrl, folder, config, fileStore, }) {
|
|
|
71
73
|
projectId: req.crowdinContext.jwtPayload.context.project_id,
|
|
72
74
|
file,
|
|
73
75
|
fileStore,
|
|
76
|
+
clientSecret,
|
|
74
77
|
});
|
|
75
78
|
response = parseFileResult.response;
|
|
76
79
|
error = parseFileResult.error;
|
|
@@ -82,7 +85,7 @@ function handle({ baseUrl, folder, config, fileStore, }) {
|
|
|
82
85
|
}));
|
|
83
86
|
}
|
|
84
87
|
function handleBuildFile(_a) {
|
|
85
|
-
return __awaiter(this, arguments, void 0, function* ({ baseUrl, dataFolder, config, req, client, context, projectId, file, fileStore, }) {
|
|
88
|
+
return __awaiter(this, arguments, void 0, function* ({ baseUrl, dataFolder, config, req, client, context, projectId, file, fileStore, clientSecret, }) {
|
|
86
89
|
const response = {};
|
|
87
90
|
if (!req.strings && !req.stringsUrl) {
|
|
88
91
|
throw new Error('Bad payload received: No strings found');
|
|
@@ -112,7 +115,7 @@ function handleBuildFile(_a) {
|
|
|
112
115
|
let url;
|
|
113
116
|
if (fileStore) {
|
|
114
117
|
const fileRef = yield fileStore.storeFile(res.contentFile);
|
|
115
|
-
url =
|
|
118
|
+
url = (0, signed_url_1.signDownloadUrl)(baseUrl, fileRef, clientSecret);
|
|
116
119
|
}
|
|
117
120
|
else {
|
|
118
121
|
if (config.storeFile) {
|
|
@@ -120,7 +123,7 @@ function handleBuildFile(_a) {
|
|
|
120
123
|
}
|
|
121
124
|
else {
|
|
122
125
|
const storedFile = yield (0, files_1.storeFile)(res.contentFile, path_1.default.join(dataFolder, 'custom-file-format'));
|
|
123
|
-
url =
|
|
126
|
+
url = (0, signed_url_1.signDownloadUrl)(baseUrl, storedFile, clientSecret);
|
|
124
127
|
}
|
|
125
128
|
}
|
|
126
129
|
response.contentUrl = url;
|
|
@@ -129,7 +132,7 @@ function handleBuildFile(_a) {
|
|
|
129
132
|
});
|
|
130
133
|
}
|
|
131
134
|
function handleParseFile(_a) {
|
|
132
|
-
return __awaiter(this, arguments, void 0, function* ({ baseUrl, dataFolder, config, req, client, context, projectId, file, fileStore, }) {
|
|
135
|
+
return __awaiter(this, arguments, void 0, function* ({ baseUrl, dataFolder, config, req, client, context, projectId, file, fileStore, clientSecret, }) {
|
|
133
136
|
const response = {};
|
|
134
137
|
if (!config.parseFile || !file) {
|
|
135
138
|
return { response };
|
|
@@ -147,7 +150,7 @@ function handleParseFile(_a) {
|
|
|
147
150
|
let url;
|
|
148
151
|
if (fileStore) {
|
|
149
152
|
const fileRef = yield fileStore.storeFile(res.previewFile);
|
|
150
|
-
url =
|
|
153
|
+
url = (0, signed_url_1.signDownloadUrl)(baseUrl, fileRef, clientSecret);
|
|
151
154
|
}
|
|
152
155
|
else {
|
|
153
156
|
if (config.storeFile) {
|
|
@@ -155,7 +158,7 @@ function handleParseFile(_a) {
|
|
|
155
158
|
}
|
|
156
159
|
else {
|
|
157
160
|
const storedFile = yield (0, files_1.storeFile)(res.previewFile, path_1.default.join(dataFolder, 'custom-file-format'));
|
|
158
|
-
url =
|
|
161
|
+
url = (0, signed_url_1.signDownloadUrl)(baseUrl, storedFile, clientSecret);
|
|
159
162
|
}
|
|
160
163
|
}
|
|
161
164
|
response.previewUrl = url;
|
|
@@ -181,7 +184,7 @@ function handleParseFile(_a) {
|
|
|
181
184
|
let url;
|
|
182
185
|
if (fileStore) {
|
|
183
186
|
const fileRef = yield fileStore.storeFile(bufferData);
|
|
184
|
-
url =
|
|
187
|
+
url = (0, signed_url_1.signDownloadUrl)(baseUrl, fileRef, clientSecret);
|
|
185
188
|
}
|
|
186
189
|
else {
|
|
187
190
|
if (config.storeFile) {
|
|
@@ -189,7 +192,7 @@ function handleParseFile(_a) {
|
|
|
189
192
|
}
|
|
190
193
|
else {
|
|
191
194
|
const storedFile = yield (0, files_1.storeFile)(bufferData, path_1.default.join(dataFolder, 'custom-file-format'));
|
|
192
|
-
url =
|
|
195
|
+
url = (0, signed_url_1.signDownloadUrl)(baseUrl, storedFile, clientSecret);
|
|
193
196
|
}
|
|
194
197
|
}
|
|
195
198
|
response.stringsUrl = url;
|
|
@@ -18,15 +18,23 @@ const path_1 = __importDefault(require("path"));
|
|
|
18
18
|
const util_1 = require("../../../util");
|
|
19
19
|
const logger_1 = require("../../../util/logger");
|
|
20
20
|
const folder_1 = require("../util/folder");
|
|
21
|
+
const signed_url_1 = require("../util/signed-url");
|
|
21
22
|
function handle(config, processingConfig, folderName) {
|
|
22
23
|
const folder = (0, folder_1.resolveModuleFilesFolder)(config.dbFolder, processingConfig.filesFolder);
|
|
23
24
|
return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
25
|
+
const fileRef = req.query.file;
|
|
26
|
+
const expires = req.query.expires;
|
|
27
|
+
const sig = req.query.sig;
|
|
28
|
+
if (!fileRef || !expires || !sig || !(0, signed_url_1.verifySignedDownloadUrl)(fileRef, expires, sig, config.clientSecret)) {
|
|
29
|
+
res.sendStatus(403);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
24
32
|
if (config.fileStore) {
|
|
25
|
-
const fileContent = yield config.fileStore.getFile(
|
|
33
|
+
const fileContent = yield config.fileStore.getFile(fileRef);
|
|
26
34
|
res.send(fileContent);
|
|
27
35
|
return;
|
|
28
36
|
}
|
|
29
|
-
const filePath = path_1.default.join(folder, folderName,
|
|
37
|
+
const filePath = path_1.default.join(folder, folderName, path_1.default.basename(fileRef));
|
|
30
38
|
(0, logger_1.log)(`Downloading file ${filePath}`);
|
|
31
39
|
res.download(filePath, function (err) {
|
|
32
40
|
if (err) {
|
|
@@ -19,6 +19,7 @@ const util_1 = require("../../../util");
|
|
|
19
19
|
const types_1 = require("../types");
|
|
20
20
|
const folder_1 = require("../util/folder");
|
|
21
21
|
const files_1 = require("../util/files");
|
|
22
|
+
const signed_url_1 = require("../util/signed-url");
|
|
22
23
|
function handle(baseConfig, config, folderName) {
|
|
23
24
|
const folderPath = (0, folder_1.resolveModuleFilesFolder)(baseConfig.dbFolder, config.filesFolder);
|
|
24
25
|
if (!baseConfig.fileStore) {
|
|
@@ -84,7 +85,7 @@ function handle(baseConfig, config, folderName) {
|
|
|
84
85
|
let url;
|
|
85
86
|
if (baseConfig.fileStore) {
|
|
86
87
|
const fileRef = yield baseConfig.fileStore.storeFile(contentFile);
|
|
87
|
-
url =
|
|
88
|
+
url = (0, signed_url_1.signDownloadUrl)(baseFilesUrl, fileRef, baseConfig.clientSecret);
|
|
88
89
|
}
|
|
89
90
|
else {
|
|
90
91
|
if (config.storeFile) {
|
|
@@ -92,7 +93,7 @@ function handle(baseConfig, config, folderName) {
|
|
|
92
93
|
}
|
|
93
94
|
else {
|
|
94
95
|
const storedFile = yield (0, files_1.storeFile)(contentFile, path_1.default.join(folderPath, folderName));
|
|
95
|
-
url =
|
|
96
|
+
url = (0, signed_url_1.signDownloadUrl)(baseFilesUrl, storedFile, baseConfig.clientSecret);
|
|
96
97
|
}
|
|
97
98
|
}
|
|
98
99
|
response.contentUrl = url;
|
|
@@ -121,7 +122,7 @@ function handle(baseConfig, config, folderName) {
|
|
|
121
122
|
let url;
|
|
122
123
|
if (baseConfig.fileStore) {
|
|
123
124
|
const fileRef = yield baseConfig.fileStore.storeFile(previewFile);
|
|
124
|
-
url =
|
|
125
|
+
url = (0, signed_url_1.signDownloadUrl)(baseFilesUrl, fileRef, baseConfig.clientSecret);
|
|
125
126
|
}
|
|
126
127
|
else {
|
|
127
128
|
if (config.storeFile) {
|
|
@@ -129,7 +130,7 @@ function handle(baseConfig, config, folderName) {
|
|
|
129
130
|
}
|
|
130
131
|
else {
|
|
131
132
|
const storedFile = yield (0, files_1.storeFile)(previewFile, path_1.default.join(folderPath, folderName));
|
|
132
|
-
url =
|
|
133
|
+
url = (0, signed_url_1.signDownloadUrl)(baseFilesUrl, storedFile, baseConfig.clientSecret);
|
|
133
134
|
}
|
|
134
135
|
}
|
|
135
136
|
response.previewUrl = url;
|
|
@@ -145,7 +146,7 @@ function handle(baseConfig, config, folderName) {
|
|
|
145
146
|
let url;
|
|
146
147
|
if (baseConfig.fileStore) {
|
|
147
148
|
const fileRef = yield baseConfig.fileStore.storeFile(bufferData);
|
|
148
|
-
url =
|
|
149
|
+
url = (0, signed_url_1.signDownloadUrl)(baseFilesUrl, fileRef, baseConfig.clientSecret);
|
|
149
150
|
}
|
|
150
151
|
else {
|
|
151
152
|
if (config.storeFile) {
|
|
@@ -153,7 +154,7 @@ function handle(baseConfig, config, folderName) {
|
|
|
153
154
|
}
|
|
154
155
|
else {
|
|
155
156
|
const storedFile = yield (0, files_1.storeFile)(bufferData, path_1.default.join(folderPath, folderName));
|
|
156
|
-
url =
|
|
157
|
+
url = (0, signed_url_1.signDownloadUrl)(baseFilesUrl, storedFile, baseConfig.clientSecret);
|
|
157
158
|
}
|
|
158
159
|
}
|
|
159
160
|
response.stringsUrl = url;
|
|
@@ -18,6 +18,7 @@ const path_1 = __importDefault(require("path"));
|
|
|
18
18
|
const util_1 = require("../../../util");
|
|
19
19
|
const folder_1 = require("../util/folder");
|
|
20
20
|
const files_1 = require("../util/files");
|
|
21
|
+
const signed_url_1 = require("../util/signed-url");
|
|
21
22
|
function handle(baseConfig, config, folderName) {
|
|
22
23
|
const folderPath = (0, folder_1.resolveModuleFilesFolder)(baseConfig.dbFolder, config.filesFolder);
|
|
23
24
|
if (!fs_1.default.existsSync(path_1.default.join(folderPath, folderName))) {
|
|
@@ -65,12 +66,16 @@ function handle(baseConfig, config, folderName) {
|
|
|
65
66
|
}
|
|
66
67
|
else {
|
|
67
68
|
let url;
|
|
68
|
-
if (
|
|
69
|
+
if (baseConfig.fileStore) {
|
|
70
|
+
const fileRef = yield baseConfig.fileStore.storeFile(bufferData);
|
|
71
|
+
url = (0, signed_url_1.signDownloadUrl)(baseFilesUrl, fileRef, baseConfig.clientSecret);
|
|
72
|
+
}
|
|
73
|
+
else if (config.storeFile) {
|
|
69
74
|
url = yield config.storeFile(bufferData);
|
|
70
75
|
}
|
|
71
76
|
else {
|
|
72
77
|
const storedFile = yield (0, files_1.storeFile)(bufferData, path_1.default.join(folderPath, folderName));
|
|
73
|
-
url =
|
|
78
|
+
url = (0, signed_url_1.signDownloadUrl)(baseFilesUrl, storedFile, baseConfig.clientSecret);
|
|
74
79
|
}
|
|
75
80
|
response.translationsUrl = url;
|
|
76
81
|
}
|
|
@@ -39,6 +39,7 @@ function registerCustomFileFormat({ config, app }) {
|
|
|
39
39
|
folder: (0, folder_1.resolveModuleFilesFolder)(config.dbFolder, ff.filesFolder),
|
|
40
40
|
config: ff,
|
|
41
41
|
fileStore: config.fileStore,
|
|
42
|
+
clientSecret: config.clientSecret,
|
|
42
43
|
}));
|
|
43
44
|
app.get(isSingle ? '/file/download' : `/file/${key}/download`, (0, file_download_1.default)(config, ff, 'custom-file-format'));
|
|
44
45
|
}
|
|
@@ -54,6 +55,7 @@ function registerCustomFileFormat({ config, app }) {
|
|
|
54
55
|
folder: (0, folder_1.resolveModuleFilesFolder)(config.dbFolder, ff.filesFolder),
|
|
55
56
|
config: ff,
|
|
56
57
|
fileStore: config.fileStore,
|
|
58
|
+
clientSecret: config.clientSecret,
|
|
57
59
|
}));
|
|
58
60
|
// END TEMPORARY CODE
|
|
59
61
|
}
|
|
@@ -17,11 +17,12 @@ exports.storeFile = storeFile;
|
|
|
17
17
|
exports.getFileContent = getFileContent;
|
|
18
18
|
exports.getFileStrings = getFileStrings;
|
|
19
19
|
const axios_1 = __importDefault(require("axios"));
|
|
20
|
+
const crypto_1 = require("crypto");
|
|
20
21
|
const fs_1 = __importDefault(require("fs"));
|
|
21
22
|
const path_1 = __importDefault(require("path"));
|
|
22
23
|
exports.MAX_BODY_SIZE = 4.9 * 1024 * 1024; //4.9mb
|
|
23
24
|
function storeFile(fileContent, folder) {
|
|
24
|
-
const fileName =
|
|
25
|
+
const fileName = (0, crypto_1.randomBytes)(16).toString('hex');
|
|
25
26
|
return new Promise((res, rej) => fs_1.default.writeFile(path_1.default.join(folder, fileName), fileContent, (err) => {
|
|
26
27
|
if (err) {
|
|
27
28
|
rej(err);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.signDownloadUrl = signDownloadUrl;
|
|
4
|
+
exports.verifySignedDownloadUrl = verifySignedDownloadUrl;
|
|
5
|
+
const crypto_1 = require("crypto");
|
|
6
|
+
const DEFAULT_EXPIRY_MS = 60 * 60 * 1000; // 1 hour
|
|
7
|
+
function signDownloadUrl(baseUrl, fileRef, secret, expiresInMs = DEFAULT_EXPIRY_MS) {
|
|
8
|
+
const expires = Date.now() + expiresInMs;
|
|
9
|
+
const sig = computeHmac(fileRef, expires, secret);
|
|
10
|
+
return `${baseUrl}?file=${encodeURIComponent(fileRef)}&expires=${expires}&sig=${sig}`;
|
|
11
|
+
}
|
|
12
|
+
function verifySignedDownloadUrl(fileRef, expires, sig, secret) {
|
|
13
|
+
const expiresMs = parseInt(expires, 10);
|
|
14
|
+
if (isNaN(expiresMs) || Date.now() > expiresMs) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
const expected = computeHmac(fileRef, expiresMs, secret);
|
|
18
|
+
const expectedBuf = Buffer.from(expected, 'hex');
|
|
19
|
+
const sigBuf = Buffer.from(sig, 'hex');
|
|
20
|
+
if (expectedBuf.length !== sigBuf.length) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
return (0, crypto_1.timingSafeEqual)(expectedBuf, sigBuf);
|
|
24
|
+
}
|
|
25
|
+
function computeHmac(fileRef, expires, secret) {
|
|
26
|
+
return (0, crypto_1.createHmac)('sha256', secret).update(`${fileRef}:${expires}`).digest('hex');
|
|
27
|
+
}
|