node-server-engine 1.13.0 → 2.0.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/README.md +1 -0
- package/dist/entities/ElasticSearch/ElasticSearch.js +2 -1
- package/dist/entities/ElasticSearch/ElasticSearch.js.map +1 -1
- package/dist/entities/PubSub/PubSub.js +13 -6
- package/dist/entities/PubSub/PubSub.js.map +1 -1
- package/dist/entities/Sequelize/Sequelize.js +21 -1
- package/dist/entities/Sequelize/Sequelize.js.map +1 -1
- package/dist/entities/Server/Server.d.ts +2 -0
- package/dist/entities/Server/Server.js +15 -1
- package/dist/entities/Server/Server.js.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/middleware/authJwt/authJwt.js +3 -5
- package/dist/middleware/authJwt/authJwt.js.map +1 -1
- package/dist/middleware/authStatic/authStatic.js +12 -8
- package/dist/middleware/authStatic/authStatic.js.map +1 -1
- package/dist/middleware/authTls/authTls.js +2 -1
- package/dist/middleware/authTls/authTls.js.map +1 -1
- package/dist/middleware/multiPartFileUploader/multiPartFileUploader.js +50 -3
- package/dist/middleware/multiPartFileUploader/multiPartFileUploader.js.map +1 -1
- package/dist/utils/dbMigration/dbMigration.js +2 -1
- package/dist/utils/dbMigration/dbMigration.js.map +1 -1
- package/dist/utils/geminiFileUpload/geminiFileUpload.d.ts +10 -1
- package/dist/utils/geminiFileUpload/geminiFileUpload.js +58 -46
- package/dist/utils/geminiFileUpload/geminiFileUpload.js.map +1 -1
- package/dist/utils/jwt/jwt.js +0 -3
- package/dist/utils/jwt/jwt.js.map +1 -1
- package/dist/utils/report/output.d.ts +2 -0
- package/dist/utils/report/output.js +27 -0
- package/dist/utils/report/output.js.map +1 -1
- package/dist/utils/sendEmail/sendEmail.js +5 -4
- package/dist/utils/sendEmail/sendEmail.js.map +1 -1
- package/dist/utils/verificationToken/verificationToken.js +16 -9
- package/dist/utils/verificationToken/verificationToken.js.map +1 -1
- package/package.json +59 -51
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["_const","require","Object","keys","forEach","key","prototype","hasOwnProperty","call","_exportNames","exports","defineProperty","enumerable","get","_entities","_utils","_tlsRequest","_middleware","_interopRequireWildcard","middleware","e","t","
|
|
1
|
+
{"version":3,"file":"index.js","names":["_const","require","Object","keys","forEach","key","prototype","hasOwnProperty","call","_exportNames","exports","defineProperty","enumerable","get","_entities","_utils","_tlsRequest","_middleware","_interopRequireWildcard","middleware","cache","e","t","__esModule","r","c","a","n","__proto__","default","WeakMap","has","set","getOwnPropertyDescriptor"],"sources":["../src/index.ts"],"sourcesContent":["// src/index.ts\nexport * from './const';\nexport * from './entities';\nexport * from './utils';\nexport * from './utils/tlsRequest';\nexport * as middleware from './middleware';\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -26,7 +26,7 @@ function authJwt() {
|
|
|
26
26
|
errorCode: 'unauthorized',
|
|
27
27
|
statusCode: 401,
|
|
28
28
|
data: {
|
|
29
|
-
|
|
29
|
+
scheme
|
|
30
30
|
}
|
|
31
31
|
});
|
|
32
32
|
}
|
|
@@ -34,8 +34,7 @@ function authJwt() {
|
|
|
34
34
|
namespace,
|
|
35
35
|
message: 'Performing JWT authentication',
|
|
36
36
|
data: {
|
|
37
|
-
scheme
|
|
38
|
-
credentials
|
|
37
|
+
scheme
|
|
39
38
|
}
|
|
40
39
|
});
|
|
41
40
|
// Verify token and extract its payload
|
|
@@ -44,8 +43,7 @@ function authJwt() {
|
|
|
44
43
|
namespace,
|
|
45
44
|
message: 'Extracted user data from JWT',
|
|
46
45
|
data: {
|
|
47
|
-
payload
|
|
48
|
-
credentials
|
|
46
|
+
payload
|
|
49
47
|
}
|
|
50
48
|
});
|
|
51
49
|
// The user object on the request is hydrated with data from the token
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authJwt.js","names":["_const","require","_EngineError","_WebError","_jwt","_report","namespace","authJwt","process","env","ACCESS_TOKEN_AUDIENCE","EngineError","message","request","response","next","scheme","credentials","headers","authorization","split","toLowerCase","WebError","errorCode","statusCode","data","reportDebug","payload","jwtVerify","TokenIssuer","AUTH_SERVICE","user"],"sources":["../../../src/middleware/authJwt/authJwt.ts"],"sourcesContent":["import { Request, Response, NextFunction } from 'express';\nimport { TokenIssuer } from 'const';\nimport { EngineError } from 'entities/EngineError';\nimport { WebError } from 'entities/WebError';\nimport { jwtVerify } from 'utils/jwt';\nimport { reportDebug } from 'utils/report';\nimport {} from './authJwt.types';\n\nconst namespace = 'engine:middleware:authJwt';\n\n/** Generate a middleware to authenticate the client connection with a JWT */\nexport function authJwt(): (\n request: Request,\n response: Response,\n next: NextFunction\n) => Promise<void> {\n if (!process.env.ACCESS_TOKEN_AUDIENCE)\n throw new EngineError({\n message: 'Env variable ACCESS_TOKEN_AUDIENCE is not defined'\n });\n\n return async (\n request: Request,\n response: Response,\n next: NextFunction\n ): Promise<void> => {\n const [scheme, credentials] =\n request.headers?.authorization?.split(' ') ?? [];\n // No bearer token has been sent\n if (!credentials || scheme.toLowerCase() !== 'bearer') {\n throw new WebError({\n message: 'No bearer token found',\n errorCode: 'unauthorized',\n statusCode: 401,\n data: {
|
|
1
|
+
{"version":3,"file":"authJwt.js","names":["_const","require","_EngineError","_WebError","_jwt","_report","namespace","authJwt","process","env","ACCESS_TOKEN_AUDIENCE","EngineError","message","request","response","next","scheme","credentials","headers","authorization","split","toLowerCase","WebError","errorCode","statusCode","data","reportDebug","payload","jwtVerify","TokenIssuer","AUTH_SERVICE","user"],"sources":["../../../src/middleware/authJwt/authJwt.ts"],"sourcesContent":["import { Request, Response, NextFunction } from 'express';\nimport { TokenIssuer } from 'const';\nimport { EngineError } from 'entities/EngineError';\nimport { WebError } from 'entities/WebError';\nimport { jwtVerify } from 'utils/jwt';\nimport { reportDebug } from 'utils/report';\nimport {} from './authJwt.types';\n\nconst namespace = 'engine:middleware:authJwt';\n\n/** Generate a middleware to authenticate the client connection with a JWT */\nexport function authJwt(): (\n request: Request,\n response: Response,\n next: NextFunction\n) => Promise<void> {\n if (!process.env.ACCESS_TOKEN_AUDIENCE)\n throw new EngineError({\n message: 'Env variable ACCESS_TOKEN_AUDIENCE is not defined'\n });\n\n return async (\n request: Request,\n response: Response,\n next: NextFunction\n ): Promise<void> => {\n const [scheme, credentials] =\n request.headers?.authorization?.split(' ') ?? [];\n // No bearer token has been sent\n if (!credentials || scheme.toLowerCase() !== 'bearer') {\n throw new WebError({\n message: 'No bearer token found',\n errorCode: 'unauthorized',\n statusCode: 401,\n data: { scheme }\n });\n }\n reportDebug({\n namespace,\n message: 'Performing JWT authentication',\n data: { scheme }\n });\n // Verify token and extract its payload\n const payload = await jwtVerify(credentials, TokenIssuer.AUTH_SERVICE);\n reportDebug({\n namespace,\n message: 'Extracted user data from JWT',\n data: { payload }\n });\n // The user object on the request is hydrated with data from the token\n request.user = payload.user;\n\n next();\n };\n}\n"],"mappings":";;;;;;AACA,IAAAA,MAAA,GAAAC,OAAA;AACA,IAAAC,YAAA,GAAAD,OAAA;AACA,IAAAE,SAAA,GAAAF,OAAA;AACA,IAAAG,IAAA,GAAAH,OAAA;AACA,IAAAI,OAAA,GAAAJ,OAAA;AACAA,OAAA;AAEA,MAAMK,SAAS,GAAG,2BAA2B;;AAE7C;AACO,SAASC,OAAOA,CAAA,EAIJ;EACjB,IAAI,CAACC,OAAO,CAACC,GAAG,CAACC,qBAAqB,EACpC,MAAM,IAAIC,wBAAW,CAAC;IACpBC,OAAO,EAAE;EACX,CAAC,CAAC;EAEJ,OAAO,OACLC,OAAgB,EAChBC,QAAkB,EAClBC,IAAkB,KACA;IAClB,MAAM,CAACC,MAAM,EAAEC,WAAW,CAAC,GACzBJ,OAAO,CAACK,OAAO,EAAEC,aAAa,EAAEC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE;IAClD;IACA,IAAI,CAACH,WAAW,IAAID,MAAM,CAACK,WAAW,CAAC,CAAC,KAAK,QAAQ,EAAE;MACrD,MAAM,IAAIC,kBAAQ,CAAC;QACjBV,OAAO,EAAE,uBAAuB;QAChCW,SAAS,EAAE,cAAc;QACzBC,UAAU,EAAE,GAAG;QACfC,IAAI,EAAE;UAAET;QAAO;MACjB,CAAC,CAAC;IACJ;IACA,IAAAU,mBAAW,EAAC;MACVpB,SAAS;MACTM,OAAO,EAAE,+BAA+B;MACxCa,IAAI,EAAE;QAAET;MAAO;IACjB,CAAC,CAAC;IACF;IACA,MAAMW,OAAO,GAAG,MAAM,IAAAC,cAAS,EAACX,WAAW,EAAEY,kBAAW,CAACC,YAAY,CAAC;IACtE,IAAAJ,mBAAW,EAAC;MACVpB,SAAS;MACTM,OAAO,EAAE,8BAA8B;MACvCa,IAAI,EAAE;QAAEE;MAAQ;IAClB,CAAC,CAAC;IACF;IACAd,OAAO,CAACkB,IAAI,GAAGJ,OAAO,CAACI,IAAI;IAE3BhB,IAAI,CAAC,CAAC;EACR,CAAC;AACH","ignoreList":[]}
|
|
@@ -4,10 +4,18 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.authStatic = authStatic;
|
|
7
|
+
var _crypto = require("crypto");
|
|
7
8
|
var _WebError = require("../../entities/WebError");
|
|
8
9
|
var _utils = require("../../utils");
|
|
9
10
|
const namespace = 'engine:middleware:authStatic';
|
|
10
11
|
|
|
12
|
+
/** Constant time string comparison to avoid leaking the token through timing */
|
|
13
|
+
function safeCompare(a, b) {
|
|
14
|
+
const bufferA = Buffer.from(a);
|
|
15
|
+
const bufferB = Buffer.from(b);
|
|
16
|
+
return bufferA.length === bufferB.length && (0, _crypto.timingSafeEqual)(bufferA, bufferB);
|
|
17
|
+
}
|
|
18
|
+
|
|
11
19
|
/**
|
|
12
20
|
* Generate a middleware to authenticate the client connection with a static token
|
|
13
21
|
* @return {Function}
|
|
@@ -25,7 +33,7 @@ function authStatic() {
|
|
|
25
33
|
errorCode: 'unauthorized',
|
|
26
34
|
message: 'No bearer token found',
|
|
27
35
|
data: {
|
|
28
|
-
|
|
36
|
+
scheme
|
|
29
37
|
}
|
|
30
38
|
});
|
|
31
39
|
}
|
|
@@ -33,19 +41,15 @@ function authStatic() {
|
|
|
33
41
|
namespace,
|
|
34
42
|
message: 'Performing Static authentication',
|
|
35
43
|
data: {
|
|
36
|
-
scheme
|
|
37
|
-
credentials
|
|
44
|
+
scheme
|
|
38
45
|
}
|
|
39
46
|
});
|
|
40
47
|
// Verify token
|
|
41
|
-
if (credentials
|
|
48
|
+
if (!safeCompare(credentials, process.env.STATIC_TOKEN)) {
|
|
42
49
|
throw new _WebError.WebError({
|
|
43
50
|
statusCode: 401,
|
|
44
51
|
errorCode: 'unauthorized',
|
|
45
|
-
message: 'Invalid token supplied'
|
|
46
|
-
data: {
|
|
47
|
-
credentials
|
|
48
|
-
}
|
|
52
|
+
message: 'Invalid token supplied'
|
|
49
53
|
});
|
|
50
54
|
}
|
|
51
55
|
next();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authStatic.js","names":["
|
|
1
|
+
{"version":3,"file":"authStatic.js","names":["_crypto","require","_WebError","_utils","namespace","safeCompare","a","b","bufferA","Buffer","from","bufferB","length","timingSafeEqual","authStatic","assertEnvironment","STATIC_TOKEN","envAssert","isString","req","res","next","scheme","credentials","headers","authorization","split","toLowerCase","WebError","statusCode","errorCode","message","data","reportDebug","process","env"],"sources":["../../../src/middleware/authStatic/authStatic.ts"],"sourcesContent":["import { timingSafeEqual } from 'crypto';\nimport { Request, Response, NextFunction } from 'express';\nimport { WebError } from 'entities/WebError';\nimport { assertEnvironment, envAssert, reportDebug } from 'utils';\n\nconst namespace = 'engine:middleware:authStatic';\n\n/** Constant time string comparison to avoid leaking the token through timing */\nfunction safeCompare(a: string, b: string): boolean {\n const bufferA = Buffer.from(a);\n const bufferB = Buffer.from(b);\n return bufferA.length === bufferB.length && timingSafeEqual(bufferA, bufferB);\n}\n\n/**\n * Generate a middleware to authenticate the client connection with a static token\n * @return {Function}\n */\nexport function authStatic(): (\n req: Request,\n res: Response,\n next: NextFunction\n) => void {\n assertEnvironment({ STATIC_TOKEN: envAssert.isString() });\n\n return (req: Request, res: Response, next: NextFunction): void => {\n const [scheme, credentials] = req.headers?.authorization?.split(' ') ?? [];\n // No bearer token has been sent\n if (!credentials || scheme.toLowerCase() !== 'bearer') {\n throw new WebError({\n statusCode: 401,\n errorCode: 'unauthorized',\n message: 'No bearer token found',\n data: { scheme }\n });\n }\n reportDebug({\n namespace,\n message: 'Performing Static authentication',\n data: { scheme }\n });\n // Verify token\n if (!safeCompare(credentials, process.env.STATIC_TOKEN as string)) {\n throw new WebError({\n statusCode: 401,\n errorCode: 'unauthorized',\n message: 'Invalid token supplied'\n });\n }\n next();\n };\n}\n"],"mappings":";;;;;;AAAA,IAAAA,OAAA,GAAAC,OAAA;AAEA,IAAAC,SAAA,GAAAD,OAAA;AACA,IAAAE,MAAA,GAAAF,OAAA;AAEA,MAAMG,SAAS,GAAG,8BAA8B;;AAEhD;AACA,SAASC,WAAWA,CAACC,CAAS,EAAEC,CAAS,EAAW;EAClD,MAAMC,OAAO,GAAGC,MAAM,CAACC,IAAI,CAACJ,CAAC,CAAC;EAC9B,MAAMK,OAAO,GAAGF,MAAM,CAACC,IAAI,CAACH,CAAC,CAAC;EAC9B,OAAOC,OAAO,CAACI,MAAM,KAAKD,OAAO,CAACC,MAAM,IAAI,IAAAC,uBAAe,EAACL,OAAO,EAAEG,OAAO,CAAC;AAC/E;;AAEA;AACA;AACA;AACA;AACO,SAASG,UAAUA,CAAA,EAIhB;EACR,IAAAC,wBAAiB,EAAC;IAAEC,YAAY,EAAEC,gBAAS,CAACC,QAAQ,CAAC;EAAE,CAAC,CAAC;EAEzD,OAAO,CAACC,GAAY,EAAEC,GAAa,EAAEC,IAAkB,KAAW;IAChE,MAAM,CAACC,MAAM,EAAEC,WAAW,CAAC,GAAGJ,GAAG,CAACK,OAAO,EAAEC,aAAa,EAAEC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE;IAC1E;IACA,IAAI,CAACH,WAAW,IAAID,MAAM,CAACK,WAAW,CAAC,CAAC,KAAK,QAAQ,EAAE;MACrD,MAAM,IAAIC,kBAAQ,CAAC;QACjBC,UAAU,EAAE,GAAG;QACfC,SAAS,EAAE,cAAc;QACzBC,OAAO,EAAE,uBAAuB;QAChCC,IAAI,EAAE;UAAEV;QAAO;MACjB,CAAC,CAAC;IACJ;IACA,IAAAW,kBAAW,EAAC;MACV7B,SAAS;MACT2B,OAAO,EAAE,kCAAkC;MAC3CC,IAAI,EAAE;QAAEV;MAAO;IACjB,CAAC,CAAC;IACF;IACA,IAAI,CAACjB,WAAW,CAACkB,WAAW,EAAEW,OAAO,CAACC,GAAG,CAACnB,YAAsB,CAAC,EAAE;MACjE,MAAM,IAAIY,kBAAQ,CAAC;QACjBC,UAAU,EAAE,GAAG;QACfC,SAAS,EAAE,cAAc;QACzBC,OAAO,EAAE;MACX,CAAC,CAAC;IACJ;IACAV,IAAI,CAAC,CAAC;EACR,CAAC;AACH","ignoreList":[]}
|
|
@@ -71,7 +71,8 @@ function authTlsMiddleware(request, response, next, options = {}) {
|
|
|
71
71
|
// Extract the subjects for the client certificate
|
|
72
72
|
const clientCertificate = socket.getPeerCertificate();
|
|
73
73
|
const subjects = [];
|
|
74
|
-
|
|
74
|
+
const commonName = clientCertificate.subject.CN;
|
|
75
|
+
if (commonName) subjects.push(...(Array.isArray(commonName) ? commonName : [commonName]));
|
|
75
76
|
if (clientCertificate.subjectaltname?.length) {
|
|
76
77
|
const altNames = clientCertificate.subjectaltname.split(',').map(subject => subject.trim()).map(value => value.split(':')[1]);
|
|
77
78
|
subjects.push(...altNames);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authTls.js","names":["_WebError","require","_checkEnvironment","_envAssert","_report","namespace","authTls","options","assertEnvironment","TLS_SERVER_KEY","envAssert","isPath","TLS_SERVER_CERT","request","response","next","authTlsMiddleware","allowedClientHosts","loadEnv","process","env","ALLOWED_CLIENT_HOSTS","isHostList","split","map","value","trim","socket","reportDebug","message","data","authorized","authorizationError","peerCert","getPeerCertificate","WebError","errorCode","statusCode","clientCertificate","subjects","subject","CN","push","subjectaltname","length","altNames","whitelist","certificate","allowed","includes","hosts"],"sources":["../../../src/middleware/authTls/authTls.ts"],"sourcesContent":["import { TLSSocket } from 'tls';\nimport { Request, Response, NextFunction } from 'express';\nimport { EndpointAuthParams, EndpointAuthType } from 'entities/Endpoint';\nimport { WebError } from 'entities/WebError';\nimport { assertEnvironment } from 'utils/checkEnvironment';\nimport { envAssert } from 'utils/envAssert';\nimport { reportDebug } from 'utils/report';\nimport {} from './authTls.types';\n\nconst namespace = 'engine:middleware:authTls';\n\n/**\n * Returns a middleware for TLS authentication\n * Both client and server must have a certificate signed by the same CA\n * Host name of the client is added to the express request object under 'request.user.host'\n * A limited number of hosts can be specified with the environment variable ALLOWED_CLIENT_HOSTS\n * @param {Object} options\n * @param {Array<String>} options.whitelist - List of certificate Common Name or Alt Name that are permitted to make requests to this endpoint\n * @return {Function}\n */\nexport function authTls(\n options: EndpointAuthParams<EndpointAuthType.TLS> = {}\n): (request: Request, response: Response, next: NextFunction) => void {\n assertEnvironment({\n TLS_SERVER_KEY: envAssert.isPath(),\n TLS_SERVER_CERT: envAssert.isPath()\n });\n\n return (request: Request, response: Response, next: NextFunction): void => {\n authTlsMiddleware(request, response, next, options);\n };\n}\n\nlet allowedClientHosts: Array<string> = [];\n\n/** Load TLS auth related environment variables */\nexport function loadEnv(): void {\n if (process.env.ALLOWED_CLIENT_HOSTS) {\n assertEnvironment({ ALLOWED_CLIENT_HOSTS: envAssert.isHostList() });\n allowedClientHosts = process.env.ALLOWED_CLIENT_HOSTS.split(',').map(\n (value) => value.trim()\n );\n } else {\n allowedClientHosts = [];\n }\n}\n\nloadEnv();\n\n/** Middleware that handles mTLS based authentication */\nexport function authTlsMiddleware(\n request: Request,\n response: Response,\n next: NextFunction,\n options: EndpointAuthParams<EndpointAuthType.TLS> = {}\n): void {\n const socket = request.socket as TLSSocket;\n\n reportDebug({\n namespace,\n message: 'TLS socket auth state',\n data: {\n authorized: socket?.authorized,\n authorizationError: (\n socket as TLSSocket & { authorizationError?: string }\n )?.authorizationError,\n peerCert: socket?.getPeerCertificate?.()\n }\n });\n\n // Check if the client's tls certificate is valid\n if (!socket?.authorized) {\n throw new WebError({\n errorCode: 'unauthorized',\n statusCode: 401,\n message: 'TLS certificate could not be validated'\n });\n }\n\n // Extract the subjects for the client certificate\n const clientCertificate = socket.getPeerCertificate();\n const subjects = [];\n
|
|
1
|
+
{"version":3,"file":"authTls.js","names":["_WebError","require","_checkEnvironment","_envAssert","_report","namespace","authTls","options","assertEnvironment","TLS_SERVER_KEY","envAssert","isPath","TLS_SERVER_CERT","request","response","next","authTlsMiddleware","allowedClientHosts","loadEnv","process","env","ALLOWED_CLIENT_HOSTS","isHostList","split","map","value","trim","socket","reportDebug","message","data","authorized","authorizationError","peerCert","getPeerCertificate","WebError","errorCode","statusCode","clientCertificate","subjects","commonName","subject","CN","push","Array","isArray","subjectaltname","length","altNames","whitelist","certificate","allowed","includes","hosts"],"sources":["../../../src/middleware/authTls/authTls.ts"],"sourcesContent":["import { TLSSocket } from 'tls';\nimport { Request, Response, NextFunction } from 'express';\nimport { EndpointAuthParams, EndpointAuthType } from 'entities/Endpoint';\nimport { WebError } from 'entities/WebError';\nimport { assertEnvironment } from 'utils/checkEnvironment';\nimport { envAssert } from 'utils/envAssert';\nimport { reportDebug } from 'utils/report';\nimport {} from './authTls.types';\n\nconst namespace = 'engine:middleware:authTls';\n\n/**\n * Returns a middleware for TLS authentication\n * Both client and server must have a certificate signed by the same CA\n * Host name of the client is added to the express request object under 'request.user.host'\n * A limited number of hosts can be specified with the environment variable ALLOWED_CLIENT_HOSTS\n * @param {Object} options\n * @param {Array<String>} options.whitelist - List of certificate Common Name or Alt Name that are permitted to make requests to this endpoint\n * @return {Function}\n */\nexport function authTls(\n options: EndpointAuthParams<EndpointAuthType.TLS> = {}\n): (request: Request, response: Response, next: NextFunction) => void {\n assertEnvironment({\n TLS_SERVER_KEY: envAssert.isPath(),\n TLS_SERVER_CERT: envAssert.isPath()\n });\n\n return (request: Request, response: Response, next: NextFunction): void => {\n authTlsMiddleware(request, response, next, options);\n };\n}\n\nlet allowedClientHosts: Array<string> = [];\n\n/** Load TLS auth related environment variables */\nexport function loadEnv(): void {\n if (process.env.ALLOWED_CLIENT_HOSTS) {\n assertEnvironment({ ALLOWED_CLIENT_HOSTS: envAssert.isHostList() });\n allowedClientHosts = process.env.ALLOWED_CLIENT_HOSTS.split(',').map(\n (value) => value.trim()\n );\n } else {\n allowedClientHosts = [];\n }\n}\n\nloadEnv();\n\n/** Middleware that handles mTLS based authentication */\nexport function authTlsMiddleware(\n request: Request,\n response: Response,\n next: NextFunction,\n options: EndpointAuthParams<EndpointAuthType.TLS> = {}\n): void {\n const socket = request.socket as TLSSocket;\n\n reportDebug({\n namespace,\n message: 'TLS socket auth state',\n data: {\n authorized: socket?.authorized,\n authorizationError: (\n socket as TLSSocket & { authorizationError?: string }\n )?.authorizationError,\n peerCert: socket?.getPeerCertificate?.()\n }\n });\n\n // Check if the client's tls certificate is valid\n if (!socket?.authorized) {\n throw new WebError({\n errorCode: 'unauthorized',\n statusCode: 401,\n message: 'TLS certificate could not be validated'\n });\n }\n\n // Extract the subjects for the client certificate\n const clientCertificate = socket.getPeerCertificate();\n const subjects: Array<string> = [];\n const commonName = clientCertificate.subject.CN;\n if (commonName)\n subjects.push(...(Array.isArray(commonName) ? commonName : [commonName]));\n if (clientCertificate.subjectaltname?.length) {\n const altNames = clientCertificate.subjectaltname\n .split(',')\n .map((subject) => subject.trim())\n .map((value) => value.split(':')[1]);\n subjects.push(...altNames);\n }\n\n const whitelist = options.whitelist ?? allowedClientHosts;\n reportDebug({\n namespace,\n message: 'Performing TLS authentication',\n data: { certificate: clientCertificate, whitelist, subjects }\n });\n // Check that the subject is authorized\n if (whitelist?.length) {\n let allowed = false;\n for (const subject of subjects) {\n if (whitelist.includes(subject)) {\n allowed = true;\n break;\n }\n }\n // Throw error if there is no subject in the whitelist\n if (!allowed)\n throw new WebError({\n errorCode: 'unauthorized',\n statusCode: 401,\n message: 'Client host is not in the whitelist',\n data: { subjects }\n });\n }\n // Pas subjects down\n request.hosts = subjects;\n next();\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -18,6 +18,18 @@ const FILE_DIR = _path.default.join(process.env.FILE_STORAGE_FOLDER || './upload
|
|
|
18
18
|
_fsExtra.default.ensureDirSync(FILE_DIR);
|
|
19
19
|
_fsExtra.default.ensureDirSync(TEMP_DIR);
|
|
20
20
|
const storage = _multer.default.memoryStorage();
|
|
21
|
+
|
|
22
|
+
/** Default maximum size of a single chunk when no limit is configured */
|
|
23
|
+
const DEFAULT_MAX_CHUNK_SIZE = 32 * 1024 * 1024;
|
|
24
|
+
/** Maximum number of chunks a single upload can be split into */
|
|
25
|
+
const MAX_CHUNKS = Number(process.env.UPLOAD_MAX_CHUNKS) || 10000;
|
|
26
|
+
/** Upload session IDs are restricted to a safe charset so they can be used as a folder name */
|
|
27
|
+
const SAFE_ID_PATTERN = /^[A-Za-z0-9_-]{1,128}$/;
|
|
28
|
+
|
|
29
|
+
/** Check that a client supplied file name cannot escape its destination folder */
|
|
30
|
+
function isSafeFilename(filename) {
|
|
31
|
+
return filename.length > 0 && filename.length <= 255 && filename !== '.' && filename !== '..' && !/[/\\\0]/.test(filename) && _path.default.basename(filename) === filename;
|
|
32
|
+
}
|
|
21
33
|
function multiPartFileUploader(filesOption, validator) {
|
|
22
34
|
return async (req, res, next) => {
|
|
23
35
|
try {
|
|
@@ -25,10 +37,27 @@ function multiPartFileUploader(filesOption, validator) {
|
|
|
25
37
|
namespace,
|
|
26
38
|
message: 'Starting file upload middleware'
|
|
27
39
|
});
|
|
40
|
+
|
|
41
|
+
// Bound the size of a single chunk held in memory
|
|
42
|
+
const chunkSizeLimit = filesOption?.maxSize && (0, _bytes.default)(filesOption.maxSize) || process.env.UPLOAD_MAX_SIZE && (0, _bytes.default)(process.env.UPLOAD_MAX_SIZE) || DEFAULT_MAX_CHUNK_SIZE;
|
|
28
43
|
const uploadSingle = (0, _multer.default)({
|
|
29
|
-
storage
|
|
44
|
+
storage,
|
|
45
|
+
limits: {
|
|
46
|
+
fileSize: chunkSizeLimit,
|
|
47
|
+
files: 1
|
|
48
|
+
}
|
|
30
49
|
}).single('file');
|
|
31
50
|
uploadSingle(req, res, async err => {
|
|
51
|
+
if (err?.code === 'LIMIT_FILE_SIZE') {
|
|
52
|
+
return next(new _WebError.WebError({
|
|
53
|
+
statusCode: 413,
|
|
54
|
+
errorCode: 'chunk-too-large',
|
|
55
|
+
message: 'Uploaded chunk exceeds the maximum allowed size',
|
|
56
|
+
data: {
|
|
57
|
+
maxSize: chunkSizeLimit
|
|
58
|
+
}
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
32
61
|
if (err) {
|
|
33
62
|
(0, _utils.reportError)('Error uploading chunk');
|
|
34
63
|
return next(new _WebError.WebError({
|
|
@@ -93,6 +122,24 @@ function multiPartFileUploader(filesOption, validator) {
|
|
|
93
122
|
}
|
|
94
123
|
}));
|
|
95
124
|
}
|
|
125
|
+
|
|
126
|
+
// Client supplied values are used to build file system paths, they must be strictly validated
|
|
127
|
+
// to prevent path traversal (e.g. uniqueID="../../etc") and resource exhaustion
|
|
128
|
+
const chunkIndexNum = Number(chunkIndex);
|
|
129
|
+
const totalChunksNum = Number(totalChunks);
|
|
130
|
+
if (typeof uniqueID !== 'string' || !SAFE_ID_PATTERN.test(uniqueID) || typeof filename !== 'string' || !isSafeFilename(filename) || !Number.isInteger(totalChunksNum) || totalChunksNum < 1 || totalChunksNum > MAX_CHUNKS || !Number.isInteger(chunkIndexNum) || chunkIndexNum < 0 || chunkIndexNum >= totalChunksNum) {
|
|
131
|
+
return next(new _WebError.WebError({
|
|
132
|
+
statusCode: 400,
|
|
133
|
+
errorCode: 'invalid-field',
|
|
134
|
+
message: 'Invalid chunk upload parameters',
|
|
135
|
+
data: {
|
|
136
|
+
filename,
|
|
137
|
+
totalChunks,
|
|
138
|
+
chunkIndex,
|
|
139
|
+
uniqueID
|
|
140
|
+
}
|
|
141
|
+
}));
|
|
142
|
+
}
|
|
96
143
|
if (filesOption?.maxSize && req.file.size > ((0, _bytes.default)(filesOption.maxSize) || 0)) {
|
|
97
144
|
return next(new _WebError.WebError({
|
|
98
145
|
statusCode: 400,
|
|
@@ -106,7 +153,7 @@ function multiPartFileUploader(filesOption, validator) {
|
|
|
106
153
|
}
|
|
107
154
|
const sessionFolder = _path.default.join(TEMP_DIR, uniqueID);
|
|
108
155
|
_fsExtra.default.ensureDirSync(sessionFolder);
|
|
109
|
-
const chunkPath = _path.default.join(sessionFolder, `${filename}.part${
|
|
156
|
+
const chunkPath = _path.default.join(sessionFolder, `${filename}.part${chunkIndexNum}`);
|
|
110
157
|
try {
|
|
111
158
|
(0, _utils.reportDebug)({
|
|
112
159
|
namespace,
|
|
@@ -136,7 +183,7 @@ function multiPartFileUploader(filesOption, validator) {
|
|
|
136
183
|
totalChunks
|
|
137
184
|
}
|
|
138
185
|
});
|
|
139
|
-
if (uploadedChunks.length ===
|
|
186
|
+
if (uploadedChunks.length === totalChunksNum) {
|
|
140
187
|
try {
|
|
141
188
|
const finalFilePath = _path.default.join(FILE_DIR, `${uniqueID}_${filename}`);
|
|
142
189
|
await mergeChunks(filename, totalChunks, sessionFolder, finalFilePath);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"multiPartFileUploader.js","names":["_multer","_interopRequireDefault","require","_fsExtra","_path","_utils","_WebError","_validate","_bytes","namespace","TEMP_DIR","path","join","process","env","FILE_STORAGE_FOLDER","FILE_DIR","fs","ensureDirSync","storage","multer","memoryStorage","multiPartFileUploader","filesOption","validator","req","res","next","reportDebug","message","uploadSingle","single","err","reportError","WebError","statusCode","errorCode","data","error","validate","filename","totalChunks","chunkIndex","uniqueID","body","undefined","file","receivedChunk","maxSize","size","bytes","receivedSize","sessionFolder","chunkPath","writeFile","buffer","writeErr","uploadedChunks","readdir","length","parseInt","finalFilePath","mergeChunks","multipartFile","isPending","originalname","filePath","mergeErr","writeStream","createWriteStream","totalChunksNum","chunks","i","partPath","chunkData","readFile","push","remove","catch","cleanupErr","write","end","Promise","resolve","reject","on","then"],"sources":["../../../src/middleware/multiPartFileUploader/multiPartFileUploader.ts"],"sourcesContent":["import { Request, Response, NextFunction } from 'express';\nimport multer from 'multer';\nimport fs from 'fs-extra';\nimport path from 'path';\nimport { Schema } from 'express-validator';\nimport { reportDebug, reportError } from 'utils';\nimport { WebError } from 'entities/WebError';\nimport { validate } from './validate';\nimport { MultiPartFileUploaderConfig } from './multiPartFileUploader.types';\nimport bytes from 'bytes';\n\nconst namespace = 'engine:middleware:multiPartFileUploader';\n\nconst TEMP_DIR: string = path.join(\n process.env.FILE_STORAGE_FOLDER || './uploads',\n 'temp_chunks'\n);\nconst FILE_DIR: string = path.join(\n process.env.FILE_STORAGE_FOLDER || './uploads',\n 'completed_files'\n);\n\nfs.ensureDirSync(FILE_DIR);\nfs.ensureDirSync(TEMP_DIR);\n\nconst storage = multer.memoryStorage();\n\nexport function multiPartFileUploader(\n filesOption: MultiPartFileUploaderConfig,\n validator: Schema\n) {\n return async (\n req: Request,\n res: Response,\n next: NextFunction\n ): Promise<void> => {\n try {\n reportDebug({ namespace, message: 'Starting file upload middleware' });\n\n const uploadSingle = multer({ storage }).single('file');\n uploadSingle(req, res, async (err) => {\n if (err) {\n reportError('Error uploading chunk');\n return next(\n new WebError({\n statusCode: 500,\n errorCode: 'upload-failed',\n message: 'Error uploading file chunk',\n data: { error: err.message }\n })\n );\n }\n\n try {\n reportDebug({ namespace, message: 'Validating request' });\n await validate(req, validator);\n } catch (error) {\n reportDebug({\n namespace,\n message: 'Validation failed',\n data: { error }\n });\n return next(\n new WebError({\n statusCode: 400,\n errorCode: 'validation-failed',\n message: 'Validation failed for file upload',\n data: { error: (error as Error).message }\n })\n );\n }\n\n const { filename, totalChunks, chunkIndex, uniqueID } = req.body;\n reportDebug({\n namespace,\n message: 'Received chunk data',\n data: { filename, totalChunks, chunkIndex, uniqueID }\n });\n\n if (\n !filename ||\n !totalChunks ||\n chunkIndex === undefined ||\n !uniqueID ||\n !req.file\n ) {\n return next(\n new WebError({\n statusCode: 400,\n errorCode: 'missing-field',\n message: 'Missing required fields',\n data: {\n filename,\n totalChunks,\n chunkIndex,\n uniqueID,\n receivedChunk: !!req.file\n }\n })\n );\n }\n\n if (\n filesOption?.maxSize &&\n req.file.size > (bytes(filesOption.maxSize) || 0)\n ) {\n return next(\n new WebError({\n statusCode: 400,\n errorCode: 'chunk-too-large',\n message: 'Uploaded chunk exceeds the maximum allowed size',\n data: {\n maxSize: filesOption.maxSize,\n receivedSize: req.file.size\n }\n })\n );\n }\n\n const sessionFolder = path.join(TEMP_DIR, uniqueID);\n fs.ensureDirSync(sessionFolder);\n const chunkPath = path.join(\n sessionFolder,\n `${filename}.part${chunkIndex}`\n );\n\n try {\n reportDebug({\n namespace,\n message: 'Writing chunk to disk',\n data: { chunkPath }\n });\n await fs.writeFile(chunkPath, req.file.buffer);\n } catch (writeErr) {\n return next(\n new WebError({\n statusCode: 500,\n errorCode: 'chunk-write-failed',\n message: 'Failed to write chunk',\n data: { chunkIndex, error: (writeErr as Error).message }\n })\n );\n }\n\n const uploadedChunks = await fs.readdir(sessionFolder);\n reportDebug({\n namespace,\n message: 'Uploaded chunk count',\n data: { uploadedChunks: uploadedChunks.length, totalChunks }\n });\n\n if (uploadedChunks.length === parseInt(totalChunks, 10)) {\n try {\n const finalFilePath = path.join(\n FILE_DIR,\n `${uniqueID}_${filename}`\n );\n await mergeChunks(\n filename,\n totalChunks,\n sessionFolder,\n finalFilePath\n );\n req.multipartFile = {\n isPending: false,\n originalname: filename,\n uniqueID,\n filePath: finalFilePath\n };\n } catch (mergeErr) {\n return next(\n new WebError({\n statusCode: 500,\n errorCode: 'file-merge-failed',\n message: 'Error merging file chunks',\n data: { error: (mergeErr as Error).message }\n })\n );\n }\n return next();\n }\n\n req.multipartFile = {\n isPending: true,\n originalname: filename,\n uniqueID,\n chunkIndex,\n totalChunks\n };\n next();\n });\n } catch (error) {\n next(\n new WebError({\n statusCode: 500,\n errorCode: 'unexpected-error',\n message: 'Unexpected error in file upload',\n data: { error: (error as Error).message }\n })\n );\n }\n };\n}\n\nasync function mergeChunks(\n filename: string,\n totalChunks: string | number,\n sessionFolder: string,\n finalFilePath: string\n): Promise<void> {\n const writeStream = fs.createWriteStream(finalFilePath);\n reportDebug({\n namespace,\n message: 'Merging file chunks',\n data: { finalFilePath }\n });\n\n const totalChunksNum = parseInt(totalChunks as string, 10);\n const chunks: Buffer[] = [];\n\n // Read all chunks into memory first\n for (let i = 0; i < totalChunksNum; i++) {\n const partPath = path.join(sessionFolder, `${filename}.part${i}`);\n try {\n const chunkData = await fs.readFile(partPath);\n chunks.push(chunkData);\n } catch (error) {\n // Cleanup on error\n await fs.remove(sessionFolder).catch((cleanupErr: Error) =>\n reportError(`Error cleaning up folder ${sessionFolder}: ${cleanupErr.message}`)\n );\n throw new WebError({\n statusCode: 500,\n errorCode: 'chunk-read-failed',\n message: `Error reading chunk ${i}`,\n data: { error: (error as Error).message }\n });\n }\n }\n\n // Write all chunks sequentially\n for (const chunkData of chunks) {\n writeStream.write(chunkData);\n }\n\n // Remove temp files\n for (let i = 0; i < totalChunksNum; i++) {\n const partPath = path.join(sessionFolder, `${filename}.part${i}`);\n fs.remove(partPath).catch((err: Error) =>\n reportError(`Error removing part ${partPath}: ${err.message}`)\n );\n }\n\n writeStream.end();\n return new Promise<void>((resolve, reject) => {\n writeStream.on('finish', () => {\n // Clean up temp folder after successful merge\n fs.remove(sessionFolder)\n .then(() => resolve(undefined))\n .catch((cleanupErr: Error) => {\n reportError(\n `Error cleaning up temp folder ${sessionFolder}: ${cleanupErr.message}`\n );\n resolve(undefined); // Don't fail the merge if cleanup fails\n });\n });\n writeStream.on('error', (error) => {\n // Cleanup on write error\n fs.remove(sessionFolder).catch((cleanupErr: Error) =>\n reportError(`Error cleaning up folder after write error: ${cleanupErr.message}`)\n );\n reject(\n new WebError({\n statusCode: 500,\n errorCode: 'file-merge-error',\n message: 'Error merging file chunks',\n data: { error: (error as Error).message }\n })\n );\n });\n });\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"multiPartFileUploader.js","names":["_multer","_interopRequireDefault","require","_fsExtra","_path","_utils","_WebError","_validate","_bytes","namespace","TEMP_DIR","path","join","process","env","FILE_STORAGE_FOLDER","FILE_DIR","fs","ensureDirSync","storage","multer","memoryStorage","DEFAULT_MAX_CHUNK_SIZE","MAX_CHUNKS","Number","UPLOAD_MAX_CHUNKS","SAFE_ID_PATTERN","isSafeFilename","filename","length","test","basename","multiPartFileUploader","filesOption","validator","req","res","next","reportDebug","message","chunkSizeLimit","maxSize","bytes","UPLOAD_MAX_SIZE","uploadSingle","limits","fileSize","files","single","err","code","WebError","statusCode","errorCode","data","reportError","error","validate","totalChunks","chunkIndex","uniqueID","body","undefined","file","receivedChunk","chunkIndexNum","totalChunksNum","isInteger","size","receivedSize","sessionFolder","chunkPath","writeFile","buffer","writeErr","uploadedChunks","readdir","finalFilePath","mergeChunks","multipartFile","isPending","originalname","filePath","mergeErr","writeStream","createWriteStream","parseInt","chunks","i","partPath","chunkData","readFile","push","remove","catch","cleanupErr","write","end","Promise","resolve","reject","on","then"],"sources":["../../../src/middleware/multiPartFileUploader/multiPartFileUploader.ts"],"sourcesContent":["import { Request, Response, NextFunction } from 'express';\nimport multer from 'multer';\nimport fs from 'fs-extra';\nimport path from 'path';\nimport { Schema } from 'express-validator';\nimport { reportDebug, reportError } from 'utils';\nimport { WebError } from 'entities/WebError';\nimport { validate } from './validate';\nimport { MultiPartFileUploaderConfig } from './multiPartFileUploader.types';\nimport bytes from 'bytes';\n\nconst namespace = 'engine:middleware:multiPartFileUploader';\n\nconst TEMP_DIR: string = path.join(\n process.env.FILE_STORAGE_FOLDER || './uploads',\n 'temp_chunks'\n);\nconst FILE_DIR: string = path.join(\n process.env.FILE_STORAGE_FOLDER || './uploads',\n 'completed_files'\n);\n\nfs.ensureDirSync(FILE_DIR);\nfs.ensureDirSync(TEMP_DIR);\n\nconst storage = multer.memoryStorage();\n\n/** Default maximum size of a single chunk when no limit is configured */\nconst DEFAULT_MAX_CHUNK_SIZE = 32 * 1024 * 1024;\n/** Maximum number of chunks a single upload can be split into */\nconst MAX_CHUNKS = Number(process.env.UPLOAD_MAX_CHUNKS) || 10000;\n/** Upload session IDs are restricted to a safe charset so they can be used as a folder name */\nconst SAFE_ID_PATTERN = /^[A-Za-z0-9_-]{1,128}$/;\n\n/** Check that a client supplied file name cannot escape its destination folder */\nfunction isSafeFilename(filename: string): boolean {\n return (\n filename.length > 0 &&\n filename.length <= 255 &&\n filename !== '.' &&\n filename !== '..' &&\n !/[/\\\\\\0]/.test(filename) &&\n path.basename(filename) === filename\n );\n}\n\nexport function multiPartFileUploader(\n filesOption: MultiPartFileUploaderConfig,\n validator: Schema\n) {\n return async (\n req: Request,\n res: Response,\n next: NextFunction\n ): Promise<void> => {\n try {\n reportDebug({ namespace, message: 'Starting file upload middleware' });\n\n // Bound the size of a single chunk held in memory\n const chunkSizeLimit =\n (filesOption?.maxSize && bytes(filesOption.maxSize)) ||\n (process.env.UPLOAD_MAX_SIZE && bytes(process.env.UPLOAD_MAX_SIZE)) ||\n DEFAULT_MAX_CHUNK_SIZE;\n const uploadSingle = multer({\n storage,\n limits: { fileSize: chunkSizeLimit, files: 1 }\n }).single('file');\n uploadSingle(req, res, async (err) => {\n if ((err as { code?: string } | undefined)?.code === 'LIMIT_FILE_SIZE') {\n return next(\n new WebError({\n statusCode: 413,\n errorCode: 'chunk-too-large',\n message: 'Uploaded chunk exceeds the maximum allowed size',\n data: { maxSize: chunkSizeLimit }\n })\n );\n }\n if (err) {\n reportError('Error uploading chunk');\n return next(\n new WebError({\n statusCode: 500,\n errorCode: 'upload-failed',\n message: 'Error uploading file chunk',\n data: { error: err.message }\n })\n );\n }\n\n try {\n reportDebug({ namespace, message: 'Validating request' });\n await validate(req, validator);\n } catch (error) {\n reportDebug({\n namespace,\n message: 'Validation failed',\n data: { error }\n });\n return next(\n new WebError({\n statusCode: 400,\n errorCode: 'validation-failed',\n message: 'Validation failed for file upload',\n data: { error: (error as Error).message }\n })\n );\n }\n\n const { filename, totalChunks, chunkIndex, uniqueID } = req.body;\n reportDebug({\n namespace,\n message: 'Received chunk data',\n data: { filename, totalChunks, chunkIndex, uniqueID }\n });\n\n if (\n !filename ||\n !totalChunks ||\n chunkIndex === undefined ||\n !uniqueID ||\n !req.file\n ) {\n return next(\n new WebError({\n statusCode: 400,\n errorCode: 'missing-field',\n message: 'Missing required fields',\n data: {\n filename,\n totalChunks,\n chunkIndex,\n uniqueID,\n receivedChunk: !!req.file\n }\n })\n );\n }\n\n // Client supplied values are used to build file system paths, they must be strictly validated\n // to prevent path traversal (e.g. uniqueID=\"../../etc\") and resource exhaustion\n const chunkIndexNum = Number(chunkIndex);\n const totalChunksNum = Number(totalChunks);\n if (\n typeof uniqueID !== 'string' ||\n !SAFE_ID_PATTERN.test(uniqueID) ||\n typeof filename !== 'string' ||\n !isSafeFilename(filename) ||\n !Number.isInteger(totalChunksNum) ||\n totalChunksNum < 1 ||\n totalChunksNum > MAX_CHUNKS ||\n !Number.isInteger(chunkIndexNum) ||\n chunkIndexNum < 0 ||\n chunkIndexNum >= totalChunksNum\n ) {\n return next(\n new WebError({\n statusCode: 400,\n errorCode: 'invalid-field',\n message: 'Invalid chunk upload parameters',\n data: { filename, totalChunks, chunkIndex, uniqueID }\n })\n );\n }\n\n if (\n filesOption?.maxSize &&\n req.file.size > (bytes(filesOption.maxSize) || 0)\n ) {\n return next(\n new WebError({\n statusCode: 400,\n errorCode: 'chunk-too-large',\n message: 'Uploaded chunk exceeds the maximum allowed size',\n data: {\n maxSize: filesOption.maxSize,\n receivedSize: req.file.size\n }\n })\n );\n }\n\n const sessionFolder = path.join(TEMP_DIR, uniqueID);\n fs.ensureDirSync(sessionFolder);\n const chunkPath = path.join(\n sessionFolder,\n `${filename}.part${chunkIndexNum}`\n );\n\n try {\n reportDebug({\n namespace,\n message: 'Writing chunk to disk',\n data: { chunkPath }\n });\n await fs.writeFile(chunkPath, req.file.buffer);\n } catch (writeErr) {\n return next(\n new WebError({\n statusCode: 500,\n errorCode: 'chunk-write-failed',\n message: 'Failed to write chunk',\n data: { chunkIndex, error: (writeErr as Error).message }\n })\n );\n }\n\n const uploadedChunks = await fs.readdir(sessionFolder);\n reportDebug({\n namespace,\n message: 'Uploaded chunk count',\n data: { uploadedChunks: uploadedChunks.length, totalChunks }\n });\n\n if (uploadedChunks.length === totalChunksNum) {\n try {\n const finalFilePath = path.join(\n FILE_DIR,\n `${uniqueID}_${filename}`\n );\n await mergeChunks(\n filename,\n totalChunks,\n sessionFolder,\n finalFilePath\n );\n req.multipartFile = {\n isPending: false,\n originalname: filename,\n uniqueID,\n filePath: finalFilePath\n };\n } catch (mergeErr) {\n return next(\n new WebError({\n statusCode: 500,\n errorCode: 'file-merge-failed',\n message: 'Error merging file chunks',\n data: { error: (mergeErr as Error).message }\n })\n );\n }\n return next();\n }\n\n req.multipartFile = {\n isPending: true,\n originalname: filename,\n uniqueID,\n chunkIndex,\n totalChunks\n };\n next();\n });\n } catch (error) {\n next(\n new WebError({\n statusCode: 500,\n errorCode: 'unexpected-error',\n message: 'Unexpected error in file upload',\n data: { error: (error as Error).message }\n })\n );\n }\n };\n}\n\nasync function mergeChunks(\n filename: string,\n totalChunks: string | number,\n sessionFolder: string,\n finalFilePath: string\n): Promise<void> {\n const writeStream = fs.createWriteStream(finalFilePath);\n reportDebug({\n namespace,\n message: 'Merging file chunks',\n data: { finalFilePath }\n });\n\n const totalChunksNum = parseInt(totalChunks as string, 10);\n const chunks: Buffer[] = [];\n\n // Read all chunks into memory first\n for (let i = 0; i < totalChunksNum; i++) {\n const partPath = path.join(sessionFolder, `${filename}.part${i}`);\n try {\n const chunkData = await fs.readFile(partPath);\n chunks.push(chunkData);\n } catch (error) {\n // Cleanup on error\n await fs.remove(sessionFolder).catch((cleanupErr: Error) =>\n reportError(`Error cleaning up folder ${sessionFolder}: ${cleanupErr.message}`)\n );\n throw new WebError({\n statusCode: 500,\n errorCode: 'chunk-read-failed',\n message: `Error reading chunk ${i}`,\n data: { error: (error as Error).message }\n });\n }\n }\n\n // Write all chunks sequentially\n for (const chunkData of chunks) {\n writeStream.write(chunkData);\n }\n\n // Remove temp files\n for (let i = 0; i < totalChunksNum; i++) {\n const partPath = path.join(sessionFolder, `${filename}.part${i}`);\n fs.remove(partPath).catch((err: Error) =>\n reportError(`Error removing part ${partPath}: ${err.message}`)\n );\n }\n\n writeStream.end();\n return new Promise<void>((resolve, reject) => {\n writeStream.on('finish', () => {\n // Clean up temp folder after successful merge\n fs.remove(sessionFolder)\n .then(() => resolve(undefined))\n .catch((cleanupErr: Error) => {\n reportError(\n `Error cleaning up temp folder ${sessionFolder}: ${cleanupErr.message}`\n );\n resolve(undefined); // Don't fail the merge if cleanup fails\n });\n });\n writeStream.on('error', (error) => {\n // Cleanup on write error\n fs.remove(sessionFolder).catch((cleanupErr: Error) =>\n reportError(`Error cleaning up folder after write error: ${cleanupErr.message}`)\n );\n reject(\n new WebError({\n statusCode: 500,\n errorCode: 'file-merge-error',\n message: 'Error merging file chunks',\n data: { error: (error as Error).message }\n })\n );\n });\n });\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -16,7 +16,8 @@ var _EngineError = require("../../entities/EngineError");
|
|
|
16
16
|
var _Sequelize = require("../../entities/Sequelize");
|
|
17
17
|
var _report = require("../report");
|
|
18
18
|
var _semverCompare = require("../semverCompare");
|
|
19
|
-
|
|
19
|
+
var cache;
|
|
20
|
+
function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var r, c, a = Object.defineProperty, n = { __proto__: null, default: e }; if (Object(e) !== e) return n; if (cache || "function" != typeof WeakMap || (cache = new WeakMap()), cache) { if (cache.has(e)) return cache.get(e); cache.set(e, n); } for (c in e) "default" !== c && {}.hasOwnProperty.call(e, c) && ((r = a && Object.getOwnPropertyDescriptor(e, c)) && (r.get || r.set) ? a(n, c, r) : n[c] = e[c]); return n; }
|
|
20
21
|
const namespace = 'engine:utils:dbMigration';
|
|
21
22
|
function getMigrationPaths() {
|
|
22
23
|
// We look between possibilities for the first directory that exists and matches our expectations
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dbMigration.js","names":["_fs","require","_path","_umzug","_migrateMetadataTable","_models","_EngineError","_Sequelize","_report","_semverCompare","_interopRequireWildcard","e","t","WeakMap","r","n","__esModule","o","i","f","__proto__","default","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","namespace","getMigrationPaths","prodDefaultPath","join","process","cwd","devDefaultPath","path","env","DB_MIGRATIONS_DIR","NODE_ENV","filter","existsSync","lstatSync","isDirectory","reportDebug","message","data","prodPath","devPath","customPath","map","p","runPendingMigrations","sequelize","EngineError","sqlHost","SQL_HOST","sqlDb","SQL_DB","sqlUser","SQL_USER","nodeEnv","migrateMetadataTable","migrationPaths","length","migrator","Umzug","migrations","glob","resolve","name","context","nameWithoutExtension","replace","migrationPromise","specifier","Promise","then","s","up","migration","down","getQueryInterface","storage","SequelizeStorage","logger","console","reportInfo","error","Error","String","stack","undefined","rollbackMigrations","BUILD_ID","SequelizeMeta","findAll","order","raw","rollbackUntil","find","version","semverCompare","buildId","to","testRecreateDB","options","query","SQL_TYPE","clean","testClearDB","transaction","clearDbTransaction","model","keys","models","includeMetadata","table","tableName"],"sources":["../../../src/utils/dbMigration/dbMigration.ts"],"sourcesContent":["import { existsSync, lstatSync } from 'fs';\nimport { join } from 'path';\nimport { Sequelize, Transaction } from 'sequelize';\nimport { Umzug, SequelizeStorage } from 'umzug';\nimport { ClearDbOptions, RecreateDbOptions } from './dbMigration.types';\nimport { migrateMetadataTable } from './migrateMetadataTable';\nimport { SequelizeMeta } from 'db/models';\nimport { EngineError } from 'entities/EngineError';\nimport { sequelize } from 'entities/Sequelize';\nimport { reportDebug, reportInfo } from 'utils/report';\nimport { semverCompare } from 'utils/semverCompare';\n\nconst namespace = 'engine:utils:dbMigration';\n\nfunction getMigrationPaths(): Array<string> | undefined {\n // We look between possibilities for the first directory that exists and matches our expectations\n // First a user specified one, then the production default, then the development default\n const prodDefaultPath = join(process.cwd(), './dist/db/migrations');\n const devDefaultPath = join(process.cwd(), './src/db/migrations');\n const path = [\n process.env.DB_MIGRATIONS_DIR,\n process.env.NODE_ENV === 'prod' ? prodDefaultPath : devDefaultPath\n ].filter((path) => path && existsSync(path) && lstatSync(path).isDirectory());\n reportDebug({\n namespace,\n message: 'Obtaining Umzug migration options',\n data: {\n path,\n prodPath: prodDefaultPath,\n devPath: devDefaultPath,\n customPath: process.env.DB_MIGRATIONS_DIR\n }\n });\n return path.map((p) =>\n process.env.NODE_ENV === 'prod' ? `${p}/*.js` : `${p}/*.ts`\n );\n}\n\n/** Run all the non-executed migrations one by one */\nexport async function runPendingMigrations(): Promise<void> {\n if (!sequelize)\n throw new EngineError({ message: 'Sequelize is not initialized' });\n \n try {\n reportDebug({\n namespace,\n message: 'Starting migration process',\n data: {\n sqlHost: process.env.SQL_HOST,\n sqlDb: process.env.SQL_DB,\n sqlUser: process.env.SQL_USER,\n nodeEnv: process.env.NODE_ENV\n }\n });\n\n // First make sure that the metadata table is up to date\n reportDebug({\n namespace,\n message: 'Migrating metadata table'\n });\n await migrateMetadataTable(sequelize);\n \n const migrationPaths = getMigrationPaths();\n\n if (!migrationPaths || migrationPaths.length <= 0) {\n reportDebug({\n namespace,\n message: 'No valid migration paths found.',\n data: { migrationPaths }\n });\n return;\n }\n\n // Process migrations sequentially to avoid race conditions\n for (const path of migrationPaths) {\n reportDebug({\n namespace,\n message: 'Processing migration path',\n data: { path }\n });\n\n const migrator = new Umzug({\n migrations: {\n glob: path,\n resolve: ({ name, path, context }) => {\n const nameWithoutExtension = name.replace(/\\.(ts|js)$/, '');\n \n // Use dynamic import for better TypeScript/ES module support\n const migrationPromise = import(path || '');\n return {\n name: nameWithoutExtension,\n up: async () => {\n reportDebug({\n namespace,\n message: `Running migration: ${nameWithoutExtension}`\n });\n const migration = await migrationPromise;\n return migration.up(context);\n },\n down: async () => {\n const migration = await migrationPromise;\n return migration.down(context);\n }\n };\n }\n },\n context: sequelize.getQueryInterface(),\n storage: new SequelizeStorage({ sequelize }),\n logger: console\n });\n\n reportDebug({\n namespace,\n message: 'Starting pending database migrations',\n data: { path }\n });\n \n await migrator.up();\n \n reportInfo({\n message: 'Database migration completed successfully',\n data: { path }\n });\n }\n } catch (error) {\n reportDebug({\n namespace,\n message: 'Migration failed with error',\n data: {\n error: error instanceof Error ? error.message : String(error),\n stack: error instanceof Error ? error.stack : undefined,\n sqlHost: process.env.SQL_HOST,\n sqlDb: process.env.SQL_DB\n }\n });\n throw error;\n }\n}\n\n/** Rollback all migrations up to a specified target version */\nexport async function rollbackMigrations(): Promise<void> {\n if (!sequelize)\n throw new EngineError({ message: 'Sequelize is not initialized' });\n if (!process.env.BUILD_ID)\n throw new EngineError({ message: 'BUILD_ID is not defined' });\n\n // Fetch executed migrations in ascending order\n const migrations = await SequelizeMeta.findAll({\n order: [['name', 'ASC']],\n raw: true\n });\n const rollbackUntil = migrations.find(\n (migration) =>\n migration.version &&\n semverCompare(migration.version, process.env.BUILD_ID as string) === 1\n );\n\n if (!rollbackUntil) {\n reportDebug({\n namespace,\n message: 'No rollback target found for the current BUILD_ID.',\n data: { buildId: process.env.BUILD_ID }\n });\n return;\n }\n\n const migrationPaths = getMigrationPaths();\n\n if (!migrationPaths || migrationPaths.length <= 0) {\n reportDebug({\n namespace,\n message: 'No valid migration paths found for rollback.',\n data: { migrationPaths }\n });\n return;\n }\n\n // Process rollback migrations sequentially\n for (const path of migrationPaths) {\n const migrator = new Umzug({\n migrations: {\n glob: path,\n resolve: ({ name, path, context }) => {\n // Strip file extension to ensure consistent naming across .ts and .js files\n const nameWithoutExtension = name.replace(/\\.(ts|js)$/, '');\n \n const migrationPromise = import(path || '');\n return {\n name: nameWithoutExtension,\n up: async () => {\n const migration = await migrationPromise;\n return migration.up(context);\n },\n down: async () => {\n const migration = await migrationPromise;\n return migration.down(context);\n }\n };\n }\n },\n context: sequelize.getQueryInterface(),\n storage: new SequelizeStorage({ sequelize }),\n logger: console\n });\n\n reportDebug({\n namespace,\n message: `Rolling back migrations up to: ${rollbackUntil.name}`,\n data: { path }\n });\n\n await migrator.down({ to: rollbackUntil.name });\n }\n}\n\n/**\n * Recreate the whole database\n * Quick way to make sure that we are working on a clean database\n * /!\\ Should only be used when running tests\n */\nexport async function testRecreateDB(\n options: RecreateDbOptions = {}\n): Promise<void> {\n if (!sequelize)\n throw new EngineError({ message: 'Sequelize is not initialized' });\n await sequelize.query(\n `DROP SCHEMA public${process.env.SQL_TYPE === 'postgres' ? ' CASCADE' : ''}`\n );\n await sequelize.query(`CREATE SCHEMA public`);\n if (!options.clean) await runPendingMigrations();\n}\n\n/**\n * Empty all the schemas in the database\n * Quick way to flush the database data while keep the structure\n * /!\\ Should only be used when running tests\n */\nexport async function testClearDB(options: ClearDbOptions = {}): Promise<void> {\n if (!sequelize)\n throw new EngineError({ message: 'Sequelize is not initialized' });\n await sequelize.transaction(clearDbTransaction(sequelize, options));\n}\n\n/** DB clearing transaction */\nfunction clearDbTransaction(sequelize: Sequelize, options: ClearDbOptions) {\n return async (t: Transaction): Promise<void> => {\n for (const model of Object.keys(sequelize.models)) {\n // We ignore the migration metadata schema\n if (!options.includeMetadata && model === 'sequelizeMeta') continue;\n const table = sequelize.models[model].tableName;\n await sequelize.query(\n `TRUNCATE ${table}${process.env.SQL_TYPE === 'postgres' ? ' CASCADE' : ''};`,\n {\n raw: true,\n transaction: t\n }\n );\n }\n };\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"dbMigration.js","names":["_fs","require","_path","_umzug","_migrateMetadataTable","_models","_EngineError","_Sequelize","_report","_semverCompare","cache","_interopRequireWildcard","e","t","__esModule","r","c","a","Object","defineProperty","n","__proto__","default","WeakMap","has","get","set","hasOwnProperty","call","getOwnPropertyDescriptor","namespace","getMigrationPaths","prodDefaultPath","join","process","cwd","devDefaultPath","path","env","DB_MIGRATIONS_DIR","NODE_ENV","filter","existsSync","lstatSync","isDirectory","reportDebug","message","data","prodPath","devPath","customPath","map","p","runPendingMigrations","sequelize","EngineError","sqlHost","SQL_HOST","sqlDb","SQL_DB","sqlUser","SQL_USER","nodeEnv","migrateMetadataTable","migrationPaths","length","migrator","Umzug","migrations","glob","resolve","name","context","nameWithoutExtension","replace","migrationPromise","specifier","Promise","then","s","up","migration","down","getQueryInterface","storage","SequelizeStorage","logger","console","reportInfo","error","Error","String","stack","undefined","rollbackMigrations","BUILD_ID","SequelizeMeta","findAll","order","raw","rollbackUntil","find","version","semverCompare","buildId","to","testRecreateDB","options","query","SQL_TYPE","clean","testClearDB","transaction","clearDbTransaction","model","keys","models","includeMetadata","table","tableName"],"sources":["../../../src/utils/dbMigration/dbMigration.ts"],"sourcesContent":["import { existsSync, lstatSync } from 'fs';\nimport { join } from 'path';\nimport { Sequelize, Transaction } from 'sequelize';\nimport { Umzug, SequelizeStorage } from 'umzug';\nimport { ClearDbOptions, RecreateDbOptions } from './dbMigration.types';\nimport { migrateMetadataTable } from './migrateMetadataTable';\nimport { SequelizeMeta } from 'db/models';\nimport { EngineError } from 'entities/EngineError';\nimport { sequelize } from 'entities/Sequelize';\nimport { reportDebug, reportInfo } from 'utils/report';\nimport { semverCompare } from 'utils/semverCompare';\n\nconst namespace = 'engine:utils:dbMigration';\n\nfunction getMigrationPaths(): Array<string> | undefined {\n // We look between possibilities for the first directory that exists and matches our expectations\n // First a user specified one, then the production default, then the development default\n const prodDefaultPath = join(process.cwd(), './dist/db/migrations');\n const devDefaultPath = join(process.cwd(), './src/db/migrations');\n const path = [\n process.env.DB_MIGRATIONS_DIR,\n process.env.NODE_ENV === 'prod' ? prodDefaultPath : devDefaultPath\n ].filter((path) => path && existsSync(path) && lstatSync(path).isDirectory());\n reportDebug({\n namespace,\n message: 'Obtaining Umzug migration options',\n data: {\n path,\n prodPath: prodDefaultPath,\n devPath: devDefaultPath,\n customPath: process.env.DB_MIGRATIONS_DIR\n }\n });\n return path.map((p) =>\n process.env.NODE_ENV === 'prod' ? `${p}/*.js` : `${p}/*.ts`\n );\n}\n\n/** Run all the non-executed migrations one by one */\nexport async function runPendingMigrations(): Promise<void> {\n if (!sequelize)\n throw new EngineError({ message: 'Sequelize is not initialized' });\n \n try {\n reportDebug({\n namespace,\n message: 'Starting migration process',\n data: {\n sqlHost: process.env.SQL_HOST,\n sqlDb: process.env.SQL_DB,\n sqlUser: process.env.SQL_USER,\n nodeEnv: process.env.NODE_ENV\n }\n });\n\n // First make sure that the metadata table is up to date\n reportDebug({\n namespace,\n message: 'Migrating metadata table'\n });\n await migrateMetadataTable(sequelize);\n \n const migrationPaths = getMigrationPaths();\n\n if (!migrationPaths || migrationPaths.length <= 0) {\n reportDebug({\n namespace,\n message: 'No valid migration paths found.',\n data: { migrationPaths }\n });\n return;\n }\n\n // Process migrations sequentially to avoid race conditions\n for (const path of migrationPaths) {\n reportDebug({\n namespace,\n message: 'Processing migration path',\n data: { path }\n });\n\n const migrator = new Umzug({\n migrations: {\n glob: path,\n resolve: ({ name, path, context }) => {\n const nameWithoutExtension = name.replace(/\\.(ts|js)$/, '');\n \n // Use dynamic import for better TypeScript/ES module support\n const migrationPromise = import(path || '');\n return {\n name: nameWithoutExtension,\n up: async () => {\n reportDebug({\n namespace,\n message: `Running migration: ${nameWithoutExtension}`\n });\n const migration = await migrationPromise;\n return migration.up(context);\n },\n down: async () => {\n const migration = await migrationPromise;\n return migration.down(context);\n }\n };\n }\n },\n context: sequelize.getQueryInterface(),\n storage: new SequelizeStorage({ sequelize }),\n logger: console\n });\n\n reportDebug({\n namespace,\n message: 'Starting pending database migrations',\n data: { path }\n });\n \n await migrator.up();\n \n reportInfo({\n message: 'Database migration completed successfully',\n data: { path }\n });\n }\n } catch (error) {\n reportDebug({\n namespace,\n message: 'Migration failed with error',\n data: {\n error: error instanceof Error ? error.message : String(error),\n stack: error instanceof Error ? error.stack : undefined,\n sqlHost: process.env.SQL_HOST,\n sqlDb: process.env.SQL_DB\n }\n });\n throw error;\n }\n}\n\n/** Rollback all migrations up to a specified target version */\nexport async function rollbackMigrations(): Promise<void> {\n if (!sequelize)\n throw new EngineError({ message: 'Sequelize is not initialized' });\n if (!process.env.BUILD_ID)\n throw new EngineError({ message: 'BUILD_ID is not defined' });\n\n // Fetch executed migrations in ascending order\n const migrations = await SequelizeMeta.findAll({\n order: [['name', 'ASC']],\n raw: true\n });\n const rollbackUntil = migrations.find(\n (migration) =>\n migration.version &&\n semverCompare(migration.version, process.env.BUILD_ID as string) === 1\n );\n\n if (!rollbackUntil) {\n reportDebug({\n namespace,\n message: 'No rollback target found for the current BUILD_ID.',\n data: { buildId: process.env.BUILD_ID }\n });\n return;\n }\n\n const migrationPaths = getMigrationPaths();\n\n if (!migrationPaths || migrationPaths.length <= 0) {\n reportDebug({\n namespace,\n message: 'No valid migration paths found for rollback.',\n data: { migrationPaths }\n });\n return;\n }\n\n // Process rollback migrations sequentially\n for (const path of migrationPaths) {\n const migrator = new Umzug({\n migrations: {\n glob: path,\n resolve: ({ name, path, context }) => {\n // Strip file extension to ensure consistent naming across .ts and .js files\n const nameWithoutExtension = name.replace(/\\.(ts|js)$/, '');\n \n const migrationPromise = import(path || '');\n return {\n name: nameWithoutExtension,\n up: async () => {\n const migration = await migrationPromise;\n return migration.up(context);\n },\n down: async () => {\n const migration = await migrationPromise;\n return migration.down(context);\n }\n };\n }\n },\n context: sequelize.getQueryInterface(),\n storage: new SequelizeStorage({ sequelize }),\n logger: console\n });\n\n reportDebug({\n namespace,\n message: `Rolling back migrations up to: ${rollbackUntil.name}`,\n data: { path }\n });\n\n await migrator.down({ to: rollbackUntil.name });\n }\n}\n\n/**\n * Recreate the whole database\n * Quick way to make sure that we are working on a clean database\n * /!\\ Should only be used when running tests\n */\nexport async function testRecreateDB(\n options: RecreateDbOptions = {}\n): Promise<void> {\n if (!sequelize)\n throw new EngineError({ message: 'Sequelize is not initialized' });\n await sequelize.query(\n `DROP SCHEMA public${process.env.SQL_TYPE === 'postgres' ? ' CASCADE' : ''}`\n );\n await sequelize.query(`CREATE SCHEMA public`);\n if (!options.clean) await runPendingMigrations();\n}\n\n/**\n * Empty all the schemas in the database\n * Quick way to flush the database data while keep the structure\n * /!\\ Should only be used when running tests\n */\nexport async function testClearDB(options: ClearDbOptions = {}): Promise<void> {\n if (!sequelize)\n throw new EngineError({ message: 'Sequelize is not initialized' });\n await sequelize.transaction(clearDbTransaction(sequelize, options));\n}\n\n/** DB clearing transaction */\nfunction clearDbTransaction(sequelize: Sequelize, options: ClearDbOptions) {\n return async (t: Transaction): Promise<void> => {\n for (const model of Object.keys(sequelize.models)) {\n // We ignore the migration metadata schema\n if (!options.includeMetadata && model === 'sequelizeMeta') continue;\n const table = sequelize.models[model].tableName;\n await sequelize.query(\n `TRUNCATE ${table}${process.env.SQL_TYPE === 'postgres' ? ' CASCADE' : ''};`,\n {\n raw: true,\n transaction: t\n }\n );\n }\n };\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
import { GoogleGenAI } from '@google/genai';
|
|
2
|
+
/** Delay between two checks of the file processing state */
|
|
3
|
+
export declare const GEMINI_POLL_INTERVAL_MS = 10000;
|
|
4
|
+
/**
|
|
5
|
+
* Get the Gemini client
|
|
6
|
+
* The client is created lazily so that GOOGLE_AI_KEY can be loaded at startup (e.g. by the Secret Manager)
|
|
7
|
+
*/
|
|
8
|
+
export declare function getGeminiClient(): GoogleGenAI;
|
|
9
|
+
/** Upload a file to Gemini and wait until it is ready to be used in prompts */
|
|
10
|
+
export declare function geminiFileUpload(buffer: Buffer, mimeType: string, originalName: string, pollIntervalMs?: number): Promise<{
|
|
2
11
|
success: true;
|
|
3
12
|
originalname: string;
|
|
4
13
|
fileUri: string;
|