@w3-commons/js-build-resources 0.0.1-security → 1.0.4
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.
Potentially problematic release.
This version of @w3-commons/js-build-resources might be problematic. Click here for more details.
- package/package.json +9 -3
- package/settings-service/.eslintrc.js +4 -0
- package/settings-service/.node-version +1 -0
- package/settings-service/.prettierrc +6 -0
- package/settings-service/.whitesource +15 -0
- package/settings-service/LICENSE +4 -0
- package/settings-service/README.md +50 -0
- package/settings-service/build.yml +56 -0
- package/settings-service/collectCodeCoverage.js +9 -0
- package/settings-service/db/cassandra/Dockerfile +3 -0
- package/settings-service/db/cassandra/createkeyspace.dev.cql +4 -0
- package/settings-service/db/cassandra/createkeyspace.dev.sh +3 -0
- package/settings-service/db/cassandra/schema_001.cql +15 -0
- package/settings-service/db/cassandra/schema_002.cql +8 -0
- package/settings-service/db/cassandra/schema_003.cql +10 -0
- package/settings-service/db/cassandra/schema_004.cql +1 -0
- package/settings-service/db/cassandra/schema_005.cql +39 -0
- package/settings-service/db/cassandra/schema_006.cql +255 -0
- package/settings-service/db/cassandra/schema_007.cql +40 -0
- package/settings-service/db/cassandra/schema_008.cql +2 -0
- package/settings-service/db/cassandra/schema_009.cql +143 -0
- package/settings-service/db/cassandra/schema_010.cql +143 -0
- package/settings-service/db/cassandra/schema_011.cql +2 -0
- package/settings-service/db/cassandra/schema_012.cql +8 -0
- package/settings-service/jest.config.fn.js +3 -0
- package/settings-service/jest.config.it.js +3 -0
- package/settings-service/jest.config.js +14 -0
- package/settings-service/jest.config.unit.js +19 -0
- package/settings-service/jest.setup.js +11 -0
- package/settings-service/package-lock.json +11772 -0
- package/settings-service/package.json +101 -0
- package/settings-service/scripts/run-fn-tests.sh +3 -0
- package/settings-service/sonar-project.properties +3 -0
- package/settings-service/src/__tests__/functional/controller/ApiKeyController.fn.ts +132 -0
- package/settings-service/src/__tests__/functional/middleware/AuthMiddlewareNextGenSSO.fn.ts +82 -0
- package/settings-service/src/__tests__/functional/repo/settingsRepo.fn.ts +302 -0
- package/settings-service/src/__tests__/functional/unified-profile/unified-profile.fn.ts +66 -0
- package/settings-service/src/__tests__/integration/repo/ApiKeyRepo.it.ts +43 -0
- package/settings-service/src/__tests__/integration/repo/settingsRepo.it.ts +142 -0
- package/settings-service/src/__tests__/integration/unified-profile/unified-profile.it.ts +31 -0
- package/settings-service/src/__tests__/unit/ErrResponse.ts +4 -0
- package/settings-service/src/__tests__/unit/JWTResponse.ts +18 -0
- package/settings-service/src/__tests__/unit/bluepagesResponse.ts +25 -0
- package/settings-service/src/__tests__/unit/controller/ApiKeyController.spec.ts +217 -0
- package/settings-service/src/__tests__/unit/controller/AppSettingsController.spec.ts +133 -0
- package/settings-service/src/__tests__/unit/controller/UserSettingsController.spec.ts +328 -0
- package/settings-service/src/__tests__/unit/controller/getAllSettings.spec.ts +83 -0
- package/settings-service/src/__tests__/unit/middleware/AuthMiddlewareNextGenSSO.spec.ts +282 -0
- package/settings-service/src/__tests__/unit/middleware/AuthenticationMiddleware.spec.ts +494 -0
- package/settings-service/src/__tests__/unit/repo/ApiKeyRepo.spec.ts +194 -0
- package/settings-service/src/__tests__/unit/repo/getAllSettings.spec.ts +100 -0
- package/settings-service/src/__tests__/unit/repo/getUserSettingsRepo.spec.ts +249 -0
- package/settings-service/src/__tests__/unit/repo/settingsRepo.spec.ts +614 -0
- package/settings-service/src/__tests__/unit/unified-profile/UnifiedProfileClient.spec.ts +31 -0
- package/settings-service/src/__tests__/unit/unified-profile/unifiedProfileUtils.spec.ts +36 -0
- package/settings-service/src/__tests__/utils/test-utils.ts +41 -0
- package/settings-service/src/config/config.ts +190 -0
- package/settings-service/src/controller/ApiKeyController.ts +114 -0
- package/settings-service/src/controller/AppSettingsController.ts +137 -0
- package/settings-service/src/controller/UserSettingsController.ts +202 -0
- package/settings-service/src/helpers/commons.ts +69 -0
- package/settings-service/src/logger/logger.ts +17 -0
- package/settings-service/src/middleware/AuthenticationMiddleware.ts +486 -0
- package/settings-service/src/middleware/AuthenticationMiddlewareFactory.ts +10 -0
- package/settings-service/src/repo/ApiKeyRepo.ts +135 -0
- package/settings-service/src/repo/ApiKeyRepoFactory.ts +10 -0
- package/settings-service/src/repo/CassandraClient.ts +33 -0
- package/settings-service/src/repo/CassandraClientFactory.ts +11 -0
- package/settings-service/src/repo/apiKeyQueries.ts +64 -0
- package/settings-service/src/repo/cassandraDBHelpers.ts +119 -0
- package/settings-service/src/repo/settingsRepo.ts +388 -0
- package/settings-service/src/repo/settingsRepoFactory.ts +10 -0
- package/settings-service/src/repo/settingsRepoQueries.ts +62 -0
- package/settings-service/src/routes/apiKeyRoutes.ts +27 -0
- package/settings-service/src/routes/appSettingsRoutes.ts +30 -0
- package/settings-service/src/routes/healthCheck.ts +10 -0
- package/settings-service/src/routes/swagger.ts +8 -0
- package/settings-service/src/routes/userSettingsRoutes.ts +30 -0
- package/settings-service/src/server.ts +77 -0
- package/settings-service/src/swagger.json +732 -0
- package/settings-service/src/types/ApiKey.ts +19 -0
- package/settings-service/src/types/IRequest.ts +9 -0
- package/settings-service/src/types/IRequestAuthorization.ts +5 -0
- package/settings-service/src/types/IRouteOptions.ts +5 -0
- package/settings-service/src/types/QueryResultsTypes.ts +6 -0
- package/settings-service/src/types/UserSettingsControllerTypes.ts +5 -0
- package/settings-service/src/types/W3IdUser.ts +36 -0
- package/settings-service/src/types/settingsRepoTypes.ts +61 -0
- package/settings-service/src/types/unifiedProfileTypes.ts +10 -0
- package/settings-service/src/unified-profile/UnifiedProfileClient.ts +29 -0
- package/settings-service/src/unified-profile/UnifiedProfileClientFactory.ts +10 -0
- package/settings-service/src/unified-profile/unifiedProfileUtils.ts +22 -0
- package/settings-service/src/util/downloadCassandra.ts +34 -0
- package/settings-service/src/util/isocodeMapper.ts +22 -0
- package/settings-service/src/util/languages.ts +1457 -0
- package/settings-service/test_resources/mockApiKeyDBResult.json +8 -0
- package/settings-service/tsconfig.json +40 -0
- package/README.md +0 -5
@@ -0,0 +1,69 @@
|
|
1
|
+
import { createCipheriv, createDecipheriv, randomBytes } from 'crypto';
|
2
|
+
import randomstring from 'randomstring';
|
3
|
+
import { logger } from '../logger/logger';
|
4
|
+
|
5
|
+
const IV_LENGTH = 16; /** For AES, this is always 16 */
|
6
|
+
const CLASS_NAME = 'commons';
|
7
|
+
|
8
|
+
export function createRandomToken(): string {
|
9
|
+
return randomstring.generate({
|
10
|
+
length: 48,
|
11
|
+
charset: 'alphanumeric',
|
12
|
+
});
|
13
|
+
}
|
14
|
+
|
15
|
+
export function encryptSync(text: string, secret: string): string {
|
16
|
+
if (secret.length !== 32) {
|
17
|
+
throw new Error('Invalid length of secret. Must be 256 bytes or 32 characters long');
|
18
|
+
}
|
19
|
+
const iv = randomBytes(IV_LENGTH);
|
20
|
+
const cipher = createCipheriv('aes-256-cbc', Buffer.from(secret), iv);
|
21
|
+
const cipherInitial = cipher.update(Buffer.from(text));
|
22
|
+
const encrypted = Buffer.concat([cipherInitial, cipher.final()]);
|
23
|
+
return `${iv.toString('hex')}:${encrypted.toString('hex')}`;
|
24
|
+
}
|
25
|
+
|
26
|
+
export function decryptSync(text: string, secret: string): string {
|
27
|
+
if (secret.length !== 32) {
|
28
|
+
throw new Error('Invalid length of secret. Must be 256 bytes or 32 characters long');
|
29
|
+
}
|
30
|
+
const [iv, encrypted]: string[] = text.split(':');
|
31
|
+
const decipher = createDecipheriv('aes-256-cbc', Buffer.from(secret), Buffer.from(iv, 'hex'));
|
32
|
+
const decipherInitial = decipher.update(Buffer.from(encrypted, 'hex'));
|
33
|
+
const decrypted = Buffer.concat([decipherInitial, decipher.final()]);
|
34
|
+
|
35
|
+
return decrypted.toString();
|
36
|
+
}
|
37
|
+
|
38
|
+
export async function encrypt(text: string, secret: string): Promise<string> {
|
39
|
+
const encryptedValue: string = encryptSync(text, secret);
|
40
|
+
logger.info(`${CLASS_NAME} | ${encrypt.name}() | Encrypted value: ${encryptedValue}`);
|
41
|
+
return encryptedValue;
|
42
|
+
}
|
43
|
+
|
44
|
+
export async function decrypt(text: string, secret: string): Promise<string> {
|
45
|
+
const decryptedValue: string = decryptSync(text, secret);
|
46
|
+
logger.debug(`${CLASS_NAME} | ${decrypt.name}() | Decrypted value: ${decryptedValue}`);
|
47
|
+
return decryptedValue;
|
48
|
+
}
|
49
|
+
|
50
|
+
/**
|
51
|
+
* This utility method returns a string array with parsed values of appId & token
|
52
|
+
* from apiKey
|
53
|
+
*
|
54
|
+
* @param apiKey
|
55
|
+
*/
|
56
|
+
export function parseAppIdAndToken(apiKey: string): string[] {
|
57
|
+
const parsedValues: string[] = [];
|
58
|
+
if (apiKey) {
|
59
|
+
const index: number = apiKey.lastIndexOf(':');
|
60
|
+
if (index > -1) {
|
61
|
+
const appId: string = apiKey.substring(0, index);
|
62
|
+
const token: string = apiKey.split(':').pop() || '';
|
63
|
+
parsedValues[0] = appId;
|
64
|
+
parsedValues[1] = token;
|
65
|
+
}
|
66
|
+
}
|
67
|
+
logger.debug(`${CLASS_NAME} | ${parseAppIdAndToken.name}() | Parsed appid & token: ${parsedValues}`);
|
68
|
+
return parsedValues;
|
69
|
+
}
|
@@ -0,0 +1,17 @@
|
|
1
|
+
import appRootPath from 'app-root-path';
|
2
|
+
import * as winston from 'winston';
|
3
|
+
import { settings } from '../config/config';
|
4
|
+
|
5
|
+
export const logger: winston.Logger = winston.createLogger({
|
6
|
+
level: settings.logLevel,
|
7
|
+
exitOnError: false,
|
8
|
+
transports: [
|
9
|
+
new winston.transports.Console({
|
10
|
+
format: winston.format.json(),
|
11
|
+
silent: process.argv.indexOf('--silent') >= 0,
|
12
|
+
}),
|
13
|
+
new winston.transports.File({
|
14
|
+
filename: `${appRootPath}/settings-service.log`,
|
15
|
+
}),
|
16
|
+
],
|
17
|
+
});
|
@@ -0,0 +1,486 @@
|
|
1
|
+
import _ from 'lodash';
|
2
|
+
import requestPromise from 'request-promise';
|
3
|
+
import { Next, Response, Route } from 'restify';
|
4
|
+
import { NotFoundError } from 'restify-errors';
|
5
|
+
import { ApiKeyRepoFactory } from '../repo/ApiKeyRepoFactory';
|
6
|
+
import W3IdUser from '../types/W3IdUser';
|
7
|
+
import { settings } from '../config/config';
|
8
|
+
import { logger } from '../logger/logger';
|
9
|
+
import { ApiKey } from '../types/ApiKey';
|
10
|
+
import { ApiKeyRepo } from '../repo/ApiKeyRepo';
|
11
|
+
import { parseAppIdAndToken } from '../helpers/commons';
|
12
|
+
import { IRequest } from '../types/IRequest';
|
13
|
+
import { IRouteOptions } from '../types/IRouteOptions';
|
14
|
+
|
15
|
+
interface UserDetails {
|
16
|
+
email: string;
|
17
|
+
id: string;
|
18
|
+
}
|
19
|
+
|
20
|
+
enum OperationType {
|
21
|
+
QUERY = 'QUERY',
|
22
|
+
MUTATION = 'MUTATION',
|
23
|
+
REST = 'REST',
|
24
|
+
}
|
25
|
+
|
26
|
+
interface OperationDetails {
|
27
|
+
operationName: string;
|
28
|
+
operationType: OperationType;
|
29
|
+
}
|
30
|
+
|
31
|
+
/**
|
32
|
+
* This is a middleware Class which contains methods to perform authentication header validation
|
33
|
+
* and read authentication plugin response.
|
34
|
+
*/
|
35
|
+
export class AuthenticationMiddleware {
|
36
|
+
public static healthCheckPath: IRouteOptions = {
|
37
|
+
path: '/health/ping',
|
38
|
+
authRequired: false,
|
39
|
+
version: settings.apiVersion,
|
40
|
+
};
|
41
|
+
|
42
|
+
public static settingsPath: IRouteOptions = {
|
43
|
+
path: `/v1/settings/users/:userID/apps/:appID`,
|
44
|
+
authRequired: true,
|
45
|
+
version: settings.apiVersion,
|
46
|
+
};
|
47
|
+
|
48
|
+
public static allSettingsPath: IRouteOptions = {
|
49
|
+
path: `/v1/settings/users/all`,
|
50
|
+
authRequired: true,
|
51
|
+
version: settings.apiVersion,
|
52
|
+
};
|
53
|
+
|
54
|
+
public static allAppSettingsPath: IRouteOptions = {
|
55
|
+
path: `/v1/settings/app/:appID`,
|
56
|
+
authRequired: true,
|
57
|
+
version: settings.apiVersion,
|
58
|
+
};
|
59
|
+
|
60
|
+
public static singleAppSettingPath: IRouteOptions = {
|
61
|
+
path: `/v1/settings/app/:appID/setting/:settingName`,
|
62
|
+
authRequired: true,
|
63
|
+
version: settings.apiVersion,
|
64
|
+
};
|
65
|
+
|
66
|
+
public static apiKeyPath: IRouteOptions = {
|
67
|
+
path: `/v1/settings/apiKey/app/:appID`,
|
68
|
+
authRequired: true,
|
69
|
+
version: settings.apiVersion,
|
70
|
+
};
|
71
|
+
|
72
|
+
public static deleteSettingsPath: IRouteOptions = {
|
73
|
+
path: `/v1/settings/users/:userId`,
|
74
|
+
authRequired: true,
|
75
|
+
version: settings.apiVersion,
|
76
|
+
};
|
77
|
+
|
78
|
+
public static AUTH_POLICY_BEARER = 'bearer';
|
79
|
+
|
80
|
+
public static AUTH_POLICY_API_KEY = 'apiKey';
|
81
|
+
|
82
|
+
static HEADER_AUTHORIZATION = 'headers.authorization';
|
83
|
+
/**
|
84
|
+
* This method checks for the required authentication headers.
|
85
|
+
* @param req
|
86
|
+
* @param res
|
87
|
+
* @param next
|
88
|
+
*/
|
89
|
+
|
90
|
+
public static checkAuthentication(req: IRequest, res: Response, next: Next): void {
|
91
|
+
const w3idAuthPluginRequest: IRequest = req;
|
92
|
+
const route: IRouteOptions = (w3idAuthPluginRequest.getRoute() as Route).spec;
|
93
|
+
|
94
|
+
if (!route.authRequired) {
|
95
|
+
return next();
|
96
|
+
}
|
97
|
+
if (process.env.APP_ENVIRONMENT === 'production') {
|
98
|
+
AuthenticationMiddleware.ensureAuthenticated(req, res, next);
|
99
|
+
return undefined;
|
100
|
+
}
|
101
|
+
try {
|
102
|
+
const authorization = _.get(req, AuthenticationMiddleware.HEADER_AUTHORIZATION);
|
103
|
+
|
104
|
+
logger.debug(
|
105
|
+
`${AuthenticationMiddleware.name} | ${AuthenticationMiddleware.checkAuthentication.name}() | Authorization: ${authorization}`,
|
106
|
+
);
|
107
|
+
if (!_.isEmpty(authorization) && authorization.includes('id') && authorization.includes('email')) {
|
108
|
+
const spaceIndex: number = _.indexOf(authorization, ' ');
|
109
|
+
const token = authorization.substring(spaceIndex + 1);
|
110
|
+
const details: UserDetails = JSON.parse(token);
|
111
|
+
req.username = details.id;
|
112
|
+
req.isAuthenticated = true;
|
113
|
+
req.auth_policy = AuthenticationMiddleware.AUTH_POLICY_BEARER;
|
114
|
+
return next();
|
115
|
+
}
|
116
|
+
AuthenticationMiddleware.ensureAuthenticated(req, res, next);
|
117
|
+
return undefined;
|
118
|
+
} catch (err) {
|
119
|
+
logger.error(
|
120
|
+
`${AuthenticationMiddleware.name} | ${AuthenticationMiddleware.checkAuthentication.name}() | Invalid token format: ${err}`,
|
121
|
+
);
|
122
|
+
res.send(400, { code: 400, message: `Bad Request token format: ${err}` });
|
123
|
+
return next(false);
|
124
|
+
}
|
125
|
+
}
|
126
|
+
|
127
|
+
/**
|
128
|
+
* This method checks rules based on authentication headers before authentication.
|
129
|
+
* It can respond with 401 code.
|
130
|
+
* Example: API key based authentication strategy must be used for deleteCascade Mutation.
|
131
|
+
* @param req
|
132
|
+
* @param res
|
133
|
+
* @param next
|
134
|
+
*/
|
135
|
+
public static checkPreAuthentication(req: IRequest, res: Response, next: Next): void {
|
136
|
+
const operationDetails: OperationDetails | undefined = AuthenticationMiddleware.getOperationDetails(req);
|
137
|
+
if (operationDetails === undefined) {
|
138
|
+
return next();
|
139
|
+
}
|
140
|
+
const errMessage: string | undefined = AuthenticationMiddleware.getPreAuthenticationErrorMessage(
|
141
|
+
req,
|
142
|
+
operationDetails,
|
143
|
+
);
|
144
|
+
if (errMessage === undefined) {
|
145
|
+
return next();
|
146
|
+
}
|
147
|
+
res.send(401, { code: 401, message: errMessage });
|
148
|
+
return next(false);
|
149
|
+
}
|
150
|
+
|
151
|
+
/**
|
152
|
+
* This method validates user's authentication token using the IBM Security Verify approach
|
153
|
+
*
|
154
|
+
* @param req
|
155
|
+
* @param res
|
156
|
+
* @param next
|
157
|
+
* @returns whether to forward to next layer or not
|
158
|
+
*/
|
159
|
+
public static async ensureAuthenticated(req: IRequest, res: Response, next: Next): Promise<void> {
|
160
|
+
try {
|
161
|
+
if (!req.headers.authorization) {
|
162
|
+
logger.info(
|
163
|
+
`${AuthenticationMiddleware.name} | ${AuthenticationMiddleware.ensureAuthenticated.name}() | Missing required authentication header in the request.`,
|
164
|
+
);
|
165
|
+
res.send(401, { code: 401, message: 'Missing required authentication header' });
|
166
|
+
return next(false);
|
167
|
+
}
|
168
|
+
const authorization: string = _.get(req, AuthenticationMiddleware.HEADER_AUTHORIZATION);
|
169
|
+
const bearerToken: string[] = _.split(authorization, ' ');
|
170
|
+
if (bearerToken.length < 2) {
|
171
|
+
logger.error(
|
172
|
+
`${AuthenticationMiddleware.name} | ${AuthenticationMiddleware.ensureAuthenticated.name}() | Missing required authentication token`,
|
173
|
+
);
|
174
|
+
res.send(401, { code: 401, message: 'Missing required authentication token' });
|
175
|
+
return next(false);
|
176
|
+
}
|
177
|
+
|
178
|
+
// Check if request to be authenticated by API key
|
179
|
+
if (bearerToken[0] === AuthenticationMiddleware.AUTH_POLICY_API_KEY) {
|
180
|
+
try {
|
181
|
+
const appId = req.params.appID;
|
182
|
+
const isValidApiKey: boolean = await AuthenticationMiddleware.verifyApiKeyWithAppId(
|
183
|
+
res,
|
184
|
+
bearerToken[1],
|
185
|
+
appId,
|
186
|
+
);
|
187
|
+
if (isValidApiKey) {
|
188
|
+
req.isAuthenticated = true;
|
189
|
+
req.auth_policy = AuthenticationMiddleware.AUTH_POLICY_API_KEY;
|
190
|
+
|
191
|
+
logger.info(
|
192
|
+
`${AuthenticationMiddleware.name} | ${AuthenticationMiddleware.ensureAuthenticated.name}() | ** Authenticated successfully by api key **`,
|
193
|
+
);
|
194
|
+
return next();
|
195
|
+
}
|
196
|
+
res.send(401, { code: 401, message: 'Api key is not valid. Please verify and try again' });
|
197
|
+
|
198
|
+
logger.warn(
|
199
|
+
`${AuthenticationMiddleware.name} | ${AuthenticationMiddleware.ensureAuthenticated.name}() | Unable to authenticate with the provided api key`,
|
200
|
+
);
|
201
|
+
return next(false);
|
202
|
+
} catch (err) {
|
203
|
+
logger.error(
|
204
|
+
`${AuthenticationMiddleware.name} | ${AuthenticationMiddleware.ensureAuthenticated.name}() | Error while validating api token: ${err}`,
|
205
|
+
);
|
206
|
+
return next(false);
|
207
|
+
}
|
208
|
+
}
|
209
|
+
|
210
|
+
try {
|
211
|
+
const authUserInfo = await AuthenticationMiddleware.introspectISV(res, bearerToken[1]);
|
212
|
+
let parsedAuthUserInfo: W3IdUser = {};
|
213
|
+
try {
|
214
|
+
parsedAuthUserInfo = JSON.parse(authUserInfo);
|
215
|
+
} catch (err) {
|
216
|
+
logger.error(
|
217
|
+
`${AuthenticationMiddleware.name} | ${
|
218
|
+
AuthenticationMiddleware.ensureAuthenticated.name
|
219
|
+
}() | JSON parse error: ${JSON.stringify(authUserInfo)}`,
|
220
|
+
);
|
221
|
+
}
|
222
|
+
logger.debug(
|
223
|
+
`${AuthenticationMiddleware.name} | ${
|
224
|
+
AuthenticationMiddleware.ensureAuthenticated.name
|
225
|
+
}() | Response from ISV : ${JSON.stringify(authUserInfo)}`,
|
226
|
+
);
|
227
|
+
const userId = _.get(parsedAuthUserInfo, 'uid');
|
228
|
+
if (!_.isEmpty(userId)) {
|
229
|
+
req.username = userId;
|
230
|
+
req.isAuthenticated = true;
|
231
|
+
req.auth_policy = AuthenticationMiddleware.AUTH_POLICY_BEARER;
|
232
|
+
return next();
|
233
|
+
}
|
234
|
+
res.send(401, { code: 401, message: 'User token is expired or is invalid' });
|
235
|
+
logger.error(
|
236
|
+
`${AuthenticationMiddleware.name} | ${AuthenticationMiddleware.ensureAuthenticated.name}() | Invalid token. Unable to retrieve userId from JWT response.`,
|
237
|
+
);
|
238
|
+
return next(false);
|
239
|
+
} catch (err) {
|
240
|
+
logger.error(
|
241
|
+
`${AuthenticationMiddleware.name} | ${
|
242
|
+
AuthenticationMiddleware.ensureAuthenticated.name
|
243
|
+
}() | Error while validating oidc token: ${JSON.stringify(err)}`,
|
244
|
+
);
|
245
|
+
return next(false);
|
246
|
+
}
|
247
|
+
} catch (err) {
|
248
|
+
logger.error(
|
249
|
+
`${AuthenticationMiddleware.name} | ${AuthenticationMiddleware.ensureAuthenticated.name}() | Error in ensureAuthenticatedv1: ${err}`,
|
250
|
+
);
|
251
|
+
return next(false);
|
252
|
+
}
|
253
|
+
}
|
254
|
+
|
255
|
+
/**
|
256
|
+
* This method validates the JWT based token
|
257
|
+
*
|
258
|
+
* @param res
|
259
|
+
* @param token
|
260
|
+
* @returns userdetails
|
261
|
+
*/
|
262
|
+
public static async introspectISV(res: Response, bearerToken: string): Promise<string> {
|
263
|
+
try {
|
264
|
+
const authRes = await requestPromise.post({
|
265
|
+
method: 'POST',
|
266
|
+
uri: settings.isv_introspectionUrl,
|
267
|
+
form: {
|
268
|
+
token: bearerToken,
|
269
|
+
client_id: settings.isv_clientId,
|
270
|
+
client_secret: settings.isv_clientSecret,
|
271
|
+
},
|
272
|
+
});
|
273
|
+
return authRes;
|
274
|
+
} catch (err) {
|
275
|
+
logger.error(
|
276
|
+
`${AuthenticationMiddleware.name} | ${AuthenticationMiddleware.introspectISV.name}() | introspectJWT error: ${err}`,
|
277
|
+
);
|
278
|
+
res.send(401, { code: 401, message: 'Unable to authenticate the user' });
|
279
|
+
throw new Error('Unable to authenticate the user');
|
280
|
+
}
|
281
|
+
}
|
282
|
+
|
283
|
+
/**
|
284
|
+
* This method validates the apikey of the incoming request
|
285
|
+
*
|
286
|
+
* @param res
|
287
|
+
* @param appId
|
288
|
+
* @param apiKey
|
289
|
+
*/
|
290
|
+
public static async verifyApiKeyWithAppId(
|
291
|
+
res: Response,
|
292
|
+
apiKey: string,
|
293
|
+
appIdFromParams: string,
|
294
|
+
): Promise<boolean> {
|
295
|
+
let flag = false;
|
296
|
+
try {
|
297
|
+
const decodedBase64Key = Buffer.from(apiKey, 'base64').toString('utf-8');
|
298
|
+
const [appIdFromToken, apiToken]: string[] = parseAppIdAndToken(decodedBase64Key);
|
299
|
+
if (appIdFromToken !== appIdFromParams && !settings.restrictedAppIdWhitelist.includes(appIdFromToken)) {
|
300
|
+
logger.error(
|
301
|
+
`${AuthenticationMiddleware.name} | ${this.verifyApiKeyWithAppId.name}() | appId from query params does not match appId from bearer token`,
|
302
|
+
);
|
303
|
+
res.send(400, {
|
304
|
+
code: 400,
|
305
|
+
message: `Unable to verify API key: appId you are trying to access does not match bearer token`,
|
306
|
+
});
|
307
|
+
throw new Error('appId you are trying to access does not match bearer token');
|
308
|
+
}
|
309
|
+
|
310
|
+
logger.debug(
|
311
|
+
`${AuthenticationMiddleware.name} | ${this.verifyApiKeyWithAppId.name}() | app id: ${appIdFromToken}`,
|
312
|
+
);
|
313
|
+
logger.debug(
|
314
|
+
`${AuthenticationMiddleware.name} | ${this.verifyApiKeyWithAppId.name}() | app token: ${apiToken}`,
|
315
|
+
);
|
316
|
+
|
317
|
+
if (appIdFromToken && apiToken) {
|
318
|
+
const apiKeyRepo: ApiKeyRepo = ApiKeyRepoFactory.accessOrCreateSingleton();
|
319
|
+
try {
|
320
|
+
if (settings.restrictedAppIdWhitelist.includes(appIdFromToken)) {
|
321
|
+
const masterPersistedKey: ApiKey = await apiKeyRepo.getApiKey(appIdFromToken);
|
322
|
+
if (masterPersistedKey.key === apiToken) {
|
323
|
+
flag = true;
|
324
|
+
}
|
325
|
+
}
|
326
|
+
// Fetch api key from DB and compare with incoming token
|
327
|
+
const persistedKey: ApiKey = await apiKeyRepo.getApiKey(appIdFromToken);
|
328
|
+
|
329
|
+
if (persistedKey.key === apiToken) {
|
330
|
+
flag = true;
|
331
|
+
}
|
332
|
+
} catch (err) {
|
333
|
+
if (err instanceof NotFoundError) {
|
334
|
+
return flag;
|
335
|
+
}
|
336
|
+
throw err;
|
337
|
+
}
|
338
|
+
}
|
339
|
+
return flag;
|
340
|
+
} catch (err) {
|
341
|
+
logger.error(
|
342
|
+
`${AuthenticationMiddleware.name} | ${AuthenticationMiddleware.verifyApiKeyWithAppId.name}() | Api key verification error: ${err}`,
|
343
|
+
);
|
344
|
+
res.send(500, { code: 500, message: `Unable to verify API key: ${err.message}` });
|
345
|
+
throw new Error(`Unable to verify API key: ${err.message}`);
|
346
|
+
}
|
347
|
+
}
|
348
|
+
|
349
|
+
/**
|
350
|
+
* This method checks rules based on authentication headers after authentication.
|
351
|
+
* It can respond with 403 code.
|
352
|
+
* Example: Authorization for operations related to API keys.
|
353
|
+
* @param req
|
354
|
+
* @param res
|
355
|
+
* @param next
|
356
|
+
*/
|
357
|
+
public static checkPostAuthentication(req: IRequest, res: Response, next: Next): void {
|
358
|
+
const operationDetails: OperationDetails | undefined = AuthenticationMiddleware.getOperationDetails(req);
|
359
|
+
if (operationDetails === undefined) {
|
360
|
+
return next();
|
361
|
+
}
|
362
|
+
if (operationDetails.operationName === 'apiKey') {
|
363
|
+
logger.info(
|
364
|
+
`${AuthenticationMiddleware.name} | ${AuthenticationMiddleware.checkPostAuthentication.name}() | Checking for user authorization`,
|
365
|
+
);
|
366
|
+
if (!AuthenticationMiddleware.isAuthorizedToRequestApiKey(req)) {
|
367
|
+
res.send(403, {
|
368
|
+
code: 403,
|
369
|
+
message: `You are not authorized for ${operationDetails.operationName} operations.`,
|
370
|
+
});
|
371
|
+
return next(false);
|
372
|
+
}
|
373
|
+
}
|
374
|
+
return next();
|
375
|
+
}
|
376
|
+
|
377
|
+
/**
|
378
|
+
* This method verifies if the api key requestor is authorized or not.
|
379
|
+
*
|
380
|
+
* @param req
|
381
|
+
*/
|
382
|
+
public static isAuthorizedToRequestApiKey(req: IRequest): boolean {
|
383
|
+
const authenticatedUser: string = _.toUpper(req.username);
|
384
|
+
const apiKeyAuthorizedUserIds: string[] = _.split(settings.apiKeyAuthorizedUserIds, ',');
|
385
|
+
const flag: boolean = req.isAuthenticated && apiKeyAuthorizedUserIds.indexOf(authenticatedUser) !== -1;
|
386
|
+
return flag;
|
387
|
+
}
|
388
|
+
|
389
|
+
private static getPreAuthenticationErrorMessage(
|
390
|
+
req: IRequest,
|
391
|
+
operationDetails: OperationDetails,
|
392
|
+
): string | undefined {
|
393
|
+
switch (operationDetails.operationType) {
|
394
|
+
case OperationType.REST: {
|
395
|
+
switch (operationDetails.operationName) {
|
396
|
+
case 'settings': {
|
397
|
+
return !AuthenticationMiddleware.usesAuthPolicyBearer(req) &&
|
398
|
+
!AuthenticationMiddleware.usesAuthPolicyApiKey(req)
|
399
|
+
? `w3id/jwt token or API key based authentication strategy must be used for ${operationDetails.operationName} Query.`
|
400
|
+
: undefined;
|
401
|
+
}
|
402
|
+
case 'appSettings': {
|
403
|
+
return !AuthenticationMiddleware.usesAuthPolicyApiKey(req)
|
404
|
+
? `API key based authentication strategy must be used for ${operationDetails.operationName} Query.`
|
405
|
+
: undefined;
|
406
|
+
}
|
407
|
+
case 'settingsDelete': {
|
408
|
+
return !AuthenticationMiddleware.usesAuthPolicyApiKey(req)
|
409
|
+
? // tslint:disable-next-line:max-line-length
|
410
|
+
`API key based authentication strategy must be used for delete settings operation.`
|
411
|
+
: undefined;
|
412
|
+
}
|
413
|
+
case 'getAllUsers': {
|
414
|
+
return !AuthenticationMiddleware.usesAuthPolicyApiKey(req)
|
415
|
+
? // tslint:disable-next-line:max-line-length
|
416
|
+
`API key based authentication strategy must be used for get all user settings operation.`
|
417
|
+
: undefined;
|
418
|
+
}
|
419
|
+
default:
|
420
|
+
return undefined;
|
421
|
+
}
|
422
|
+
}
|
423
|
+
default:
|
424
|
+
return undefined;
|
425
|
+
}
|
426
|
+
}
|
427
|
+
|
428
|
+
private static getOperationDetails(req: IRequest): OperationDetails | undefined {
|
429
|
+
const pathValue: string = req.path();
|
430
|
+
if (pathValue && pathValue.startsWith('/v1/settings/users/all')) {
|
431
|
+
logger.debug(
|
432
|
+
`${AuthenticationMiddleware.name} | ${this.getOperationDetails.name}() | Path value: ${req.path()}`,
|
433
|
+
);
|
434
|
+
return { operationName: 'getAllUsers', operationType: OperationType.REST };
|
435
|
+
}
|
436
|
+
if (pathValue && pathValue.startsWith('/v1/settings/users') && req.method === 'DELETE') {
|
437
|
+
logger.debug(
|
438
|
+
`${AuthenticationMiddleware.name} | ${this.getOperationDetails.name}() | Path value: ${req.path()}`,
|
439
|
+
);
|
440
|
+
return { operationName: 'settingsDelete', operationType: OperationType.REST };
|
441
|
+
}
|
442
|
+
if (pathValue && pathValue.startsWith('/v1/settings/users')) {
|
443
|
+
logger.debug(
|
444
|
+
`${AuthenticationMiddleware.name} | ${this.getOperationDetails.name}() | Path value: ${req.path()}`,
|
445
|
+
);
|
446
|
+
return { operationName: 'settings', operationType: OperationType.REST };
|
447
|
+
}
|
448
|
+
if (pathValue && pathValue.startsWith('/v1/settings/app')) {
|
449
|
+
logger.debug(
|
450
|
+
`${AuthenticationMiddleware.name} | ${this.getOperationDetails.name}() | Path value: ${req.path()}`,
|
451
|
+
);
|
452
|
+
return { operationName: 'appSettings', operationType: OperationType.REST };
|
453
|
+
}
|
454
|
+
if (pathValue && pathValue.startsWith('/v1/settings/apiKey')) {
|
455
|
+
logger.debug(
|
456
|
+
`${AuthenticationMiddleware.name} | ${this.getOperationDetails.name}() | Path value: ${req.path()}`,
|
457
|
+
);
|
458
|
+
return { operationName: 'apiKey', operationType: OperationType.REST };
|
459
|
+
}
|
460
|
+
return undefined;
|
461
|
+
}
|
462
|
+
|
463
|
+
private static usesAuthPolicyBearer(req: IRequest): boolean {
|
464
|
+
if (!req.headers.authorization) {
|
465
|
+
return false;
|
466
|
+
}
|
467
|
+
const authorization: string = _.get(req, AuthenticationMiddleware.HEADER_AUTHORIZATION);
|
468
|
+
const bearerToken: string[] = _.split(authorization, ' ');
|
469
|
+
if (bearerToken.length < 2) {
|
470
|
+
return false;
|
471
|
+
}
|
472
|
+
return bearerToken[0].toLowerCase() === AuthenticationMiddleware.AUTH_POLICY_BEARER.toLowerCase();
|
473
|
+
}
|
474
|
+
|
475
|
+
private static usesAuthPolicyApiKey(req: IRequest): boolean {
|
476
|
+
if (!req.headers.authorization) {
|
477
|
+
return false;
|
478
|
+
}
|
479
|
+
const authorization: string = _.get(req, AuthenticationMiddleware.HEADER_AUTHORIZATION);
|
480
|
+
const bearerToken: string[] = _.split(authorization, ' ');
|
481
|
+
if (bearerToken.length < 2) {
|
482
|
+
return false;
|
483
|
+
}
|
484
|
+
return bearerToken[0] === AuthenticationMiddleware.AUTH_POLICY_API_KEY;
|
485
|
+
}
|
486
|
+
}
|
@@ -0,0 +1,10 @@
|
|
1
|
+
import { AuthenticationMiddleware } from './AuthenticationMiddleware';
|
2
|
+
|
3
|
+
export class AuthenticationMiddlewareFactory {
|
4
|
+
private static singleton: AuthenticationMiddleware;
|
5
|
+
|
6
|
+
static accessOrCreateSingleton(): AuthenticationMiddleware {
|
7
|
+
this.singleton = this.singleton || new AuthenticationMiddleware();
|
8
|
+
return this.singleton;
|
9
|
+
}
|
10
|
+
}
|