node-server-engine 1.14.0 → 2.0.1
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/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.d.ts +1 -0
- package/dist/utils/dbMigration/dbMigration.js +15 -3
- package/dist/utils/dbMigration/dbMigration.js.map +1 -1
- package/dist/utils/dbMigration/migrateMetadataTable/migrateMetadataTable.js +29 -3
- package/dist/utils/dbMigration/migrateMetadataTable/migrateMetadataTable.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
|
@@ -1,57 +1,82 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
3
|
Object.defineProperty(exports, "__esModule", {
|
|
5
4
|
value: true
|
|
6
5
|
});
|
|
6
|
+
exports.GEMINI_POLL_INTERVAL_MS = void 0;
|
|
7
7
|
exports.geminiFileUpload = geminiFileUpload;
|
|
8
|
-
|
|
9
|
-
var
|
|
10
|
-
var _path = _interopRequireDefault(require("path"));
|
|
8
|
+
exports.getGeminiClient = getGeminiClient;
|
|
9
|
+
var _genai = require("@google/genai");
|
|
11
10
|
var _promises = require("timers/promises");
|
|
12
|
-
var _tmp = _interopRequireDefault(require("tmp"));
|
|
13
11
|
var _report = require("../report");
|
|
14
12
|
const namespace = 'engine:utils:geminiFileUpload';
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
13
|
+
|
|
14
|
+
/** Delay between two checks of the file processing state */
|
|
15
|
+
const GEMINI_POLL_INTERVAL_MS = exports.GEMINI_POLL_INTERVAL_MS = 10_000;
|
|
16
|
+
/** Maximum time to wait for Gemini to process an uploaded file */
|
|
17
|
+
const GEMINI_PROCESSING_TIMEOUT_MS = Number(process.env.GEMINI_PROCESSING_TIMEOUT_MS) || 10 * 60 * 1000;
|
|
18
|
+
let client;
|
|
19
|
+
let clientKey;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Get the Gemini client
|
|
23
|
+
* The client is created lazily so that GOOGLE_AI_KEY can be loaded at startup (e.g. by the Secret Manager)
|
|
24
|
+
*/
|
|
25
|
+
function getGeminiClient() {
|
|
26
|
+
const apiKey = process.env.GOOGLE_AI_KEY ?? '';
|
|
27
|
+
if (!client || clientKey !== apiKey) {
|
|
28
|
+
client = new _genai.GoogleGenAI({
|
|
29
|
+
apiKey
|
|
22
30
|
});
|
|
23
|
-
|
|
31
|
+
clientKey = apiKey;
|
|
32
|
+
}
|
|
33
|
+
return client;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Upload a file to Gemini and wait until it is ready to be used in prompts */
|
|
37
|
+
async function geminiFileUpload(buffer, mimeType, originalName, pollIntervalMs = GEMINI_POLL_INTERVAL_MS) {
|
|
38
|
+
try {
|
|
39
|
+
const {
|
|
40
|
+
files
|
|
41
|
+
} = getGeminiClient();
|
|
24
42
|
|
|
25
|
-
//
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
43
|
+
// 1. Upload the file straight from memory
|
|
44
|
+
const file = await files.upload({
|
|
45
|
+
file: new Blob([new Uint8Array(buffer)], {
|
|
46
|
+
type: mimeType
|
|
47
|
+
}),
|
|
48
|
+
config: {
|
|
49
|
+
mimeType,
|
|
50
|
+
displayName: originalName
|
|
51
|
+
}
|
|
29
52
|
});
|
|
30
|
-
if (!
|
|
53
|
+
if (!file?.name) {
|
|
31
54
|
throw new Error('Upload failed: No file returned');
|
|
32
55
|
}
|
|
33
|
-
const file = uploadResult.file;
|
|
34
56
|
(0, _report.reportDebug)({
|
|
35
57
|
namespace,
|
|
36
|
-
message: 'Uploaded file successfully',
|
|
58
|
+
message: 'Uploaded file successfully, waiting for file processing',
|
|
37
59
|
data: {
|
|
38
60
|
displayName: file.displayName,
|
|
39
61
|
name: file.name
|
|
40
62
|
}
|
|
41
63
|
});
|
|
42
|
-
(0, _report.reportDebug)({
|
|
43
|
-
namespace,
|
|
44
|
-
message: 'Waiting for file processing...'
|
|
45
|
-
});
|
|
46
64
|
|
|
47
|
-
//
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
65
|
+
// 2. Poll until the file is processed, with an upper bound on the wait
|
|
66
|
+
const deadline = Date.now() + GEMINI_PROCESSING_TIMEOUT_MS;
|
|
67
|
+
let geminiFile = await files.get({
|
|
68
|
+
name: file.name
|
|
69
|
+
});
|
|
70
|
+
while (geminiFile?.state === _genai.FileState.PROCESSING) {
|
|
71
|
+
if (Date.now() > deadline) {
|
|
72
|
+
throw new Error(`Timed out waiting for Gemini to process ${file.name}`);
|
|
73
|
+
}
|
|
74
|
+
await (0, _promises.setTimeout)(pollIntervalMs);
|
|
75
|
+
geminiFile = await files.get({
|
|
76
|
+
name: file.name
|
|
77
|
+
});
|
|
53
78
|
}
|
|
54
|
-
if (
|
|
79
|
+
if (geminiFile?.state !== _genai.FileState.ACTIVE) {
|
|
55
80
|
const errorMessage = `File processing failed for ${file.name}`;
|
|
56
81
|
(0, _report.reportError)(errorMessage);
|
|
57
82
|
return {
|
|
@@ -66,8 +91,8 @@ async function geminiFileUpload(buffer, mimeType, originalName) {
|
|
|
66
91
|
return {
|
|
67
92
|
success: true,
|
|
68
93
|
originalname: file.name,
|
|
69
|
-
fileUri: file.uri,
|
|
70
|
-
mimeType: file.mimeType
|
|
94
|
+
fileUri: geminiFile.uri ?? file.uri ?? '',
|
|
95
|
+
mimeType: geminiFile.mimeType ?? file.mimeType ?? mimeType
|
|
71
96
|
};
|
|
72
97
|
} catch (error) {
|
|
73
98
|
(0, _report.reportError)({
|
|
@@ -79,19 +104,6 @@ async function geminiFileUpload(buffer, mimeType, originalName) {
|
|
|
79
104
|
success: false,
|
|
80
105
|
error
|
|
81
106
|
};
|
|
82
|
-
} finally {
|
|
83
|
-
// Ensure cleanup of temp file
|
|
84
|
-
if (tempFile) {
|
|
85
|
-
try {
|
|
86
|
-
tempFile.removeCallback();
|
|
87
|
-
} catch (cleanupError) {
|
|
88
|
-
(0, _report.reportError)({
|
|
89
|
-
namespace,
|
|
90
|
-
message: 'Error cleaning up temp file',
|
|
91
|
-
error: cleanupError
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
107
|
}
|
|
96
108
|
}
|
|
97
109
|
//# sourceMappingURL=geminiFileUpload.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"geminiFileUpload.js","names":["
|
|
1
|
+
{"version":3,"file":"geminiFileUpload.js","names":["_genai","require","_promises","_report","namespace","GEMINI_POLL_INTERVAL_MS","exports","GEMINI_PROCESSING_TIMEOUT_MS","Number","process","env","client","clientKey","getGeminiClient","apiKey","GOOGLE_AI_KEY","GoogleGenAI","geminiFileUpload","buffer","mimeType","originalName","pollIntervalMs","files","file","upload","Blob","Uint8Array","type","config","displayName","name","Error","reportDebug","message","data","deadline","Date","now","geminiFile","get","state","FileState","PROCESSING","setTimeoutPromise","ACTIVE","errorMessage","reportError","success","error","originalname","fileUri","uri"],"sources":["../../../src/utils/geminiFileUpload/geminiFileUpload.ts"],"sourcesContent":["import { GoogleGenAI, File as GeminiFile, FileState } from '@google/genai';\nimport { setTimeout as setTimeoutPromise } from 'timers/promises';\nimport { reportError, reportDebug } from 'utils/report';\n\nconst namespace = 'engine:utils:geminiFileUpload';\n\n/** Delay between two checks of the file processing state */\nexport const GEMINI_POLL_INTERVAL_MS = 10_000;\n/** Maximum time to wait for Gemini to process an uploaded file */\nconst GEMINI_PROCESSING_TIMEOUT_MS =\n Number(process.env.GEMINI_PROCESSING_TIMEOUT_MS) || 10 * 60 * 1000;\n\nlet client: GoogleGenAI | undefined;\nlet clientKey: string | undefined;\n\n/**\n * Get the Gemini client\n * The client is created lazily so that GOOGLE_AI_KEY can be loaded at startup (e.g. by the Secret Manager)\n */\nexport function getGeminiClient(): GoogleGenAI {\n const apiKey = process.env.GOOGLE_AI_KEY ?? '';\n if (!client || clientKey !== apiKey) {\n client = new GoogleGenAI({ apiKey });\n clientKey = apiKey;\n }\n return client;\n}\n\n/** Upload a file to Gemini and wait until it is ready to be used in prompts */\nexport async function geminiFileUpload(\n buffer: Buffer,\n mimeType: string,\n originalName: string,\n pollIntervalMs: number = GEMINI_POLL_INTERVAL_MS\n): Promise<\n | { success: true; originalname: string; fileUri: string; mimeType: string }\n | { success: false; error: unknown }\n> {\n try {\n const { files } = getGeminiClient();\n\n // 1. Upload the file straight from memory\n const file = await files.upload({\n file: new Blob([new Uint8Array(buffer)], { type: mimeType }),\n config: { mimeType, displayName: originalName }\n });\n\n if (!file?.name) {\n throw new Error('Upload failed: No file returned');\n }\n\n reportDebug({\n namespace,\n message: 'Uploaded file successfully, waiting for file processing',\n data: { displayName: file.displayName, name: file.name }\n });\n\n // 2. Poll until the file is processed, with an upper bound on the wait\n const deadline = Date.now() + GEMINI_PROCESSING_TIMEOUT_MS;\n let geminiFile: GeminiFile | undefined = await files.get({\n name: file.name\n });\n while (geminiFile?.state === FileState.PROCESSING) {\n if (Date.now() > deadline) {\n throw new Error(`Timed out waiting for Gemini to process ${file.name}`);\n }\n await setTimeoutPromise(pollIntervalMs);\n geminiFile = await files.get({ name: file.name });\n }\n\n if (geminiFile?.state !== FileState.ACTIVE) {\n const errorMessage = `File processing failed for ${file.name}`;\n reportError(errorMessage);\n return { success: false, error: new Error(errorMessage) };\n }\n\n reportDebug({ namespace, message: 'File is ready' });\n\n return {\n success: true,\n originalname: file.name,\n fileUri: geminiFile.uri ?? file.uri ?? '',\n mimeType: geminiFile.mimeType ?? file.mimeType ?? mimeType\n };\n } catch (error) {\n reportError({\n namespace,\n message: 'Error during Gemini file upload and processing',\n error\n });\n return { success: false, error };\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
package/dist/utils/jwt/jwt.js
CHANGED
|
@@ -150,9 +150,6 @@ async function jwtVerify(token, issuer = _const.TokenIssuer.AUTH_SERVICE) {
|
|
|
150
150
|
errorCode: 'unauthorized',
|
|
151
151
|
statusCode: 401,
|
|
152
152
|
message: 'Invalid token supplied',
|
|
153
|
-
data: {
|
|
154
|
-
token
|
|
155
|
-
},
|
|
156
153
|
error: error instanceof Error ? error : undefined
|
|
157
154
|
}));
|
|
158
155
|
return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jwt.js","names":["_jsonwebtoken","require","_fs","_interopRequireDefault","_const","_EngineError","_KeySet","_WebError","_checkEnvironment","_envAssert","_report","keySet","exports","namespace","initKeySets","process","env","CHART","ECDSA_PUBLIC_KEY","EngineError","message","KeySet","file","AUTH_SERVICE_URL","internal","jwksPath","JWKS_PATH","promise","init","bind","Promise","all","shutdownKeySets","shutdown","jwtGetKey","header","callback","NODE_ENV","JWT_SECRET","kid","data","reportDebug","key","getKey","error","Error","WebError","statusCode","errorCode","jwtVerify","token","issuer","TokenIssuer","AUTH_SERVICE","algorithms","assertEnvironment","envAssert","isString","ACCESS_TOKEN_AUDIENCE","ACCESS_TOKEN_ISSUER","ACCESS_TOKEN_EXPIRATION_TIME","options","audience","resolve","reject","verify","result","undefined","generateJwtToken","user","signOptions","algorithm","expiresIn","parseInt","keyid","Object","keys","getKeys","privateKey","PRIVATE_KEY","PRIVATE_KEY_PATH","fs","readFileSync","String","sign"],"sources":["../../../src/utils/jwt/jwt.ts"],"sourcesContent":["import {\n verify,\n JwtHeader,\n SigningKeyCallback,\n Algorithm,\n VerifyOptions,\n sign\n} from 'jsonwebtoken';\nimport fs from 'fs';\nimport { UserTokenPayload } from './jwt.types';\nimport { TokenIssuer } from 'const';\nimport { EngineError } from 'entities/EngineError';\nimport { KeySet } from 'entities/KeySet';\nimport { WebError } from 'entities/WebError';\nimport { assertEnvironment } from 'utils/checkEnvironment';\nimport { envAssert } from 'utils/envAssert';\nimport { reportDebug } from 'utils/report';\n\nexport let keySet: KeySet | undefined;\n\nconst namespace = 'engine:utils:jwt';\n\n/** Initiate key fetching mechanism */\nexport async function initKeySets(): Promise<void> {\n // Create the KeySet instance\n // The auth-service fetches its key from its disk and not from a network request\n // This prevents fetch failures the first time the auth-service is started\n if (process.env.CHART === 'auth-service') {\n if (!process.env.ECDSA_PUBLIC_KEY)\n throw new EngineError({\n message: 'Environment variable \"ECDSA_PUBLIC_KEY\" is not defined'\n });\n keySet = new KeySet(process.env.ECDSA_PUBLIC_KEY, { file: true });\n } else {\n if (process.env.AUTH_SERVICE_URL)\n keySet = new KeySet(\n `${process.env.AUTH_SERVICE_URL}/.well-known/jwks.json`,\n { internal: true }\n );\n else {\n // Use JWKS_PATH if set (e.g., by Secret Manager), otherwise fall back to local path\n const jwksPath = process.env.JWKS_PATH || './keys/jwks.json';\n keySet = new KeySet(jwksPath, { internal: true, file: true });\n }\n\n if (!keySet)\n throw new EngineError({\n message:\n 'Environment variable \"AUTH_SERVICE_URL\" is not defined or JWKS not found'\n });\n }\n const promise = [keySet.init.bind(keySet)()];\n\n await Promise.all(promise);\n}\n\n/** Stop fetching keys regularly */\nexport function shutdownKeySets(): void {\n if (keySet) keySet.shutdown.bind(keySet)();\n}\n\n/** Get the JWT public key */\nfunction jwtGetKey(header: JwtHeader, callback: SigningKeyCallback): void {\n // In test environment we return the symmetric signature secret\n if (process.env.NODE_ENV === 'test') {\n callback(null, process.env.JWT_SECRET);\n return;\n }\n // Check that KeySet has been initialized\n if (!keySet) {\n callback(new EngineError({ message: 'KeySet has not been initialized' }));\n return;\n }\n // Check that the token has a key ID\n if (!header.kid) {\n callback(\n new EngineError({\n message:\n 'Token has not key ID in header, impossible to match with known key',\n data: header\n })\n );\n return;\n }\n\n // In test environment we use a symmetrical secret bases signature\n if (process.env.NODE_ENV === 'test') {\n callback(null, process.env.JWT_SECRET);\n return;\n }\n reportDebug({\n namespace,\n message: `Looking for key with ID \"${header.kid}\"`\n });\n // Go through each key set to check if the token's key is in one of them\n let key;\n try {\n key = keySet.getKey(header.kid);\n } catch (error: Error | null | unknown) {\n callback(error instanceof Error ? error : null);\n return;\n }\n\n if (key) {\n reportDebug({ namespace, message: `Found key with ID \"${header.kid}\"` });\n callback(null, key);\n return;\n }\n callback(\n new WebError({\n statusCode: 401,\n errorCode: 'unauthorized',\n message: 'Could not find the tokens key in any key set',\n data: { kid: header.kid }\n })\n );\n}\n\n/** Verify an user Json Web Token */\nexport async function jwtVerify(\n token: string,\n issuer: TokenIssuer = TokenIssuer.AUTH_SERVICE\n): Promise<UserTokenPayload> {\n let algorithms = ['ES256' as Algorithm];\n if (process.env.NODE_ENV === 'test') {\n assertEnvironment({ JWT_SECRET: envAssert.isString() });\n algorithms = ['HS256' as Algorithm];\n }\n assertEnvironment({\n ACCESS_TOKEN_AUDIENCE: envAssert.isString(),\n ACCESS_TOKEN_ISSUER: envAssert.isString(),\n ACCESS_TOKEN_EXPIRATION_TIME: envAssert.isString()\n });\n let options: VerifyOptions | undefined;\n\n switch (issuer) {\n case TokenIssuer.AUTH_SERVICE:\n options = {\n audience: [process.env.ACCESS_TOKEN_AUDIENCE as string],\n issuer: [process.env.ACCESS_TOKEN_ISSUER as string],\n algorithms\n };\n break;\n default:\n throw new EngineError({\n message: `Invalid token issuer specified \"${issuer as string}\"`\n });\n }\n\n return new Promise<UserTokenPayload>((resolve, reject) => {\n verify(token, jwtGetKey, options, (error, result) => {\n if (error) {\n reject(\n new WebError({\n errorCode: 'unauthorized',\n statusCode: 401,\n message: 'Invalid token supplied',\n data: { token },\n error: error instanceof Error ? error : undefined\n })\n );\n return;\n }\n resolve(result as UserTokenPayload);\n });\n });\n}\n\nexport function generateJwtToken(user: unknown) {\n const signOptions = {\n algorithm: 'ES256' as const, // or 'ES256' as Algorithm\n expiresIn: process.env.ACCESS_TOKEN_EXPIRATION_TIME\n ? parseInt(process.env.ACCESS_TOKEN_EXPIRATION_TIME, 10)\n : 3600,\n issuer: process.env.ACCESS_TOKEN_ISSUER,\n audience: process.env.ACCESS_TOKEN_AUDIENCE,\n keyid: Object.keys(keySet?.getKeys() ?? {})[0]\n };\n\n // Get private key - can be either the key content directly or a file path\n let privateKey: string;\n \n if (process.env.PRIVATE_KEY) {\n // Direct key content in environment variable\n privateKey = process.env.PRIVATE_KEY;\n } else if (process.env.PRIVATE_KEY_PATH) {\n // File path in environment variable - read the file\n try {\n privateKey = fs.readFileSync(process.env.PRIVATE_KEY_PATH, 'utf8');\n } catch (error) {\n throw new EngineError({\n message: `Failed to read private key from path: ${process.env.PRIVATE_KEY_PATH}`,\n data: { error: error instanceof Error ? error.message : String(error) }\n });\n }\n } else {\n throw new EngineError({ \n message: 'Neither PRIVATE_KEY nor PRIVATE_KEY_PATH environment variable is set' \n });\n }\n\n const token = sign({ user }, privateKey, signOptions);\n\n return token;\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"jwt.js","names":["_jsonwebtoken","require","_fs","_interopRequireDefault","_const","_EngineError","_KeySet","_WebError","_checkEnvironment","_envAssert","_report","keySet","exports","namespace","initKeySets","process","env","CHART","ECDSA_PUBLIC_KEY","EngineError","message","KeySet","file","AUTH_SERVICE_URL","internal","jwksPath","JWKS_PATH","promise","init","bind","Promise","all","shutdownKeySets","shutdown","jwtGetKey","header","callback","NODE_ENV","JWT_SECRET","kid","data","reportDebug","key","getKey","error","Error","WebError","statusCode","errorCode","jwtVerify","token","issuer","TokenIssuer","AUTH_SERVICE","algorithms","assertEnvironment","envAssert","isString","ACCESS_TOKEN_AUDIENCE","ACCESS_TOKEN_ISSUER","ACCESS_TOKEN_EXPIRATION_TIME","options","audience","resolve","reject","verify","result","undefined","generateJwtToken","user","signOptions","algorithm","expiresIn","parseInt","keyid","Object","keys","getKeys","privateKey","PRIVATE_KEY","PRIVATE_KEY_PATH","fs","readFileSync","String","sign"],"sources":["../../../src/utils/jwt/jwt.ts"],"sourcesContent":["import {\n verify,\n JwtHeader,\n SigningKeyCallback,\n Algorithm,\n VerifyOptions,\n sign\n} from 'jsonwebtoken';\nimport fs from 'fs';\nimport { UserTokenPayload } from './jwt.types';\nimport { TokenIssuer } from 'const';\nimport { EngineError } from 'entities/EngineError';\nimport { KeySet } from 'entities/KeySet';\nimport { WebError } from 'entities/WebError';\nimport { assertEnvironment } from 'utils/checkEnvironment';\nimport { envAssert } from 'utils/envAssert';\nimport { reportDebug } from 'utils/report';\n\nexport let keySet: KeySet | undefined;\n\nconst namespace = 'engine:utils:jwt';\n\n/** Initiate key fetching mechanism */\nexport async function initKeySets(): Promise<void> {\n // Create the KeySet instance\n // The auth-service fetches its key from its disk and not from a network request\n // This prevents fetch failures the first time the auth-service is started\n if (process.env.CHART === 'auth-service') {\n if (!process.env.ECDSA_PUBLIC_KEY)\n throw new EngineError({\n message: 'Environment variable \"ECDSA_PUBLIC_KEY\" is not defined'\n });\n keySet = new KeySet(process.env.ECDSA_PUBLIC_KEY, { file: true });\n } else {\n if (process.env.AUTH_SERVICE_URL)\n keySet = new KeySet(\n `${process.env.AUTH_SERVICE_URL}/.well-known/jwks.json`,\n { internal: true }\n );\n else {\n // Use JWKS_PATH if set (e.g., by Secret Manager), otherwise fall back to local path\n const jwksPath = process.env.JWKS_PATH || './keys/jwks.json';\n keySet = new KeySet(jwksPath, { internal: true, file: true });\n }\n\n if (!keySet)\n throw new EngineError({\n message:\n 'Environment variable \"AUTH_SERVICE_URL\" is not defined or JWKS not found'\n });\n }\n const promise = [keySet.init.bind(keySet)()];\n\n await Promise.all(promise);\n}\n\n/** Stop fetching keys regularly */\nexport function shutdownKeySets(): void {\n if (keySet) keySet.shutdown.bind(keySet)();\n}\n\n/** Get the JWT public key */\nfunction jwtGetKey(header: JwtHeader, callback: SigningKeyCallback): void {\n // In test environment we return the symmetric signature secret\n if (process.env.NODE_ENV === 'test') {\n callback(null, process.env.JWT_SECRET);\n return;\n }\n // Check that KeySet has been initialized\n if (!keySet) {\n callback(new EngineError({ message: 'KeySet has not been initialized' }));\n return;\n }\n // Check that the token has a key ID\n if (!header.kid) {\n callback(\n new EngineError({\n message:\n 'Token has not key ID in header, impossible to match with known key',\n data: header\n })\n );\n return;\n }\n\n // In test environment we use a symmetrical secret bases signature\n if (process.env.NODE_ENV === 'test') {\n callback(null, process.env.JWT_SECRET);\n return;\n }\n reportDebug({\n namespace,\n message: `Looking for key with ID \"${header.kid}\"`\n });\n // Go through each key set to check if the token's key is in one of them\n let key;\n try {\n key = keySet.getKey(header.kid);\n } catch (error: Error | null | unknown) {\n callback(error instanceof Error ? error : null);\n return;\n }\n\n if (key) {\n reportDebug({ namespace, message: `Found key with ID \"${header.kid}\"` });\n callback(null, key);\n return;\n }\n callback(\n new WebError({\n statusCode: 401,\n errorCode: 'unauthorized',\n message: 'Could not find the tokens key in any key set',\n data: { kid: header.kid }\n })\n );\n}\n\n/** Verify an user Json Web Token */\nexport async function jwtVerify(\n token: string,\n issuer: TokenIssuer = TokenIssuer.AUTH_SERVICE\n): Promise<UserTokenPayload> {\n let algorithms = ['ES256' as Algorithm];\n if (process.env.NODE_ENV === 'test') {\n assertEnvironment({ JWT_SECRET: envAssert.isString() });\n algorithms = ['HS256' as Algorithm];\n }\n assertEnvironment({\n ACCESS_TOKEN_AUDIENCE: envAssert.isString(),\n ACCESS_TOKEN_ISSUER: envAssert.isString(),\n ACCESS_TOKEN_EXPIRATION_TIME: envAssert.isString()\n });\n let options: VerifyOptions | undefined;\n\n switch (issuer) {\n case TokenIssuer.AUTH_SERVICE:\n options = {\n audience: [process.env.ACCESS_TOKEN_AUDIENCE as string],\n issuer: [process.env.ACCESS_TOKEN_ISSUER as string],\n algorithms\n };\n break;\n default:\n throw new EngineError({\n message: `Invalid token issuer specified \"${issuer as string}\"`\n });\n }\n\n return new Promise<UserTokenPayload>((resolve, reject) => {\n verify(token, jwtGetKey, options, (error, result) => {\n if (error) {\n reject(\n new WebError({\n errorCode: 'unauthorized',\n statusCode: 401,\n message: 'Invalid token supplied',\n error: error instanceof Error ? error : undefined\n })\n );\n return;\n }\n resolve(result as UserTokenPayload);\n });\n });\n}\n\nexport function generateJwtToken(user: unknown) {\n const signOptions = {\n algorithm: 'ES256' as const, // or 'ES256' as Algorithm\n expiresIn: process.env.ACCESS_TOKEN_EXPIRATION_TIME\n ? parseInt(process.env.ACCESS_TOKEN_EXPIRATION_TIME, 10)\n : 3600,\n issuer: process.env.ACCESS_TOKEN_ISSUER,\n audience: process.env.ACCESS_TOKEN_AUDIENCE,\n keyid: Object.keys(keySet?.getKeys() ?? {})[0]\n };\n\n // Get private key - can be either the key content directly or a file path\n let privateKey: string;\n \n if (process.env.PRIVATE_KEY) {\n // Direct key content in environment variable\n privateKey = process.env.PRIVATE_KEY;\n } else if (process.env.PRIVATE_KEY_PATH) {\n // File path in environment variable - read the file\n try {\n privateKey = fs.readFileSync(process.env.PRIVATE_KEY_PATH, 'utf8');\n } catch (error) {\n throw new EngineError({\n message: `Failed to read private key from path: ${process.env.PRIVATE_KEY_PATH}`,\n data: { error: error instanceof Error ? error.message : String(error) }\n });\n }\n } else {\n throw new EngineError({ \n message: 'Neither PRIVATE_KEY nor PRIVATE_KEY_PATH environment variable is set' \n });\n }\n\n const token = sign({ user }, privateKey, signOptions);\n\n return token;\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -10,5 +10,7 @@ export declare function formatTime(date?: Date): string;
|
|
|
10
10
|
*/
|
|
11
11
|
export declare function formatLocalLog(severity: LogSeverity | string, message: string): void;
|
|
12
12
|
export declare function formatLocalLog(entry: FullLogEntry): string;
|
|
13
|
+
/** Returns a copy of the value with sensitive fields masked */
|
|
14
|
+
export declare function redactSensitive(value: unknown, depth?: number, seen?: WeakSet<object>): unknown;
|
|
13
15
|
/** Add metadata to the log entry and print it */
|
|
14
16
|
export declare function output(data: LogEntry): FullLogEntry;
|
|
@@ -8,6 +8,7 @@ exports.INSTANCE_ID = void 0;
|
|
|
8
8
|
exports.formatLocalLog = formatLocalLog;
|
|
9
9
|
exports.formatTime = formatTime;
|
|
10
10
|
exports.output = output;
|
|
11
|
+
exports.redactSensitive = redactSensitive;
|
|
11
12
|
var _crypto = require("crypto");
|
|
12
13
|
var _callsite = _interopRequireDefault(require("callsite"));
|
|
13
14
|
var _jsonStringifySafe = _interopRequireDefault(require("json-stringify-safe"));
|
|
@@ -136,8 +137,34 @@ function isLocalDevelopment() {
|
|
|
136
137
|
return process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'dev' || !process.env.NODE_ENV && !process.env.KUBERNETES_POD_NAME && !process.env.CLOUD_RUN_JOB;
|
|
137
138
|
}
|
|
138
139
|
|
|
140
|
+
/** Keys whose values must never be written to the logs */
|
|
141
|
+
const SENSITIVE_KEY_PATTERN = /^(authorization|proxy[-_]?authorization|cookie|set[-_]?cookie|password|passwd|pass|secret|client[-_]?secret|token|access[-_]?token|refresh[-_]?token|id[-_]?token|api[-_]?key|private[-_]?key|credentials?|otp)$/i;
|
|
142
|
+
const REDACTED = '[REDACTED]';
|
|
143
|
+
const MAX_REDACT_DEPTH = 10;
|
|
144
|
+
|
|
145
|
+
/** Returns a copy of the value with sensitive fields masked */
|
|
146
|
+
function redactSensitive(value, depth = 0, seen = new WeakSet()) {
|
|
147
|
+
if (!value || typeof value !== 'object' || depth > MAX_REDACT_DEPTH) return value;
|
|
148
|
+
if (seen.has(value)) return value;
|
|
149
|
+
seen.add(value);
|
|
150
|
+
if (Array.isArray(value)) return value.map(item => redactSensitive(item, depth + 1, seen));
|
|
151
|
+
// Only walk plain objects, leave class instances (Errors, Buffers, Dates...) untouched
|
|
152
|
+
const prototype = Object.getPrototypeOf(value);
|
|
153
|
+
if (prototype !== Object.prototype && prototype !== null) return value;
|
|
154
|
+
const result = {};
|
|
155
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
156
|
+
result[key] = SENSITIVE_KEY_PATTERN.test(key) ? REDACTED : redactSensitive(entry, depth + 1, seen);
|
|
157
|
+
}
|
|
158
|
+
return result;
|
|
159
|
+
}
|
|
160
|
+
|
|
139
161
|
/** Add metadata to the log entry and print it */
|
|
140
162
|
function output(data) {
|
|
163
|
+
if (data.data !== undefined) data = {
|
|
164
|
+
...data,
|
|
165
|
+
data: redactSensitive(data.data)
|
|
166
|
+
};
|
|
167
|
+
|
|
141
168
|
// Gather useful information about where the logger was called from
|
|
142
169
|
const call = (0, _callsite.default)()[2];
|
|
143
170
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"output.js","names":["_crypto","require","_callsite","_interopRequireDefault","_jsonStringifySafe","_const","INSTANCE_ID","exports","randomUUID","colors","reset","bright","dim","red","green","yellow","blue","magenta","cyan","white","gray","getSeverityColor","severity","LogSeverity","DEBUG","INFO","WARNING","ERROR","CRITICAL","formatTime","date","now","Date","toTimeString","split","formatLocalLog","severityOrEntry","message","undefined","time","severityColor","useColors","process","env","NODE_ENV","output","padEnd","console","log","entry","showDetailedLogs","DETAILED_LOGS","parts","push","join","errorType","errorCode","statusCode","errorParts","statusColor","length","context","httpRequest","req","method","url","stack_trace","data","Object","keys","JSON","stringify","fileName","lineNumber","isLocalDevelopment","LOG_FORMAT","KUBERNETES_POD_NAME","CLOUD_RUN_JOB","call","callsite","fullEntry","getLineNumber","getFileName","replace","serviceContext","service","CHART","HOSTNAME","version","BUILD_ID","environment","instance","SILENCE_REPORT","dir","depth","formattedLog","error"],"sources":["../../../src/utils/report/output.ts"],"sourcesContent":["import { randomUUID } from 'crypto';\nimport callsite from 'callsite';\nimport stringify from 'json-stringify-safe';\nimport { LogEntry, FullLogEntry } from './output.types';\nimport { LogSeverity } from 'const';\n\n// NOTE: Server instance id (helpful for distributed logs)\nexport const INSTANCE_ID = randomUUID();\n\n// ANSI color codes for terminal output\nconst colors = {\n reset: '\\x1b[0m',\n bright: '\\x1b[1m',\n dim: '\\x1b[2m',\n red: '\\x1b[31m',\n green: '\\x1b[32m',\n yellow: '\\x1b[33m',\n blue: '\\x1b[34m',\n magenta: '\\x1b[35m',\n cyan: '\\x1b[36m',\n white: '\\x1b[37m',\n gray: '\\x1b[90m'\n};\n\n/**\n * Get color for log severity level\n */\nfunction getSeverityColor(severity?: string): string {\n switch (severity) {\n case LogSeverity.DEBUG:\n return colors.cyan;\n case LogSeverity.INFO:\n return colors.green;\n case LogSeverity.WARNING:\n return colors.yellow;\n case LogSeverity.ERROR:\n return colors.red;\n case LogSeverity.CRITICAL:\n return `${colors.bright}${colors.red}`;\n default:\n return colors.white;\n }\n}\n\n/**\n * Format timestamp as HH:MM:SS\n */\nexport function formatTime(date?: Date): string {\n const now = date || new Date();\n return now.toTimeString().split(' ')[0];\n}\n\n/**\n * Format log entry for human-readable local development output\n */\nexport function formatLocalLog(severity: LogSeverity | string, message: string): void;\nexport function formatLocalLog(entry: FullLogEntry): string;\nexport function formatLocalLog(severityOrEntry: LogSeverity | string | FullLogEntry, message?: string): string | void {\n // Handle overloaded signature for testing\n if (typeof severityOrEntry === 'string' && message !== undefined) {\n const time = formatTime();\n const severityColor = getSeverityColor(severityOrEntry);\n const useColors = process.env.NODE_ENV !== 'production';\n \n const output = useColors \n ? `${colors.gray}[${time}]${colors.reset} ${severityColor}${severityOrEntry.padEnd(8)}${colors.reset} ${message}`\n : `[${time}] ${severityOrEntry.padEnd(8)} ${message}`;\n \n console.log(output);\n return;\n }\n \n // Original function implementation\n const entry = severityOrEntry as FullLogEntry;\n const time = formatTime();\n const severity = entry.severity || 'INFO';\n const severityColor = getSeverityColor(severity);\n const showDetailedLogs = process.env.DETAILED_LOGS === 'true';\n \n const parts: string[] = [];\n \n // Timestamp and severity\n parts.push(\n `${colors.gray}[${time}]${colors.reset} ${severityColor}${severity.padEnd(8)}${colors.reset}`\n );\n \n // Message\n if (entry.message) {\n parts.push(`${entry.message}`);\n }\n \n let output = parts.join(' ');\n \n // Error details (compact format)\n if (entry.errorType || entry.errorCode || entry.statusCode) {\n const errorParts: string[] = [];\n if (entry.errorCode) errorParts.push(`${colors.red}${entry.errorCode}${colors.reset}`);\n if (entry.statusCode) {\n const statusColor = entry.statusCode >= 500 ? colors.red : entry.statusCode >= 400 ? colors.yellow : colors.green;\n errorParts.push(`${statusColor}${entry.statusCode}${colors.reset}`);\n }\n if (errorParts.length > 0) {\n output += ` ${colors.dim}[${errorParts.join(' ')}]${colors.reset}`;\n }\n }\n \n // HTTP request context (compact)\n if (entry.context?.httpRequest) {\n const req = entry.context.httpRequest;\n output += ` ${colors.dim}${req.method} ${req.url}${colors.reset}`;\n }\n \n // Stack trace (only in detailed mode)\n if (showDetailedLogs && entry.stack_trace) {\n output += `\\n${colors.gray}Stack Trace:${colors.reset}\\n${colors.dim}${entry.stack_trace}${colors.reset}`;\n }\n \n // Additional data (only in detailed mode or if no message)\n if (showDetailedLogs && entry.data && Object.keys(entry.data).length > 0) {\n output += `\\n${colors.cyan}Data:${colors.reset}\\n${colors.dim}${JSON.stringify(entry.data, null, 2)}${colors.reset}`;\n }\n \n // File location (only in detailed mode)\n if (showDetailedLogs && entry.fileName) {\n output += `\\n ${colors.dim}${entry.fileName}:${entry.lineNumber || '?'}${colors.reset}`;\n }\n \n return output;\n}\n\n/**\n * Check if running in local development environment\n */\nfunction isLocalDevelopment(): boolean {\n // Allow forcing local format via environment variable\n if (process.env.LOG_FORMAT === 'local') return true;\n if (process.env.LOG_FORMAT === 'json') return false;\n \n return (\n process.env.NODE_ENV === 'development' ||\n process.env.NODE_ENV === 'dev' ||\n (!process.env.NODE_ENV && !process.env.KUBERNETES_POD_NAME && !process.env.CLOUD_RUN_JOB)\n );\n}\n\n/** Add metadata to the log entry and print it */\nexport function output(data: LogEntry): FullLogEntry {\n // Gather useful information about where the logger was called from\n const call = callsite()[2];\n\n // Append additional metadata to the log entry\n const fullEntry: FullLogEntry = {\n ...data,\n lineNumber: call.getLineNumber(),\n fileName: call\n .getFileName()\n .replace(/.*node_modules\\//, '')\n .replace(/.*src\\//, 'src/'),\n serviceContext: {\n service: process.env.CHART ?? process.env.HOSTNAME,\n version: process.env.BUILD_ID,\n environment: process.env.NODE_ENV,\n instance: process.env.KUBERNETES_POD_NAME ?? INSTANCE_ID\n }\n };\n\n // Ignore when silenced\n if (process.env.SILENCE_REPORT) return fullEntry;\n\n if (process.env.NODE_ENV === 'test') {\n // In test mode pretty print with full depth\n console.dir(fullEntry, { depth: 5 });\n } else if (isLocalDevelopment()) {\n // Local development: use formatted, colorized output\n const formattedLog = formatLocalLog(fullEntry);\n if (data.severity === LogSeverity.ERROR || data.severity === LogSeverity.CRITICAL || (data.statusCode && data.statusCode >= 500)) {\n console.error(formattedLog);\n } else {\n console.log(formattedLog);\n }\n } else {\n // Production/GCP: use JSON format for structured logging\n const entry = stringify(fullEntry);\n if (data.statusCode && data.statusCode >= 500) {\n console.error(entry);\n } else {\n console.log(entry);\n }\n }\n \n return fullEntry;\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"output.js","names":["_crypto","require","_callsite","_interopRequireDefault","_jsonStringifySafe","_const","INSTANCE_ID","exports","randomUUID","colors","reset","bright","dim","red","green","yellow","blue","magenta","cyan","white","gray","getSeverityColor","severity","LogSeverity","DEBUG","INFO","WARNING","ERROR","CRITICAL","formatTime","date","now","Date","toTimeString","split","formatLocalLog","severityOrEntry","message","undefined","time","severityColor","useColors","process","env","NODE_ENV","output","padEnd","console","log","entry","showDetailedLogs","DETAILED_LOGS","parts","push","join","errorType","errorCode","statusCode","errorParts","statusColor","length","context","httpRequest","req","method","url","stack_trace","data","Object","keys","JSON","stringify","fileName","lineNumber","isLocalDevelopment","LOG_FORMAT","KUBERNETES_POD_NAME","CLOUD_RUN_JOB","SENSITIVE_KEY_PATTERN","REDACTED","MAX_REDACT_DEPTH","redactSensitive","value","depth","seen","WeakSet","has","add","Array","isArray","map","item","prototype","getPrototypeOf","result","key","entries","test","call","callsite","fullEntry","getLineNumber","getFileName","replace","serviceContext","service","CHART","HOSTNAME","version","BUILD_ID","environment","instance","SILENCE_REPORT","dir","formattedLog","error"],"sources":["../../../src/utils/report/output.ts"],"sourcesContent":["import { randomUUID } from 'crypto';\nimport callsite from 'callsite';\nimport stringify from 'json-stringify-safe';\nimport { LogEntry, FullLogEntry } from './output.types';\nimport { LogSeverity } from 'const';\n\n// NOTE: Server instance id (helpful for distributed logs)\nexport const INSTANCE_ID = randomUUID();\n\n// ANSI color codes for terminal output\nconst colors = {\n reset: '\\x1b[0m',\n bright: '\\x1b[1m',\n dim: '\\x1b[2m',\n red: '\\x1b[31m',\n green: '\\x1b[32m',\n yellow: '\\x1b[33m',\n blue: '\\x1b[34m',\n magenta: '\\x1b[35m',\n cyan: '\\x1b[36m',\n white: '\\x1b[37m',\n gray: '\\x1b[90m'\n};\n\n/**\n * Get color for log severity level\n */\nfunction getSeverityColor(severity?: string): string {\n switch (severity) {\n case LogSeverity.DEBUG:\n return colors.cyan;\n case LogSeverity.INFO:\n return colors.green;\n case LogSeverity.WARNING:\n return colors.yellow;\n case LogSeverity.ERROR:\n return colors.red;\n case LogSeverity.CRITICAL:\n return `${colors.bright}${colors.red}`;\n default:\n return colors.white;\n }\n}\n\n/**\n * Format timestamp as HH:MM:SS\n */\nexport function formatTime(date?: Date): string {\n const now = date || new Date();\n return now.toTimeString().split(' ')[0];\n}\n\n/**\n * Format log entry for human-readable local development output\n */\nexport function formatLocalLog(severity: LogSeverity | string, message: string): void;\nexport function formatLocalLog(entry: FullLogEntry): string;\nexport function formatLocalLog(severityOrEntry: LogSeverity | string | FullLogEntry, message?: string): string | void {\n // Handle overloaded signature for testing\n if (typeof severityOrEntry === 'string' && message !== undefined) {\n const time = formatTime();\n const severityColor = getSeverityColor(severityOrEntry);\n const useColors = process.env.NODE_ENV !== 'production';\n \n const output = useColors \n ? `${colors.gray}[${time}]${colors.reset} ${severityColor}${severityOrEntry.padEnd(8)}${colors.reset} ${message}`\n : `[${time}] ${severityOrEntry.padEnd(8)} ${message}`;\n \n console.log(output);\n return;\n }\n \n // Original function implementation\n const entry = severityOrEntry as FullLogEntry;\n const time = formatTime();\n const severity = entry.severity || 'INFO';\n const severityColor = getSeverityColor(severity);\n const showDetailedLogs = process.env.DETAILED_LOGS === 'true';\n \n const parts: string[] = [];\n \n // Timestamp and severity\n parts.push(\n `${colors.gray}[${time}]${colors.reset} ${severityColor}${severity.padEnd(8)}${colors.reset}`\n );\n \n // Message\n if (entry.message) {\n parts.push(`${entry.message}`);\n }\n \n let output = parts.join(' ');\n \n // Error details (compact format)\n if (entry.errorType || entry.errorCode || entry.statusCode) {\n const errorParts: string[] = [];\n if (entry.errorCode) errorParts.push(`${colors.red}${entry.errorCode}${colors.reset}`);\n if (entry.statusCode) {\n const statusColor = entry.statusCode >= 500 ? colors.red : entry.statusCode >= 400 ? colors.yellow : colors.green;\n errorParts.push(`${statusColor}${entry.statusCode}${colors.reset}`);\n }\n if (errorParts.length > 0) {\n output += ` ${colors.dim}[${errorParts.join(' ')}]${colors.reset}`;\n }\n }\n \n // HTTP request context (compact)\n if (entry.context?.httpRequest) {\n const req = entry.context.httpRequest;\n output += ` ${colors.dim}${req.method} ${req.url}${colors.reset}`;\n }\n \n // Stack trace (only in detailed mode)\n if (showDetailedLogs && entry.stack_trace) {\n output += `\\n${colors.gray}Stack Trace:${colors.reset}\\n${colors.dim}${entry.stack_trace}${colors.reset}`;\n }\n \n // Additional data (only in detailed mode or if no message)\n if (showDetailedLogs && entry.data && Object.keys(entry.data).length > 0) {\n output += `\\n${colors.cyan}Data:${colors.reset}\\n${colors.dim}${JSON.stringify(entry.data, null, 2)}${colors.reset}`;\n }\n \n // File location (only in detailed mode)\n if (showDetailedLogs && entry.fileName) {\n output += `\\n ${colors.dim}${entry.fileName}:${entry.lineNumber || '?'}${colors.reset}`;\n }\n \n return output;\n}\n\n/**\n * Check if running in local development environment\n */\nfunction isLocalDevelopment(): boolean {\n // Allow forcing local format via environment variable\n if (process.env.LOG_FORMAT === 'local') return true;\n if (process.env.LOG_FORMAT === 'json') return false;\n \n return (\n process.env.NODE_ENV === 'development' ||\n process.env.NODE_ENV === 'dev' ||\n (!process.env.NODE_ENV && !process.env.KUBERNETES_POD_NAME && !process.env.CLOUD_RUN_JOB)\n );\n}\n\n/** Keys whose values must never be written to the logs */\nconst SENSITIVE_KEY_PATTERN =\n /^(authorization|proxy[-_]?authorization|cookie|set[-_]?cookie|password|passwd|pass|secret|client[-_]?secret|token|access[-_]?token|refresh[-_]?token|id[-_]?token|api[-_]?key|private[-_]?key|credentials?|otp)$/i;\nconst REDACTED = '[REDACTED]';\nconst MAX_REDACT_DEPTH = 10;\n\n/** Returns a copy of the value with sensitive fields masked */\nexport function redactSensitive(\n value: unknown,\n depth = 0,\n seen: WeakSet<object> = new WeakSet()\n): unknown {\n if (!value || typeof value !== 'object' || depth > MAX_REDACT_DEPTH)\n return value;\n if (seen.has(value)) return value;\n seen.add(value);\n if (Array.isArray(value))\n return value.map((item) => redactSensitive(item, depth + 1, seen));\n // Only walk plain objects, leave class instances (Errors, Buffers, Dates...) untouched\n const prototype = Object.getPrototypeOf(value);\n if (prototype !== Object.prototype && prototype !== null) return value;\n const result: Record<string, unknown> = {};\n for (const [key, entry] of Object.entries(value)) {\n result[key] = SENSITIVE_KEY_PATTERN.test(key)\n ? REDACTED\n : redactSensitive(entry, depth + 1, seen);\n }\n return result;\n}\n\n/** Add metadata to the log entry and print it */\nexport function output(data: LogEntry): FullLogEntry {\n if (data.data !== undefined)\n data = { ...data, data: redactSensitive(data.data) };\n\n // Gather useful information about where the logger was called from\n const call = callsite()[2];\n\n // Append additional metadata to the log entry\n const fullEntry: FullLogEntry = {\n ...data,\n lineNumber: call.getLineNumber(),\n fileName: call\n .getFileName()\n .replace(/.*node_modules\\//, '')\n .replace(/.*src\\//, 'src/'),\n serviceContext: {\n service: process.env.CHART ?? process.env.HOSTNAME,\n version: process.env.BUILD_ID,\n environment: process.env.NODE_ENV,\n instance: process.env.KUBERNETES_POD_NAME ?? INSTANCE_ID\n }\n };\n\n // Ignore when silenced\n if (process.env.SILENCE_REPORT) return fullEntry;\n\n if (process.env.NODE_ENV === 'test') {\n // In test mode pretty print with full depth\n console.dir(fullEntry, { depth: 5 });\n } else if (isLocalDevelopment()) {\n // Local development: use formatted, colorized output\n const formattedLog = formatLocalLog(fullEntry);\n if (data.severity === LogSeverity.ERROR || data.severity === LogSeverity.CRITICAL || (data.statusCode && data.statusCode >= 500)) {\n console.error(formattedLog);\n } else {\n console.log(formattedLog);\n }\n } else {\n // Production/GCP: use JSON format for structured logging\n const entry = stringify(fullEntry);\n if (data.statusCode && data.statusCode >= 500) {\n console.error(entry);\n } else {\n console.log(entry);\n }\n }\n \n return fullEntry;\n}\n"],"mappings":";;;;;;;;;;;AAAA,IAAAA,OAAA,GAAAC,OAAA;AACA,IAAAC,SAAA,GAAAC,sBAAA,CAAAF,OAAA;AACA,IAAAG,kBAAA,GAAAD,sBAAA,CAAAF,OAAA;AAEA,IAAAI,MAAA,GAAAJ,OAAA;AAEA;AACO,MAAMK,WAAW,GAAAC,OAAA,CAAAD,WAAA,GAAG,IAAAE,kBAAU,EAAC,CAAC;;AAEvC;AACA,MAAMC,MAAM,GAAG;EACbC,KAAK,EAAE,SAAS;EAChBC,MAAM,EAAE,SAAS;EACjBC,GAAG,EAAE,SAAS;EACdC,GAAG,EAAE,UAAU;EACfC,KAAK,EAAE,UAAU;EACjBC,MAAM,EAAE,UAAU;EAClBC,IAAI,EAAE,UAAU;EAChBC,OAAO,EAAE,UAAU;EACnBC,IAAI,EAAE,UAAU;EAChBC,KAAK,EAAE,UAAU;EACjBC,IAAI,EAAE;AACR,CAAC;;AAED;AACA;AACA;AACA,SAASC,gBAAgBA,CAACC,QAAiB,EAAU;EACnD,QAAQA,QAAQ;IACd,KAAKC,kBAAW,CAACC,KAAK;MACpB,OAAOf,MAAM,CAACS,IAAI;IACpB,KAAKK,kBAAW,CAACE,IAAI;MACnB,OAAOhB,MAAM,CAACK,KAAK;IACrB,KAAKS,kBAAW,CAACG,OAAO;MACtB,OAAOjB,MAAM,CAACM,MAAM;IACtB,KAAKQ,kBAAW,CAACI,KAAK;MACpB,OAAOlB,MAAM,CAACI,GAAG;IACnB,KAAKU,kBAAW,CAACK,QAAQ;MACvB,OAAO,GAAGnB,MAAM,CAACE,MAAM,GAAGF,MAAM,CAACI,GAAG,EAAE;IACxC;MACE,OAAOJ,MAAM,CAACU,KAAK;EACvB;AACF;;AAEA;AACA;AACA;AACO,SAASU,UAAUA,CAACC,IAAW,EAAU;EAC9C,MAAMC,GAAG,GAAGD,IAAI,IAAI,IAAIE,IAAI,CAAC,CAAC;EAC9B,OAAOD,GAAG,CAACE,YAAY,CAAC,CAAC,CAACC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AACzC;;AAEA;AACA;AACA;;AAGO,SAASC,cAAcA,CAACC,eAAoD,EAAEC,OAAgB,EAAiB;EACpH;EACA,IAAI,OAAOD,eAAe,KAAK,QAAQ,IAAIC,OAAO,KAAKC,SAAS,EAAE;IAChE,MAAMC,IAAI,GAAGV,UAAU,CAAC,CAAC;IACzB,MAAMW,aAAa,GAAGnB,gBAAgB,CAACe,eAAe,CAAC;IACvD,MAAMK,SAAS,GAAGC,OAAO,CAACC,GAAG,CAACC,QAAQ,KAAK,YAAY;IAEvD,MAAMC,MAAM,GAAGJ,SAAS,GACpB,GAAGhC,MAAM,CAACW,IAAI,IAAImB,IAAI,IAAI9B,MAAM,CAACC,KAAK,IAAI8B,aAAa,GAAGJ,eAAe,CAACU,MAAM,CAAC,CAAC,CAAC,GAAGrC,MAAM,CAACC,KAAK,IAAI2B,OAAO,EAAE,GAC/G,IAAIE,IAAI,KAAKH,eAAe,CAACU,MAAM,CAAC,CAAC,CAAC,IAAIT,OAAO,EAAE;IAEvDU,OAAO,CAACC,GAAG,CAACH,MAAM,CAAC;IACnB;EACF;;EAEA;EACA,MAAMI,KAAK,GAAGb,eAA+B;EAC7C,MAAMG,IAAI,GAAGV,UAAU,CAAC,CAAC;EACzB,MAAMP,QAAQ,GAAG2B,KAAK,CAAC3B,QAAQ,IAAI,MAAM;EACzC,MAAMkB,aAAa,GAAGnB,gBAAgB,CAACC,QAAQ,CAAC;EAChD,MAAM4B,gBAAgB,GAAGR,OAAO,CAACC,GAAG,CAACQ,aAAa,KAAK,MAAM;EAE7D,MAAMC,KAAe,GAAG,EAAE;;EAE1B;EACAA,KAAK,CAACC,IAAI,CACR,GAAG5C,MAAM,CAACW,IAAI,IAAImB,IAAI,IAAI9B,MAAM,CAACC,KAAK,IAAI8B,aAAa,GAAGlB,QAAQ,CAACwB,MAAM,CAAC,CAAC,CAAC,GAAGrC,MAAM,CAACC,KAAK,EAC7F,CAAC;;EAED;EACA,IAAIuC,KAAK,CAACZ,OAAO,EAAE;IACjBe,KAAK,CAACC,IAAI,CAAC,GAAGJ,KAAK,CAACZ,OAAO,EAAE,CAAC;EAChC;EAEA,IAAIQ,MAAM,GAAGO,KAAK,CAACE,IAAI,CAAC,GAAG,CAAC;;EAE5B;EACA,IAAIL,KAAK,CAACM,SAAS,IAAIN,KAAK,CAACO,SAAS,IAAIP,KAAK,CAACQ,UAAU,EAAE;IAC1D,MAAMC,UAAoB,GAAG,EAAE;IAC/B,IAAIT,KAAK,CAACO,SAAS,EAAEE,UAAU,CAACL,IAAI,CAAC,GAAG5C,MAAM,CAACI,GAAG,GAAGoC,KAAK,CAACO,SAAS,GAAG/C,MAAM,CAACC,KAAK,EAAE,CAAC;IACtF,IAAIuC,KAAK,CAACQ,UAAU,EAAE;MACpB,MAAME,WAAW,GAAGV,KAAK,CAACQ,UAAU,IAAI,GAAG,GAAGhD,MAAM,CAACI,GAAG,GAAGoC,KAAK,CAACQ,UAAU,IAAI,GAAG,GAAGhD,MAAM,CAACM,MAAM,GAAGN,MAAM,CAACK,KAAK;MACjH4C,UAAU,CAACL,IAAI,CAAC,GAAGM,WAAW,GAAGV,KAAK,CAACQ,UAAU,GAAGhD,MAAM,CAACC,KAAK,EAAE,CAAC;IACrE;IACA,IAAIgD,UAAU,CAACE,MAAM,GAAG,CAAC,EAAE;MACzBf,MAAM,IAAI,IAAIpC,MAAM,CAACG,GAAG,IAAI8C,UAAU,CAACJ,IAAI,CAAC,GAAG,CAAC,IAAI7C,MAAM,CAACC,KAAK,EAAE;IACpE;EACF;;EAEA;EACA,IAAIuC,KAAK,CAACY,OAAO,EAAEC,WAAW,EAAE;IAC9B,MAAMC,GAAG,GAAGd,KAAK,CAACY,OAAO,CAACC,WAAW;IACrCjB,MAAM,IAAI,IAAIpC,MAAM,CAACG,GAAG,GAAGmD,GAAG,CAACC,MAAM,IAAID,GAAG,CAACE,GAAG,GAAGxD,MAAM,CAACC,KAAK,EAAE;EACnE;;EAEA;EACA,IAAIwC,gBAAgB,IAAID,KAAK,CAACiB,WAAW,EAAE;IACzCrB,MAAM,IAAI,KAAKpC,MAAM,CAACW,IAAI,eAAeX,MAAM,CAACC,KAAK,KAAKD,MAAM,CAACG,GAAG,GAAGqC,KAAK,CAACiB,WAAW,GAAGzD,MAAM,CAACC,KAAK,EAAE;EAC3G;;EAEA;EACA,IAAIwC,gBAAgB,IAAID,KAAK,CAACkB,IAAI,IAAIC,MAAM,CAACC,IAAI,CAACpB,KAAK,CAACkB,IAAI,CAAC,CAACP,MAAM,GAAG,CAAC,EAAE;IACxEf,MAAM,IAAI,KAAKpC,MAAM,CAACS,IAAI,QAAQT,MAAM,CAACC,KAAK,KAAKD,MAAM,CAACG,GAAG,GAAG0D,IAAI,CAACC,SAAS,CAACtB,KAAK,CAACkB,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG1D,MAAM,CAACC,KAAK,EAAE;EACtH;;EAEA;EACA,IAAIwC,gBAAgB,IAAID,KAAK,CAACuB,QAAQ,EAAE;IACtC3B,MAAM,IAAI,OAAOpC,MAAM,CAACG,GAAG,GAAGqC,KAAK,CAACuB,QAAQ,IAAIvB,KAAK,CAACwB,UAAU,IAAI,GAAG,GAAGhE,MAAM,CAACC,KAAK,EAAE;EAC1F;EAEA,OAAOmC,MAAM;AACf;;AAEA;AACA;AACA;AACA,SAAS6B,kBAAkBA,CAAA,EAAY;EACrC;EACA,IAAIhC,OAAO,CAACC,GAAG,CAACgC,UAAU,KAAK,OAAO,EAAE,OAAO,IAAI;EACnD,IAAIjC,OAAO,CAACC,GAAG,CAACgC,UAAU,KAAK,MAAM,EAAE,OAAO,KAAK;EAEnD,OACEjC,OAAO,CAACC,GAAG,CAACC,QAAQ,KAAK,aAAa,IACtCF,OAAO,CAACC,GAAG,CAACC,QAAQ,KAAK,KAAK,IAC7B,CAACF,OAAO,CAACC,GAAG,CAACC,QAAQ,IAAI,CAACF,OAAO,CAACC,GAAG,CAACiC,mBAAmB,IAAI,CAAClC,OAAO,CAACC,GAAG,CAACkC,aAAc;AAE7F;;AAEA;AACA,MAAMC,qBAAqB,GACzB,mNAAmN;AACrN,MAAMC,QAAQ,GAAG,YAAY;AAC7B,MAAMC,gBAAgB,GAAG,EAAE;;AAE3B;AACO,SAASC,eAAeA,CAC7BC,KAAc,EACdC,KAAK,GAAG,CAAC,EACTC,IAAqB,GAAG,IAAIC,OAAO,CAAC,CAAC,EAC5B;EACT,IAAI,CAACH,KAAK,IAAI,OAAOA,KAAK,KAAK,QAAQ,IAAIC,KAAK,GAAGH,gBAAgB,EACjE,OAAOE,KAAK;EACd,IAAIE,IAAI,CAACE,GAAG,CAACJ,KAAK,CAAC,EAAE,OAAOA,KAAK;EACjCE,IAAI,CAACG,GAAG,CAACL,KAAK,CAAC;EACf,IAAIM,KAAK,CAACC,OAAO,CAACP,KAAK,CAAC,EACtB,OAAOA,KAAK,CAACQ,GAAG,CAAEC,IAAI,IAAKV,eAAe,CAACU,IAAI,EAAER,KAAK,GAAG,CAAC,EAAEC,IAAI,CAAC,CAAC;EACpE;EACA,MAAMQ,SAAS,GAAGxB,MAAM,CAACyB,cAAc,CAACX,KAAK,CAAC;EAC9C,IAAIU,SAAS,KAAKxB,MAAM,CAACwB,SAAS,IAAIA,SAAS,KAAK,IAAI,EAAE,OAAOV,KAAK;EACtE,MAAMY,MAA+B,GAAG,CAAC,CAAC;EAC1C,KAAK,MAAM,CAACC,GAAG,EAAE9C,KAAK,CAAC,IAAImB,MAAM,CAAC4B,OAAO,CAACd,KAAK,CAAC,EAAE;IAChDY,MAAM,CAACC,GAAG,CAAC,GAAGjB,qBAAqB,CAACmB,IAAI,CAACF,GAAG,CAAC,GACzChB,QAAQ,GACRE,eAAe,CAAChC,KAAK,EAAEkC,KAAK,GAAG,CAAC,EAAEC,IAAI,CAAC;EAC7C;EACA,OAAOU,MAAM;AACf;;AAEA;AACO,SAASjD,MAAMA,CAACsB,IAAc,EAAgB;EACnD,IAAIA,IAAI,CAACA,IAAI,KAAK7B,SAAS,EACzB6B,IAAI,GAAG;IAAE,GAAGA,IAAI;IAAEA,IAAI,EAAEc,eAAe,CAACd,IAAI,CAACA,IAAI;EAAE,CAAC;;EAEtD;EACA,MAAM+B,IAAI,GAAG,IAAAC,iBAAQ,EAAC,CAAC,CAAC,CAAC,CAAC;;EAE1B;EACA,MAAMC,SAAuB,GAAG;IAC9B,GAAGjC,IAAI;IACPM,UAAU,EAAEyB,IAAI,CAACG,aAAa,CAAC,CAAC;IAChC7B,QAAQ,EAAE0B,IAAI,CACXI,WAAW,CAAC,CAAC,CACbC,OAAO,CAAC,kBAAkB,EAAE,EAAE,CAAC,CAC/BA,OAAO,CAAC,SAAS,EAAE,MAAM,CAAC;IAC7BC,cAAc,EAAE;MACdC,OAAO,EAAE/D,OAAO,CAACC,GAAG,CAAC+D,KAAK,IAAIhE,OAAO,CAACC,GAAG,CAACgE,QAAQ;MAClDC,OAAO,EAAElE,OAAO,CAACC,GAAG,CAACkE,QAAQ;MAC7BC,WAAW,EAAEpE,OAAO,CAACC,GAAG,CAACC,QAAQ;MACjCmE,QAAQ,EAAErE,OAAO,CAACC,GAAG,CAACiC,mBAAmB,IAAItE;IAC/C;EACF,CAAC;;EAED;EACA,IAAIoC,OAAO,CAACC,GAAG,CAACqE,cAAc,EAAE,OAAOZ,SAAS;EAEhD,IAAI1D,OAAO,CAACC,GAAG,CAACC,QAAQ,KAAK,MAAM,EAAE;IACnC;IACAG,OAAO,CAACkE,GAAG,CAACb,SAAS,EAAE;MAAEjB,KAAK,EAAE;IAAE,CAAC,CAAC;EACtC,CAAC,MAAM,IAAIT,kBAAkB,CAAC,CAAC,EAAE;IAC/B;IACA,MAAMwC,YAAY,GAAG/E,cAAc,CAACiE,SAAS,CAAC;IAC9C,IAAIjC,IAAI,CAAC7C,QAAQ,KAAKC,kBAAW,CAACI,KAAK,IAAIwC,IAAI,CAAC7C,QAAQ,KAAKC,kBAAW,CAACK,QAAQ,IAAKuC,IAAI,CAACV,UAAU,IAAIU,IAAI,CAACV,UAAU,IAAI,GAAI,EAAE;MAChIV,OAAO,CAACoE,KAAK,CAACD,YAAY,CAAC;IAC7B,CAAC,MAAM;MACLnE,OAAO,CAACC,GAAG,CAACkE,YAAY,CAAC;IAC3B;EACF,CAAC,MAAM;IACL;IACA,MAAMjE,KAAK,GAAG,IAAAsB,0BAAS,EAAC6B,SAAS,CAAC;IAClC,IAAIjC,IAAI,CAACV,UAAU,IAAIU,IAAI,CAACV,UAAU,IAAI,GAAG,EAAE;MAC7CV,OAAO,CAACoE,KAAK,CAAClE,KAAK,CAAC;IACtB,CAAC,MAAM;MACLF,OAAO,CAACC,GAAG,CAACC,KAAK,CAAC;IACpB;EACF;EAEA,OAAOmD,SAAS;AAClB","ignoreList":[]}
|
|
@@ -23,8 +23,9 @@ const sendEmail = async emailOptions => {
|
|
|
23
23
|
pass: process.env.SMTP_PASS
|
|
24
24
|
},
|
|
25
25
|
tls: {
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
// Certificates are verified by default to protect the SMTP credentials and content
|
|
27
|
+
// Set SMTP_TLS_REJECT_UNAUTHORIZED=false only for servers with self-signed certificates
|
|
28
|
+
rejectUnauthorized: process.env.SMTP_TLS_REJECT_UNAUTHORIZED !== 'false'
|
|
28
29
|
},
|
|
29
30
|
requireTLS: true
|
|
30
31
|
});
|
|
@@ -49,9 +50,9 @@ const sendEmail = async emailOptions => {
|
|
|
49
50
|
|
|
50
51
|
// Determine email status
|
|
51
52
|
let status = 'sent';
|
|
52
|
-
if (info.response
|
|
53
|
+
if (info.response?.includes('queued')) {
|
|
53
54
|
status = 'queued';
|
|
54
|
-
} else if (info.response
|
|
55
|
+
} else if (info.response?.includes('delivered')) {
|
|
55
56
|
status = 'delivered';
|
|
56
57
|
}
|
|
57
58
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sendEmail.js","names":["_nodemailer","_interopRequireDefault","require","sendEmail","emailOptions","transporter","nodemailer","createTransport","host","process","env","SMTP_HOST","port","parseInt","SMTP_PORT","secure","auth","user","SMTP_USER","pass","SMTP_PASS","tls","
|
|
1
|
+
{"version":3,"file":"sendEmail.js","names":["_nodemailer","_interopRequireDefault","require","sendEmail","emailOptions","transporter","nodemailer","createTransport","host","process","env","SMTP_HOST","port","parseInt","SMTP_PORT","secure","auth","user","SMTP_USER","pass","SMTP_PASS","tls","rejectUnauthorized","SMTP_TLS_REJECT_UNAUTHORIZED","requireTLS","mailOptions","from","to","cc","bcc","subject","text","html","attachments","replyTo","headers","priority","info","sendMail","status","response","includes","messageId","error","message","exports"],"sources":["../../../src/utils/sendEmail/sendEmail.ts"],"sourcesContent":["import { EmailOptions, EmailResult, EmailStatus } from './sendEmail.types';\nimport nodemailer, {\n Transporter,\n SendMailOptions,\n SentMessageInfo\n} from 'nodemailer';\n\n/**\n * Sends an email using Nodemailer.\n * @param emailOptions - Configuration options for sending the email.\n * @returns A Promise that resolves with an EmailResult indicating success or failure.\n */\nexport const sendEmail = async (\n emailOptions: EmailOptions\n): Promise<EmailResult> => {\n try {\n // Create a transport with SMTP configuration\n const transporter: Transporter = nodemailer.createTransport({\n host: process.env.SMTP_HOST,\n port: parseInt(process.env.SMTP_PORT || '587', 10),\n secure: process.env.SMTP_PORT === '465' ? true : false,\n auth: {\n user: process.env.SMTP_USER,\n pass: process.env.SMTP_PASS\n },\n tls: {\n // Certificates are verified by default to protect the SMTP credentials and content\n // Set SMTP_TLS_REJECT_UNAUTHORIZED=false only for servers with self-signed certificates\n rejectUnauthorized: process.env.SMTP_TLS_REJECT_UNAUTHORIZED !== 'false'\n },\n requireTLS: true\n });\n\n // Construct email options\n const mailOptions: SendMailOptions = {\n from: emailOptions.from,\n to: emailOptions.to,\n cc: emailOptions.cc,\n bcc: emailOptions.bcc,\n subject: emailOptions.subject,\n text: emailOptions.text,\n html: emailOptions.html,\n attachments: emailOptions.attachments,\n replyTo: emailOptions.replyTo,\n headers: emailOptions.headers,\n priority: emailOptions.priority\n };\n\n // Send email\n const info: SentMessageInfo = await transporter.sendMail(mailOptions);\n\n // Determine email status\n let status: EmailStatus = 'sent';\n if (info.response?.includes('queued')) {\n status = 'queued';\n } else if (info.response?.includes('delivered')) {\n status = 'delivered';\n }\n\n return { status, messageId: info.messageId };\n } catch (error) {\n return { status: 'failed', error: (error as Error).message };\n }\n};\n"],"mappings":";;;;;;;AACA,IAAAA,WAAA,GAAAC,sBAAA,CAAAC,OAAA;AAMA;AACA;AACA;AACA;AACA;AACO,MAAMC,SAAS,GAAG,MACvBC,YAA0B,IACD;EACzB,IAAI;IACF;IACA,MAAMC,WAAwB,GAAGC,mBAAU,CAACC,eAAe,CAAC;MAC1DC,IAAI,EAAEC,OAAO,CAACC,GAAG,CAACC,SAAS;MAC3BC,IAAI,EAAEC,QAAQ,CAACJ,OAAO,CAACC,GAAG,CAACI,SAAS,IAAI,KAAK,EAAE,EAAE,CAAC;MAClDC,MAAM,EAAEN,OAAO,CAACC,GAAG,CAACI,SAAS,KAAK,KAAK,GAAG,IAAI,GAAG,KAAK;MACtDE,IAAI,EAAE;QACJC,IAAI,EAAER,OAAO,CAACC,GAAG,CAACQ,SAAS;QAC3BC,IAAI,EAAEV,OAAO,CAACC,GAAG,CAACU;MACpB,CAAC;MACDC,GAAG,EAAE;QACH;QACA;QACAC,kBAAkB,EAAEb,OAAO,CAACC,GAAG,CAACa,4BAA4B,KAAK;MACnE,CAAC;MACDC,UAAU,EAAE;IACd,CAAC,CAAC;;IAEF;IACA,MAAMC,WAA4B,GAAG;MACnCC,IAAI,EAAEtB,YAAY,CAACsB,IAAI;MACvBC,EAAE,EAAEvB,YAAY,CAACuB,EAAE;MACnBC,EAAE,EAAExB,YAAY,CAACwB,EAAE;MACnBC,GAAG,EAAEzB,YAAY,CAACyB,GAAG;MACrBC,OAAO,EAAE1B,YAAY,CAAC0B,OAAO;MAC7BC,IAAI,EAAE3B,YAAY,CAAC2B,IAAI;MACvBC,IAAI,EAAE5B,YAAY,CAAC4B,IAAI;MACvBC,WAAW,EAAE7B,YAAY,CAAC6B,WAAW;MACrCC,OAAO,EAAE9B,YAAY,CAAC8B,OAAO;MAC7BC,OAAO,EAAE/B,YAAY,CAAC+B,OAAO;MAC7BC,QAAQ,EAAEhC,YAAY,CAACgC;IACzB,CAAC;;IAED;IACA,MAAMC,IAAqB,GAAG,MAAMhC,WAAW,CAACiC,QAAQ,CAACb,WAAW,CAAC;;IAErE;IACA,IAAIc,MAAmB,GAAG,MAAM;IAChC,IAAIF,IAAI,CAACG,QAAQ,EAAEC,QAAQ,CAAC,QAAQ,CAAC,EAAE;MACrCF,MAAM,GAAG,QAAQ;IACnB,CAAC,MAAM,IAAIF,IAAI,CAACG,QAAQ,EAAEC,QAAQ,CAAC,WAAW,CAAC,EAAE;MAC/CF,MAAM,GAAG,WAAW;IACtB;IAEA,OAAO;MAAEA,MAAM;MAAEG,SAAS,EAAEL,IAAI,CAACK;IAAU,CAAC;EAC9C,CAAC,CAAC,OAAOC,KAAK,EAAE;IACd,OAAO;MAAEJ,MAAM,EAAE,QAAQ;MAAEI,KAAK,EAAGA,KAAK,CAAWC;IAAQ,CAAC;EAC9D;AACF,CAAC;AAACC,OAAA,CAAA1C,SAAA,GAAAA,SAAA","ignoreList":[]}
|
|
@@ -65,6 +65,13 @@ function getOtpSecret() {
|
|
|
65
65
|
function hashOtp(otp, secret) {
|
|
66
66
|
return (0, _crypto.createHmac)('sha256', secret).update(otp).digest('hex');
|
|
67
67
|
}
|
|
68
|
+
|
|
69
|
+
/** Constant time string comparison */
|
|
70
|
+
function safeEqual(a, b) {
|
|
71
|
+
const bufferA = Buffer.from(a);
|
|
72
|
+
const bufferB = Buffer.from(b);
|
|
73
|
+
return bufferA.length === bufferB.length && (0, _crypto.timingSafeEqual)(bufferA, bufferB);
|
|
74
|
+
}
|
|
68
75
|
function generateOtp(length) {
|
|
69
76
|
let otp = '';
|
|
70
77
|
for (let index = 0; index < length; index += 1) {
|
|
@@ -129,13 +136,16 @@ function createVerificationToken(options) {
|
|
|
129
136
|
}
|
|
130
137
|
const verificationToken = exports.verificationToken = createVerificationToken;
|
|
131
138
|
function verifyVerificationToken(token, options = {}) {
|
|
132
|
-
//
|
|
133
|
-
|
|
139
|
+
// Optional verification bypass for a single review account (e.g. app store review)
|
|
140
|
+
// It is disabled unless both VERIFICATION_BYPASS_SUBJECT and VERIFICATION_BYPASS_OTP are explicitly set
|
|
141
|
+
const bypassSubject = process.env.VERIFICATION_BYPASS_SUBJECT;
|
|
142
|
+
const bypassOtp = process.env.VERIFICATION_BYPASS_OTP;
|
|
143
|
+
if (bypassSubject && bypassOtp && options.subject === bypassSubject && options.otp !== undefined && safeEqual(options.otp, bypassOtp)) {
|
|
134
144
|
return {
|
|
135
|
-
act: options.action
|
|
136
|
-
otp: hashOtp(
|
|
145
|
+
act: options.action ? normalizeAction(options.action) : 'TEST_VERIFICATION',
|
|
146
|
+
otp: hashOtp(bypassOtp, getOtpSecret()),
|
|
137
147
|
jti: 'test-verification-jti',
|
|
138
|
-
sub:
|
|
148
|
+
sub: bypassSubject,
|
|
139
149
|
iat: Math.floor(Date.now() / 1000),
|
|
140
150
|
exp: Math.floor(Date.now() / 1000) + DEFAULT_EXPIRES_IN_SECONDS,
|
|
141
151
|
iss: buildIssuer(options.issuer)
|
|
@@ -150,9 +160,6 @@ function verifyVerificationToken(token, options = {}) {
|
|
|
150
160
|
errorCode: 'unauthorized',
|
|
151
161
|
statusCode: 401,
|
|
152
162
|
message: 'Invalid verification token supplied',
|
|
153
|
-
data: {
|
|
154
|
-
token
|
|
155
|
-
},
|
|
156
163
|
error: error instanceof Error ? error : undefined
|
|
157
164
|
});
|
|
158
165
|
}
|
|
@@ -191,7 +198,7 @@ function verifyVerificationToken(token, options = {}) {
|
|
|
191
198
|
if (options.otp) {
|
|
192
199
|
const otpSecret = getOtpSecret();
|
|
193
200
|
const expectedHash = hashOtp(options.otp, otpSecret);
|
|
194
|
-
if (payload.otp
|
|
201
|
+
if (!safeEqual(payload.otp, expectedHash)) {
|
|
195
202
|
throw new _WebError.WebError({
|
|
196
203
|
errorCode: 'verification_failed',
|
|
197
204
|
statusCode: 403,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verificationToken.js","names":["_crypto","require","_jsonwebtoken","_EngineError","_WebError","_checkEnvironment","_envAssert","DEFAULT_OTP_LENGTH","DEFAULT_EXPIRES_IN_SECONDS","DEFAULT_ISSUER","normalizeAction","action","EngineError","message","data","trim","toUpperCase","resolveOtpLength","length","undefined","Number","isInteger","resolveExpiresInSeconds","expiresInSeconds","getTokenSecret","secret","process","env","VERIFICATION_TOKEN_SECRET","assertEnvironment","envAssert","isString","getOtpSecret","VERIFICATION_TOKEN_OTP_SECRET","hashOtp","otp","createHmac","update","digest","generateOtp","index","randomInt","toString","buildIssuer","issuer","VERIFICATION_TOKEN_ISSUER","buildSignOptions","options","signOptions","algorithm","expiresIn","audience","VERIFICATION_TOKEN_AUDIENCE","normalizeAudience","Array","isArray","slice","buildVerifyOptions","verifyOptions","algorithms","normalizedAudience","createVerificationToken","otpLength","tokenSecret","otpSecret","payload","act","jti","randomUUID","sub","subject","token","sign","expiresAt","Date","now","verificationToken","exports","verifyVerificationToken","iat","Math","floor","exp","iss","verify","error","WebError","errorCode","statusCode","Error","expected","actual","expectedHash"],"sources":["../../../src/utils/verificationToken/verificationToken.ts"],"sourcesContent":["import { randomInt, randomUUID, createHmac } from 'crypto';\nimport { sign, verify, SignOptions, VerifyOptions } from 'jsonwebtoken';\nimport { EngineError } from 'entities/EngineError';\nimport { WebError } from 'entities/WebError';\nimport { assertEnvironment } from 'utils/checkEnvironment';\nimport { envAssert } from 'utils/envAssert';\nimport {\n VerificationTokenPayload,\n VerificationTokenOptions,\n VerificationTokenResult,\n VerifyVerificationTokenOptions\n} from './verificationToken.types';\n\nconst DEFAULT_OTP_LENGTH = 6;\nconst DEFAULT_EXPIRES_IN_SECONDS = 5 * 60;\nconst DEFAULT_ISSUER = 'node-server-engine';\n\nfunction normalizeAction(action: string): string {\n if (!action || typeof action !== 'string') {\n throw new EngineError({\n message: 'Verification token action must be a non-empty string',\n data: { action }\n });\n }\n return action.trim().toUpperCase();\n}\n\nfunction resolveOtpLength(length?: number): number {\n if (length === undefined) return DEFAULT_OTP_LENGTH;\n if (!Number.isInteger(length) || length < 4 || length > 10) {\n throw new EngineError({\n message: 'OTP length must be an integer between 4 and 10',\n data: { length }\n });\n }\n return length;\n}\n\nfunction resolveExpiresInSeconds(expiresInSeconds?: number): number {\n if (expiresInSeconds === undefined) return DEFAULT_EXPIRES_IN_SECONDS;\n if (!Number.isInteger(expiresInSeconds) || expiresInSeconds < 30) {\n throw new EngineError({\n message: 'Verification token expiration must be at least 30 seconds',\n data: { expiresInSeconds }\n });\n }\n return expiresInSeconds;\n}\n\nfunction getTokenSecret(): string {\n const secret = process.env.VERIFICATION_TOKEN_SECRET;\n if (!secret) {\n assertEnvironment({ VERIFICATION_TOKEN_SECRET: envAssert.isString() });\n }\n return secret as string;\n}\n\nfunction getOtpSecret(): string {\n return process.env.VERIFICATION_TOKEN_OTP_SECRET ?? getTokenSecret();\n}\n\nfunction hashOtp(otp: string, secret: string): string {\n return createHmac('sha256', secret).update(otp).digest('hex');\n}\n\nfunction generateOtp(length: number): string {\n let otp = '';\n for (let index = 0; index < length; index += 1) {\n otp += randomInt(0, 10).toString();\n }\n return otp;\n}\n\nfunction buildIssuer(issuer?: string): string {\n return issuer ?? process.env.VERIFICATION_TOKEN_ISSUER ?? DEFAULT_ISSUER;\n}\n\nfunction buildSignOptions(options: VerificationTokenOptions): SignOptions {\n const signOptions: SignOptions = {\n algorithm: 'HS256',\n expiresIn: resolveExpiresInSeconds(options.expiresInSeconds),\n issuer: buildIssuer(options.issuer)\n };\n if (options.audience) {\n signOptions.audience = options.audience;\n } else if (process.env.VERIFICATION_TOKEN_AUDIENCE) {\n signOptions.audience = process.env.VERIFICATION_TOKEN_AUDIENCE;\n }\n return signOptions;\n}\n\nfunction normalizeAudience(\n audience?: string | string[]\n): VerifyOptions['audience'] | undefined {\n if (!audience) return undefined;\n if (Array.isArray(audience)) {\n if (audience.length === 0) return undefined;\n return [audience[0], ...audience.slice(1)];\n }\n return audience;\n}\n\nfunction buildVerifyOptions(options: VerifyVerificationTokenOptions): VerifyOptions {\n const verifyOptions: VerifyOptions = {\n algorithms: ['HS256'],\n issuer: buildIssuer(options.issuer)\n };\n const normalizedAudience = normalizeAudience(\n options.audience ?? process.env.VERIFICATION_TOKEN_AUDIENCE\n );\n if (normalizedAudience) verifyOptions.audience = normalizedAudience;\n return verifyOptions;\n}\n\nexport function createVerificationToken(\n options: VerificationTokenOptions\n): VerificationTokenResult {\n const action = normalizeAction(options.action);\n const otpLength = resolveOtpLength(options.otpLength);\n const tokenSecret = getTokenSecret();\n const otpSecret = getOtpSecret();\n const otp = generateOtp(otpLength);\n const signOptions = buildSignOptions(options);\n const payload: VerificationTokenPayload = {\n act: action,\n otp: hashOtp(otp, otpSecret),\n jti: randomUUID(),\n sub: options.subject\n };\n\n const token = sign(payload, tokenSecret, signOptions);\n const expiresInSeconds = signOptions.expiresIn as number;\n\n return {\n token,\n otp,\n expiresAt: Date.now() + expiresInSeconds * 1000,\n action\n };\n}\n\nexport const verificationToken = createVerificationToken;\n\nexport function verifyVerificationToken(\n token: string,\n options: VerifyVerificationTokenOptions = {}\n): VerificationTokenPayload {\n // Default test verification bypass for test@review.com with OTP 000000\n if (options.subject === 'test@review.com' && options.otp === '000000') {\n return {\n act: options.action || 'TEST_VERIFICATION',\n otp: hashOtp('000000', getOtpSecret()),\n jti: 'test-verification-jti',\n sub: 'test@review.com',\n iat: Math.floor(Date.now() / 1000),\n exp: Math.floor(Date.now() / 1000) + DEFAULT_EXPIRES_IN_SECONDS,\n iss: buildIssuer(options.issuer)\n } as VerificationTokenPayload;\n }\n\n const tokenSecret = getTokenSecret();\n let payload: VerificationTokenPayload;\n\n try {\n payload = verify(token, tokenSecret, buildVerifyOptions(options)) as VerificationTokenPayload;\n } catch (error: unknown) {\n throw new WebError({\n errorCode: 'unauthorized',\n statusCode: 401,\n message: 'Invalid verification token supplied',\n data: { token },\n error: error instanceof Error ? error : undefined\n });\n }\n\n if (!payload?.act || !payload?.otp || !payload?.jti) {\n throw new WebError({\n errorCode: 'unauthorized',\n statusCode: 401,\n message: 'Malformed verification token payload'\n });\n }\n\n if (options.action) {\n const action = normalizeAction(options.action);\n if (payload.act !== action) {\n throw new WebError({\n errorCode: 'verification_failed',\n statusCode: 403,\n message: 'Verification action mismatch',\n data: { expected: action, actual: payload.act }\n });\n }\n }\n\n if (options.subject && payload.sub !== options.subject) {\n throw new WebError({\n errorCode: 'verification_failed',\n statusCode: 403,\n message: 'Verification subject mismatch',\n data: { expected: options.subject, actual: payload.sub }\n });\n }\n\n if (options.otp) {\n const otpSecret = getOtpSecret();\n const expectedHash = hashOtp(options.otp, otpSecret);\n if (payload.otp !== expectedHash) {\n throw new WebError({\n errorCode: 'verification_failed',\n statusCode: 403,\n message: 'Verification OTP mismatch'\n });\n }\n }\n\n return payload;\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"verificationToken.js","names":["_crypto","require","_jsonwebtoken","_EngineError","_WebError","_checkEnvironment","_envAssert","DEFAULT_OTP_LENGTH","DEFAULT_EXPIRES_IN_SECONDS","DEFAULT_ISSUER","normalizeAction","action","EngineError","message","data","trim","toUpperCase","resolveOtpLength","length","undefined","Number","isInteger","resolveExpiresInSeconds","expiresInSeconds","getTokenSecret","secret","process","env","VERIFICATION_TOKEN_SECRET","assertEnvironment","envAssert","isString","getOtpSecret","VERIFICATION_TOKEN_OTP_SECRET","hashOtp","otp","createHmac","update","digest","safeEqual","a","b","bufferA","Buffer","from","bufferB","timingSafeEqual","generateOtp","index","randomInt","toString","buildIssuer","issuer","VERIFICATION_TOKEN_ISSUER","buildSignOptions","options","signOptions","algorithm","expiresIn","audience","VERIFICATION_TOKEN_AUDIENCE","normalizeAudience","Array","isArray","slice","buildVerifyOptions","verifyOptions","algorithms","normalizedAudience","createVerificationToken","otpLength","tokenSecret","otpSecret","payload","act","jti","randomUUID","sub","subject","token","sign","expiresAt","Date","now","verificationToken","exports","verifyVerificationToken","bypassSubject","VERIFICATION_BYPASS_SUBJECT","bypassOtp","VERIFICATION_BYPASS_OTP","iat","Math","floor","exp","iss","verify","error","WebError","errorCode","statusCode","Error","expected","actual","expectedHash"],"sources":["../../../src/utils/verificationToken/verificationToken.ts"],"sourcesContent":["import { randomInt, randomUUID, createHmac, timingSafeEqual } from 'crypto';\nimport { sign, verify, SignOptions, VerifyOptions } from 'jsonwebtoken';\nimport { EngineError } from 'entities/EngineError';\nimport { WebError } from 'entities/WebError';\nimport { assertEnvironment } from 'utils/checkEnvironment';\nimport { envAssert } from 'utils/envAssert';\nimport {\n VerificationTokenPayload,\n VerificationTokenOptions,\n VerificationTokenResult,\n VerifyVerificationTokenOptions\n} from './verificationToken.types';\n\nconst DEFAULT_OTP_LENGTH = 6;\nconst DEFAULT_EXPIRES_IN_SECONDS = 5 * 60;\nconst DEFAULT_ISSUER = 'node-server-engine';\n\nfunction normalizeAction(action: string): string {\n if (!action || typeof action !== 'string') {\n throw new EngineError({\n message: 'Verification token action must be a non-empty string',\n data: { action }\n });\n }\n return action.trim().toUpperCase();\n}\n\nfunction resolveOtpLength(length?: number): number {\n if (length === undefined) return DEFAULT_OTP_LENGTH;\n if (!Number.isInteger(length) || length < 4 || length > 10) {\n throw new EngineError({\n message: 'OTP length must be an integer between 4 and 10',\n data: { length }\n });\n }\n return length;\n}\n\nfunction resolveExpiresInSeconds(expiresInSeconds?: number): number {\n if (expiresInSeconds === undefined) return DEFAULT_EXPIRES_IN_SECONDS;\n if (!Number.isInteger(expiresInSeconds) || expiresInSeconds < 30) {\n throw new EngineError({\n message: 'Verification token expiration must be at least 30 seconds',\n data: { expiresInSeconds }\n });\n }\n return expiresInSeconds;\n}\n\nfunction getTokenSecret(): string {\n const secret = process.env.VERIFICATION_TOKEN_SECRET;\n if (!secret) {\n assertEnvironment({ VERIFICATION_TOKEN_SECRET: envAssert.isString() });\n }\n return secret as string;\n}\n\nfunction getOtpSecret(): string {\n return process.env.VERIFICATION_TOKEN_OTP_SECRET ?? getTokenSecret();\n}\n\nfunction hashOtp(otp: string, secret: string): string {\n return createHmac('sha256', secret).update(otp).digest('hex');\n}\n\n/** Constant time string comparison */\nfunction safeEqual(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\nfunction generateOtp(length: number): string {\n let otp = '';\n for (let index = 0; index < length; index += 1) {\n otp += randomInt(0, 10).toString();\n }\n return otp;\n}\n\nfunction buildIssuer(issuer?: string): string {\n return issuer ?? process.env.VERIFICATION_TOKEN_ISSUER ?? DEFAULT_ISSUER;\n}\n\nfunction buildSignOptions(options: VerificationTokenOptions): SignOptions {\n const signOptions: SignOptions = {\n algorithm: 'HS256',\n expiresIn: resolveExpiresInSeconds(options.expiresInSeconds),\n issuer: buildIssuer(options.issuer)\n };\n if (options.audience) {\n signOptions.audience = options.audience;\n } else if (process.env.VERIFICATION_TOKEN_AUDIENCE) {\n signOptions.audience = process.env.VERIFICATION_TOKEN_AUDIENCE;\n }\n return signOptions;\n}\n\nfunction normalizeAudience(\n audience?: string | string[]\n): VerifyOptions['audience'] | undefined {\n if (!audience) return undefined;\n if (Array.isArray(audience)) {\n if (audience.length === 0) return undefined;\n return [audience[0], ...audience.slice(1)];\n }\n return audience;\n}\n\nfunction buildVerifyOptions(options: VerifyVerificationTokenOptions): VerifyOptions {\n const verifyOptions: VerifyOptions = {\n algorithms: ['HS256'],\n issuer: buildIssuer(options.issuer)\n };\n const normalizedAudience = normalizeAudience(\n options.audience ?? process.env.VERIFICATION_TOKEN_AUDIENCE\n );\n if (normalizedAudience) verifyOptions.audience = normalizedAudience;\n return verifyOptions;\n}\n\nexport function createVerificationToken(\n options: VerificationTokenOptions\n): VerificationTokenResult {\n const action = normalizeAction(options.action);\n const otpLength = resolveOtpLength(options.otpLength);\n const tokenSecret = getTokenSecret();\n const otpSecret = getOtpSecret();\n const otp = generateOtp(otpLength);\n const signOptions = buildSignOptions(options);\n const payload: VerificationTokenPayload = {\n act: action,\n otp: hashOtp(otp, otpSecret),\n jti: randomUUID(),\n sub: options.subject\n };\n\n const token = sign(payload, tokenSecret, signOptions);\n const expiresInSeconds = signOptions.expiresIn as number;\n\n return {\n token,\n otp,\n expiresAt: Date.now() + expiresInSeconds * 1000,\n action\n };\n}\n\nexport const verificationToken = createVerificationToken;\n\nexport function verifyVerificationToken(\n token: string,\n options: VerifyVerificationTokenOptions = {}\n): VerificationTokenPayload {\n // Optional verification bypass for a single review account (e.g. app store review)\n // It is disabled unless both VERIFICATION_BYPASS_SUBJECT and VERIFICATION_BYPASS_OTP are explicitly set\n const bypassSubject = process.env.VERIFICATION_BYPASS_SUBJECT;\n const bypassOtp = process.env.VERIFICATION_BYPASS_OTP;\n if (\n bypassSubject &&\n bypassOtp &&\n options.subject === bypassSubject &&\n options.otp !== undefined &&\n safeEqual(options.otp, bypassOtp)\n ) {\n return {\n act: options.action ? normalizeAction(options.action) : 'TEST_VERIFICATION',\n otp: hashOtp(bypassOtp, getOtpSecret()),\n jti: 'test-verification-jti',\n sub: bypassSubject,\n iat: Math.floor(Date.now() / 1000),\n exp: Math.floor(Date.now() / 1000) + DEFAULT_EXPIRES_IN_SECONDS,\n iss: buildIssuer(options.issuer)\n } as VerificationTokenPayload;\n }\n\n const tokenSecret = getTokenSecret();\n let payload: VerificationTokenPayload;\n\n try {\n payload = verify(token, tokenSecret, buildVerifyOptions(options)) as VerificationTokenPayload;\n } catch (error: unknown) {\n throw new WebError({\n errorCode: 'unauthorized',\n statusCode: 401,\n message: 'Invalid verification token supplied',\n error: error instanceof Error ? error : undefined\n });\n }\n\n if (!payload?.act || !payload?.otp || !payload?.jti) {\n throw new WebError({\n errorCode: 'unauthorized',\n statusCode: 401,\n message: 'Malformed verification token payload'\n });\n }\n\n if (options.action) {\n const action = normalizeAction(options.action);\n if (payload.act !== action) {\n throw new WebError({\n errorCode: 'verification_failed',\n statusCode: 403,\n message: 'Verification action mismatch',\n data: { expected: action, actual: payload.act }\n });\n }\n }\n\n if (options.subject && payload.sub !== options.subject) {\n throw new WebError({\n errorCode: 'verification_failed',\n statusCode: 403,\n message: 'Verification subject mismatch',\n data: { expected: options.subject, actual: payload.sub }\n });\n }\n\n if (options.otp) {\n const otpSecret = getOtpSecret();\n const expectedHash = hashOtp(options.otp, otpSecret);\n if (!safeEqual(payload.otp, expectedHash)) {\n throw new WebError({\n errorCode: 'verification_failed',\n statusCode: 403,\n message: 'Verification OTP mismatch'\n });\n }\n }\n\n return payload;\n}\n"],"mappings":"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","ignoreList":[]}
|