@bigid/apps-infrastructure-node-js 1.194.0 → 1.197.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/lib/abstractProviders/logsProvider.js +1 -3
- package/lib/dto/signatureData.d.ts +5 -0
- package/lib/dto/signatureData.js +2 -0
- package/lib/dto/signaturePayload.d.ts +4 -0
- package/lib/dto/signaturePayload.js +2 -0
- package/lib/interceptor/signatureValidationInterceptor.d.ts +2 -0
- package/lib/interceptor/signatureValidationInterceptor.js +58 -0
- package/lib/server.d.ts +3 -1
- package/lib/server.js +3 -2
- package/lib/services/publicKeyService.d.ts +1 -0
- package/lib/services/publicKeyService.js +59 -0
- package/lib/services/signatureValidationService.d.ts +1 -0
- package/lib/services/signatureValidationService.js +31 -0
- package/lib/utils/appLogger.js +1 -2
- package/lib/utils/constants.d.ts +1 -2
- package/lib/utils/constants.js +2 -3
- package/package.json +1 -1
- package/src/abstractProviders/logsProvider.ts +2 -6
- package/src/dto/signatureData.ts +6 -0
- package/src/dto/signaturePayload.ts +4 -0
- package/src/interceptor/signatureValidationInterceptor.ts +60 -0
- package/src/server.ts +10 -1
- package/src/services/publicKeyService.ts +52 -0
- package/src/services/signatureValidationService.ts +26 -0
- package/src/utils/appLogger.ts +2 -3
- package/src/utils/constants.ts +1 -2
|
@@ -3,11 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.fetchLogs = void 0;
|
|
4
4
|
const fs_1 = require("fs");
|
|
5
5
|
const constants_1 = require("../utils/constants");
|
|
6
|
-
const READONLY_MODE_ENABLED = process.env.READONLY_MODE_ENABLED === "true";
|
|
7
|
-
const logFolder = READONLY_MODE_ENABLED ? constants_1.TMP_LOGS_PATH : constants_1.LOGS_PATH;
|
|
8
6
|
const fetchLogs = (req, res) => {
|
|
9
7
|
const tenantId = req.headers.tenantid;
|
|
10
|
-
const data = (0, fs_1.readFileSync)(
|
|
8
|
+
const data = (0, fs_1.readFileSync)(constants_1.LOGS_PATH, { encoding: 'utf8' });
|
|
11
9
|
const lines = data.split('\n');
|
|
12
10
|
const tenantLogLines = lines
|
|
13
11
|
.filter(line => line.includes(`[tenantId: ${tenantId}`) || !line.includes('[tenantId: '))
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.signatureValidationInterceptor = void 0;
|
|
13
|
+
const signatureValidationService_1 = require("../services/signatureValidationService");
|
|
14
|
+
const utils_1 = require("../utils");
|
|
15
|
+
const signatureValidationInterceptor = (baseUrl) => {
|
|
16
|
+
return (req, res, next) => __awaiter(void 0, void 0, void 0, function* () {
|
|
17
|
+
if (baseUrl == null) {
|
|
18
|
+
res.status(500).send({ message: 'Internal server error - Missing base API URL' });
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const signedToken = req.header('X-Signed-Token');
|
|
22
|
+
if (signedToken) {
|
|
23
|
+
try {
|
|
24
|
+
const decodedBytes = Buffer.from(signedToken, 'base64');
|
|
25
|
+
const decodedToken = decodedBytes.toString();
|
|
26
|
+
const signatureData = JSON.parse(decodedToken);
|
|
27
|
+
(0, utils_1.logDebug)(`Decoded X-Signed-Token: ${decodedToken}`);
|
|
28
|
+
if (!isTokenValid(decodedToken, signatureData.signaturePayload.exp)) {
|
|
29
|
+
(0, utils_1.logError)('Expired Signature');
|
|
30
|
+
res.status(401).send({ message: 'Unauthorized: Expired signature' });
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const tenantId = req.header('Tenantid');
|
|
34
|
+
const isValidSignature = yield (0, signatureValidationService_1.validateSignature)(signatureData.signaturePayload.kid, signatureData.signature, JSON.stringify(signatureData.signaturePayload), tenantId, baseUrl);
|
|
35
|
+
if (!isValidSignature) {
|
|
36
|
+
(0, utils_1.logError)('Invalid Signature');
|
|
37
|
+
res.status(401).send({ message: 'Unauthorized: Invalid signature' });
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
next();
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
(0, utils_1.logError)(`Error processing the signed token ${error.message}`);
|
|
44
|
+
res.status(500).send({ message: `Internal server error - ${error.message}` });
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
(0, utils_1.logError)('Request intercepted: X-Signed-Token header is missing or empty');
|
|
50
|
+
res.status(403).send({ message: 'Forbidden: Missing or invalid X-Signed-Token' });
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
exports.signatureValidationInterceptor = signatureValidationInterceptor;
|
|
56
|
+
const isTokenValid = (token, exp) => {
|
|
57
|
+
return token !== '' && Date.now() < exp * 1000;
|
|
58
|
+
};
|
package/lib/server.d.ts
CHANGED
|
@@ -11,5 +11,7 @@ export type ServerInit = {
|
|
|
11
11
|
serverPort?: number;
|
|
12
12
|
additionalEndpoints?: (app: Express) => void;
|
|
13
13
|
expressBodyLimit?: string;
|
|
14
|
+
signatureValidationEnabled?: boolean;
|
|
15
|
+
baseUrl?: string;
|
|
14
16
|
};
|
|
15
|
-
export declare const deployServer: ({ manifestController, iconsController, executionController, serverPort, configureController, additionalEndpoints, expressBodyLimit, }: ServerInit) => void;
|
|
17
|
+
export declare const deployServer: ({ manifestController, iconsController, executionController, serverPort, configureController, additionalEndpoints, expressBodyLimit, signatureValidationEnabled, baseUrl, }: ServerInit) => void;
|
package/lib/server.js
CHANGED
|
@@ -43,8 +43,9 @@ const executionProvider_1 = require("./abstractProviders/executionProvider");
|
|
|
43
43
|
const logsProvider_1 = require("./abstractProviders/logsProvider");
|
|
44
44
|
const http_errors_1 = __importDefault(require("http-errors"));
|
|
45
45
|
const configureProvider_1 = require("./abstractProviders/configureProvider");
|
|
46
|
+
const signatureValidationInterceptor_1 = require("./interceptor/signatureValidationInterceptor");
|
|
46
47
|
const app = (0, express_1.default)();
|
|
47
|
-
const deployServer = ({ manifestController, iconsController, executionController, serverPort, configureController, additionalEndpoints, expressBodyLimit, }) => {
|
|
48
|
+
const deployServer = ({ manifestController, iconsController, executionController, serverPort, configureController, additionalEndpoints, expressBodyLimit, signatureValidationEnabled = false, baseUrl, }) => {
|
|
48
49
|
app.use(express_1.default.urlencoded({ extended: false }));
|
|
49
50
|
app.use(bodyParser.json({ limit: expressBodyLimit || '5mb' }));
|
|
50
51
|
app.get('/assets/icon', (req, res) => res.sendFile(iconsController.getIconPath()));
|
|
@@ -52,7 +53,7 @@ const deployServer = ({ manifestController, iconsController, executionController
|
|
|
52
53
|
app.get('/manifest', manifestController.getManifest);
|
|
53
54
|
app.get('/logs', (req, res) => __awaiter(void 0, void 0, void 0, function* () { return yield (0, logsProvider_1.fetchLogs)(req, res); }));
|
|
54
55
|
executionController &&
|
|
55
|
-
app.post('/execute', (req, res) => __awaiter(void 0, void 0, void 0, function* () { return yield (0, executionProvider_1.handleExecution)(req, res, executionController); }));
|
|
56
|
+
app.post('/execute', signatureValidationEnabled ? (0, signatureValidationInterceptor_1.signatureValidationInterceptor)(baseUrl) : (req, res, next) => next(), (req, res) => __awaiter(void 0, void 0, void 0, function* () { return yield (0, executionProvider_1.handleExecution)(req, res, executionController); }));
|
|
56
57
|
configureController &&
|
|
57
58
|
app.post('/configure', (req, res) => __awaiter(void 0, void 0, void 0, function* () { return yield (0, configureProvider_1.handleTenantConfigure)(req, res, configureController); }));
|
|
58
59
|
additionalEndpoints && additionalEndpoints(app);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const getPublicKey: (baseUrl: string, publicKeyId: string, tenantId: string) => Promise<string | undefined>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.getPublicKey = void 0;
|
|
16
|
+
const axios_1 = __importDefault(require("axios"));
|
|
17
|
+
const utils_1 = require("../utils");
|
|
18
|
+
const PUBLIC_KEY_PATH = '/tpa/public-key';
|
|
19
|
+
const EXPIRY_DURATION_MILLIS = 15 * 60 * 1000;
|
|
20
|
+
let publicKeyList = {};
|
|
21
|
+
let lastFetchTime = 0;
|
|
22
|
+
const getCommonHeaders = () => {
|
|
23
|
+
return {};
|
|
24
|
+
};
|
|
25
|
+
const isExpired = () => {
|
|
26
|
+
return Date.now() - lastFetchTime > EXPIRY_DURATION_MILLIS;
|
|
27
|
+
};
|
|
28
|
+
const fetchPublicKeys = (baseUrl, tenantId) => __awaiter(void 0, void 0, void 0, function* () {
|
|
29
|
+
(0, utils_1.logInfo)('Fetching new public keys');
|
|
30
|
+
try {
|
|
31
|
+
lastFetchTime = Date.now();
|
|
32
|
+
const headers = getCommonHeaders();
|
|
33
|
+
if (tenantId) {
|
|
34
|
+
headers['Tenantid'] = tenantId;
|
|
35
|
+
}
|
|
36
|
+
(0, utils_1.logDebug)(`${baseUrl} - ${JSON.stringify(headers)}`);
|
|
37
|
+
const response = yield axios_1.default.get(baseUrl + PUBLIC_KEY_PATH, { headers });
|
|
38
|
+
if (response.data && response.data.publicKeys) {
|
|
39
|
+
publicKeyList = response.data.publicKeys.reduce((acc, publicKeyEntry) => {
|
|
40
|
+
acc[publicKeyEntry.keyId] = publicKeyEntry.publicKey;
|
|
41
|
+
return acc;
|
|
42
|
+
}, {});
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
throw new Error('No public keys found');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
(0, utils_1.logError)(`Error while fetching/processing public keys: ${error}`);
|
|
50
|
+
publicKeyList = {};
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
const getPublicKey = (baseUrl, publicKeyId, tenantId) => __awaiter(void 0, void 0, void 0, function* () {
|
|
54
|
+
if (!publicKeyList[publicKeyId] || Object.keys(publicKeyList).length === 0 || isExpired()) {
|
|
55
|
+
yield fetchPublicKeys(baseUrl, tenantId);
|
|
56
|
+
}
|
|
57
|
+
return publicKeyList[publicKeyId];
|
|
58
|
+
});
|
|
59
|
+
exports.getPublicKey = getPublicKey;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const validateSignature: (keyId: string, signature: string, payload: string, tenantId: string, baseUrl: string) => Promise<boolean>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.validateSignature = void 0;
|
|
13
|
+
const crypto_1 = require("crypto");
|
|
14
|
+
const publicKeyService_1 = require("./publicKeyService");
|
|
15
|
+
const utils_1 = require("../utils");
|
|
16
|
+
const validateSignature = (keyId, signature, payload, tenantId, baseUrl) => __awaiter(void 0, void 0, void 0, function* () {
|
|
17
|
+
(0, utils_1.logDebug)('Validating Signature');
|
|
18
|
+
try {
|
|
19
|
+
const publicKeyStr = yield (0, publicKeyService_1.getPublicKey)(baseUrl, keyId, tenantId);
|
|
20
|
+
if (!publicKeyStr) {
|
|
21
|
+
throw new Error('Public key not found for key ID: ' + keyId);
|
|
22
|
+
}
|
|
23
|
+
const publicKey = (0, crypto_1.createPublicKey)(publicKeyStr);
|
|
24
|
+
return (0, crypto_1.verify)('RSA-SHA256', Buffer.from(payload), publicKey, Buffer.from(signature, 'base64'));
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
(0, utils_1.logError)(error.message);
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
exports.validateSignature = validateSignature;
|
package/lib/utils/appLogger.js
CHANGED
|
@@ -4,7 +4,6 @@ exports.logDebug = exports.logWarn = exports.logError = exports.logInfo = void 0
|
|
|
4
4
|
const log4js_1 = require("log4js");
|
|
5
5
|
const constants_1 = require("./constants");
|
|
6
6
|
const USER_LOG_BACKUPS = parseInt(process.env.LOG_BACKUPS + "") || 3;
|
|
7
|
-
const READONLY_MODE_ENABLED = process.env.READONLY_MODE_ENABLED === "true";
|
|
8
7
|
const MAX_BACKUPS = 10;
|
|
9
8
|
(0, log4js_1.configure)({
|
|
10
9
|
appenders: {
|
|
@@ -12,7 +11,7 @@ const MAX_BACKUPS = 10;
|
|
|
12
11
|
dateFile: {
|
|
13
12
|
type: 'dateFile',
|
|
14
13
|
layout: { type: 'basic' },
|
|
15
|
-
filename:
|
|
14
|
+
filename: constants_1.LOGS_PATH,
|
|
16
15
|
compress: true,
|
|
17
16
|
numBackups: USER_LOG_BACKUPS > MAX_BACKUPS ? MAX_BACKUPS : USER_LOG_BACKUPS,
|
|
18
17
|
keepFileExt: true,
|
package/lib/utils/constants.d.ts
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
export declare const LOGS_PATH
|
|
2
|
-
export declare const TMP_LOGS_PATH = "/tmp/log/app.log";
|
|
1
|
+
export declare const LOGS_PATH: string;
|
package/lib/utils/constants.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
exports.LOGS_PATH = 'log/app.log';
|
|
5
|
-
exports.TMP_LOGS_PATH = '/tmp/log/app.log';
|
|
3
|
+
exports.LOGS_PATH = void 0;
|
|
4
|
+
exports.LOGS_PATH = !!process.env.READONLY_WRITING_PATH ? process.env.READONLY_WRITING_PATH + '/log/app.log' : 'log/app.log';
|
package/package.json
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
import { readFileSync } from 'fs';
|
|
2
2
|
import { Request, Response } from 'express';
|
|
3
|
-
import {LOGS_PATH
|
|
4
|
-
|
|
5
|
-
const READONLY_MODE_ENABLED = process.env.READONLY_MODE_ENABLED === "true";
|
|
6
|
-
|
|
7
|
-
const logFolder = READONLY_MODE_ENABLED ? TMP_LOGS_PATH : LOGS_PATH
|
|
3
|
+
import {LOGS_PATH} from '../utils/constants';
|
|
8
4
|
|
|
9
5
|
export const fetchLogs = (req: Request, res: Response) => {
|
|
10
6
|
const tenantId = req.headers.tenantid;
|
|
11
|
-
const data = readFileSync(
|
|
7
|
+
const data = readFileSync(LOGS_PATH, { encoding: 'utf8' });
|
|
12
8
|
const lines = data.split('\n');
|
|
13
9
|
const tenantLogLines = lines
|
|
14
10
|
.filter(line => line.includes(`[tenantId: ${tenantId}`) || !line.includes('[tenantId: '))
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from 'express';
|
|
2
|
+
import { SignatureData } from '../dto/signatureData';
|
|
3
|
+
import { validateSignature } from '../services/signatureValidationService';
|
|
4
|
+
import { logDebug, logError } from '../utils';
|
|
5
|
+
|
|
6
|
+
export const signatureValidationInterceptor = (baseUrl: string | undefined) => {
|
|
7
|
+
return async (req: Request, res: Response, next: NextFunction): Promise<void> => {
|
|
8
|
+
if (baseUrl == null) {
|
|
9
|
+
res.status(500).send({ message: 'Internal server error - Missing base API URL' });
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const signedToken = req.header('X-Signed-Token');
|
|
14
|
+
|
|
15
|
+
if (signedToken) {
|
|
16
|
+
try {
|
|
17
|
+
const decodedBytes = Buffer.from(signedToken, 'base64');
|
|
18
|
+
const decodedToken = decodedBytes.toString();
|
|
19
|
+
const signatureData: SignatureData = JSON.parse(decodedToken);
|
|
20
|
+
|
|
21
|
+
logDebug(`Decoded X-Signed-Token: ${decodedToken}`);
|
|
22
|
+
|
|
23
|
+
if (!isTokenValid(decodedToken, signatureData.signaturePayload.exp)) {
|
|
24
|
+
logError('Expired Signature');
|
|
25
|
+
res.status(401).send({ message: 'Unauthorized: Expired signature' });
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const tenantId = req.header('Tenantid');
|
|
30
|
+
const isValidSignature = await validateSignature(
|
|
31
|
+
signatureData.signaturePayload.kid,
|
|
32
|
+
signatureData.signature,
|
|
33
|
+
JSON.stringify(signatureData.signaturePayload),
|
|
34
|
+
<string>tenantId,
|
|
35
|
+
<string>baseUrl,
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
if (!isValidSignature) {
|
|
39
|
+
logError('Invalid Signature');
|
|
40
|
+
res.status(401).send({ message: 'Unauthorized: Invalid signature' });
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
next();
|
|
45
|
+
} catch (error: any) {
|
|
46
|
+
logError(`Error processing the signed token ${error.message}`);
|
|
47
|
+
res.status(500).send({ message: `Internal server error - ${error.message}` });
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
} else {
|
|
51
|
+
logError('Request intercepted: X-Signed-Token header is missing or empty');
|
|
52
|
+
res.status(403).send({ message: 'Forbidden: Missing or invalid X-Signed-Token' });
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const isTokenValid = (token: string, exp: number): boolean => {
|
|
59
|
+
return token !== '' && Date.now() < exp * 1000;
|
|
60
|
+
};
|
package/src/server.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { ExecutionProvider, handleExecution } from './abstractProviders/executio
|
|
|
7
7
|
import { fetchLogs } from './abstractProviders/logsProvider';
|
|
8
8
|
import createError from 'http-errors';
|
|
9
9
|
import { ConfigureProvider, handleTenantConfigure } from './abstractProviders/configureProvider';
|
|
10
|
+
import { signatureValidationInterceptor } from './interceptor/signatureValidationInterceptor';
|
|
10
11
|
|
|
11
12
|
export type ServerInit = {
|
|
12
13
|
manifestController: ManifestProvider;
|
|
@@ -16,6 +17,8 @@ export type ServerInit = {
|
|
|
16
17
|
serverPort?: number;
|
|
17
18
|
additionalEndpoints?: (app: Express) => void;
|
|
18
19
|
expressBodyLimit?: string;
|
|
20
|
+
signatureValidationEnabled?: boolean;
|
|
21
|
+
baseUrl?: string;
|
|
19
22
|
};
|
|
20
23
|
|
|
21
24
|
const app = express();
|
|
@@ -28,6 +31,8 @@ export const deployServer = ({
|
|
|
28
31
|
configureController,
|
|
29
32
|
additionalEndpoints,
|
|
30
33
|
expressBodyLimit,
|
|
34
|
+
signatureValidationEnabled = false,
|
|
35
|
+
baseUrl,
|
|
31
36
|
}: ServerInit): void => {
|
|
32
37
|
app.use(express.urlencoded({ extended: false }));
|
|
33
38
|
app.use(bodyParser.json({ limit: expressBodyLimit || '5mb' }));
|
|
@@ -38,7 +43,11 @@ export const deployServer = ({
|
|
|
38
43
|
app.get('/logs', async (req: Request, res: Response) => await fetchLogs(req, res));
|
|
39
44
|
|
|
40
45
|
executionController &&
|
|
41
|
-
app.post(
|
|
46
|
+
app.post(
|
|
47
|
+
'/execute',
|
|
48
|
+
signatureValidationEnabled ? signatureValidationInterceptor(baseUrl) : (req, res, next) => next(),
|
|
49
|
+
async (req: Request, res: Response) => await handleExecution(req, res, executionController),
|
|
50
|
+
);
|
|
42
51
|
configureController &&
|
|
43
52
|
app.post(
|
|
44
53
|
'/configure',
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import { logDebug, logError, logInfo } from '../utils';
|
|
3
|
+
|
|
4
|
+
const PUBLIC_KEY_PATH = '/tpa/public-key';
|
|
5
|
+
const EXPIRY_DURATION_MILLIS = 15 * 60 * 1000;
|
|
6
|
+
let publicKeyList: { [keyId: string]: string } = {};
|
|
7
|
+
let lastFetchTime = 0;
|
|
8
|
+
|
|
9
|
+
const getCommonHeaders = (): { [key: string]: string } => {
|
|
10
|
+
return {};
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const isExpired = (): boolean => {
|
|
14
|
+
return Date.now() - lastFetchTime > EXPIRY_DURATION_MILLIS;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const fetchPublicKeys = async (baseUrl: string, tenantId: string): Promise<void> => {
|
|
18
|
+
logInfo('Fetching new public keys');
|
|
19
|
+
try {
|
|
20
|
+
lastFetchTime = Date.now();
|
|
21
|
+
const headers = getCommonHeaders();
|
|
22
|
+
if (tenantId) {
|
|
23
|
+
headers['Tenantid'] = tenantId;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
logDebug(`${baseUrl} - ${JSON.stringify(headers)}`);
|
|
27
|
+
|
|
28
|
+
const response = await axios.get(baseUrl + PUBLIC_KEY_PATH, { headers });
|
|
29
|
+
if (response.data && response.data.publicKeys) {
|
|
30
|
+
publicKeyList = response.data.publicKeys.reduce((acc: { [key: string]: string }, publicKeyEntry: any) => {
|
|
31
|
+
acc[publicKeyEntry.keyId] = publicKeyEntry.publicKey;
|
|
32
|
+
return acc;
|
|
33
|
+
}, {});
|
|
34
|
+
} else {
|
|
35
|
+
throw new Error('No public keys found');
|
|
36
|
+
}
|
|
37
|
+
} catch (error) {
|
|
38
|
+
logError(`Error while fetching/processing public keys: ${error}`);
|
|
39
|
+
publicKeyList = {};
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const getPublicKey = async (
|
|
44
|
+
baseUrl: string,
|
|
45
|
+
publicKeyId: string,
|
|
46
|
+
tenantId: string,
|
|
47
|
+
): Promise<string | undefined> => {
|
|
48
|
+
if (!publicKeyList[publicKeyId] || Object.keys(publicKeyList).length === 0 || isExpired()) {
|
|
49
|
+
await fetchPublicKeys(baseUrl, tenantId);
|
|
50
|
+
}
|
|
51
|
+
return publicKeyList[publicKeyId];
|
|
52
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { verify as verifySignature, createPublicKey } from 'crypto';
|
|
2
|
+
import { getPublicKey } from './publicKeyService';
|
|
3
|
+
import { logDebug, logError } from '../utils';
|
|
4
|
+
|
|
5
|
+
export const validateSignature = async (
|
|
6
|
+
keyId: string,
|
|
7
|
+
signature: string,
|
|
8
|
+
payload: string,
|
|
9
|
+
tenantId: string,
|
|
10
|
+
baseUrl: string,
|
|
11
|
+
): Promise<boolean> => {
|
|
12
|
+
logDebug('Validating Signature');
|
|
13
|
+
try {
|
|
14
|
+
const publicKeyStr = await getPublicKey(baseUrl, keyId, tenantId);
|
|
15
|
+
if (!publicKeyStr) {
|
|
16
|
+
throw new Error('Public key not found for key ID: ' + keyId);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const publicKey = createPublicKey(publicKeyStr);
|
|
20
|
+
|
|
21
|
+
return verifySignature('RSA-SHA256', Buffer.from(payload), publicKey, Buffer.from(signature, 'base64'));
|
|
22
|
+
} catch (error: any) {
|
|
23
|
+
logError(error.message);
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
};
|
package/src/utils/appLogger.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { configure, getLogger } from 'log4js';
|
|
2
|
-
import { LOGS_PATH
|
|
2
|
+
import { LOGS_PATH } from './constants';
|
|
3
3
|
|
|
4
4
|
type LogMetadata = {
|
|
5
5
|
tenantId: string;
|
|
@@ -8,7 +8,6 @@ type LogMetadata = {
|
|
|
8
8
|
};
|
|
9
9
|
const USER_LOG_BACKUPS = parseInt(process.env.LOG_BACKUPS + "") || 3;
|
|
10
10
|
|
|
11
|
-
const READONLY_MODE_ENABLED = process.env.READONLY_MODE_ENABLED === "true";
|
|
12
11
|
const MAX_BACKUPS = 10;
|
|
13
12
|
|
|
14
13
|
configure({
|
|
@@ -17,7 +16,7 @@ configure({
|
|
|
17
16
|
dateFile: {
|
|
18
17
|
type: 'dateFile',
|
|
19
18
|
layout: { type: 'basic' },
|
|
20
|
-
filename:
|
|
19
|
+
filename: LOGS_PATH,
|
|
21
20
|
compress: true,
|
|
22
21
|
numBackups: USER_LOG_BACKUPS > MAX_BACKUPS ? MAX_BACKUPS : USER_LOG_BACKUPS,
|
|
23
22
|
keepFileExt: true,
|
package/src/utils/constants.ts
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
export const LOGS_PATH = 'log/app.log';
|
|
2
|
-
export const TMP_LOGS_PATH = '/tmp/log/app.log';
|
|
1
|
+
export const LOGS_PATH = !!process.env.READONLY_WRITING_PATH ? process.env.READONLY_WRITING_PATH + '/log/app.log' : 'log/app.log';
|