lambda-essentials-ts 4.1.4 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/README.md +48 -9
- package/lib/exceptions/clientException.js +1 -0
- package/lib/exceptions/exception.js +1 -1
- package/lib/httpClient/httpClient.js +36 -28
- package/lib/index.d.ts +5 -3
- package/lib/index.js +11 -5
- package/lib/logger/logger.js +9 -5
- package/lib/openApi/openApiWrapper.js +11 -5
- package/lib/tokenProvider/kmsTokenProvider.d.ts +28 -0
- package/lib/tokenProvider/kmsTokenProvider.js +26 -0
- package/lib/tokenProvider/secretsManagerTokenProvider.d.ts +29 -0
- package/lib/tokenProvider/secretsManagerTokenProvider.js +23 -0
- package/lib/tokenProvider/tokenProvider.d.ts +7 -16
- package/lib/tokenProvider/tokenProvider.js +9 -26
- package/lib/util.js +1 -1
- package/package.json +20 -20
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,30 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
|
6
6
|
|
|
7
|
+
## [5.1.0] - 2022-09-05
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
Dependencies aren't pinned to a fixed version to allow users of the library to independently upgrade minor (devDependencies) and patch (dependencies) versions. This will simplify fixing security alerts faster than in this library, for example by applying `npm audit fix`.
|
|
12
|
+
|
|
13
|
+
## [5.0.0] - 2022-02-22
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- **[Breaking change]** `TokenProvider` was replaced by more specific `KmsTokenProvider` class.
|
|
18
|
+
The functionality and interface remains the same, the imports need to be changed.
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- New `SecretsManagerTokenProvider` that relies on AWS Secrets Manager to retrieve client ID and client secret.
|
|
23
|
+
The advantage of using AWS Secrets Manager is that it can be supplied with a secret rotation function.
|
|
24
|
+
|
|
25
|
+
## [4.1.5] - 2022-02-10
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
`ClientException` now maps `HTTP 422` client responses to `HTTP 422` server responses (was `HTTP 503` before).
|
|
30
|
+
|
|
7
31
|
## [4.1.2] - 2021-12-02
|
|
8
32
|
|
|
9
33
|
### Changed
|
package/README.md
CHANGED
|
@@ -88,15 +88,55 @@ let axiosClient = axios.create({ timeout: 3000 });
|
|
|
88
88
|
new HttpClient({ client: axiosClient });
|
|
89
89
|
```
|
|
90
90
|
|
|
91
|
-
###
|
|
91
|
+
### SecretsManagerTokenProvider
|
|
92
|
+
|
|
93
|
+
It uses AWS Secrets Manager to retrieve the client ID and secret and then calls the specified token endpoint the retrieve JWT.
|
|
94
|
+
|
|
95
|
+
CloudFormation to create a secret. Also allow the lambda function to access the secret by attaching `AWSSecretsManagerGetSecretValuePolicy` IAM Policy.
|
|
96
|
+
|
|
97
|
+
```yaml
|
|
98
|
+
Auth0Secret:
|
|
99
|
+
Type: AWS::SecretsManager::Secret
|
|
100
|
+
Properties:
|
|
101
|
+
Description: 'Auth0 Client ID/Secret'
|
|
102
|
+
GenerateSecretString:
|
|
103
|
+
SecretStringTemplate: '{"Auth0ClientID": "client_id", "Auth0ClientSecret": "client_secret"}'
|
|
104
|
+
GenerateStringKey: 'Auth0ClientSecret'
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
import {
|
|
109
|
+
SecretsManagerTokenProvider,
|
|
110
|
+
SecretsManagerTokenConfiguration,
|
|
111
|
+
} from 'lambda-essentials-ts';
|
|
112
|
+
|
|
113
|
+
const configuration: SecretsManagerTokenConfiguration = {
|
|
114
|
+
clientSecretId: 'arn:aws:secretsmanager:eu-west-1:<aws_account_id>:secret:<secret_id>',
|
|
115
|
+
audience: 'https://example.com/',
|
|
116
|
+
tokenEndpoint: 'https://example.com/oauth/token',
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const secretsManagerClient = new aws.SecretsManager({ region: 'eu-west-1' });
|
|
120
|
+
const tokenProvider = new SecretsManagerTokenProvider({
|
|
121
|
+
secretsManagerClient: secretsManagerClient,
|
|
122
|
+
tokenConfiguration: configuration,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// recommended way to retrieve token (utilizes caching and takes care of token expiration)
|
|
126
|
+
const accessToken = await tokenProvider.getToken();
|
|
127
|
+
|
|
128
|
+
// or bypass caching and get a new fresh token
|
|
129
|
+
const freshAccessToken = await tokenProvider.getTokenWithoutCache();
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### KmsTokenProvider
|
|
92
133
|
|
|
93
134
|
It uses AWS KMS to decrypt the client secret and then calls the specified token endpoint the retrieve JWT.
|
|
94
135
|
|
|
95
136
|
```typescript
|
|
96
|
-
import
|
|
97
|
-
import { TokenProvider, TokenConfiguration } from 'lambda-essentials-ts';
|
|
137
|
+
import { KmsTokenProvider, KmsTokenConfiguration } from 'lambda-essentials-ts';
|
|
98
138
|
|
|
99
|
-
const configuration:
|
|
139
|
+
const configuration: KmsTokenConfiguration = {
|
|
100
140
|
clientId: 'CLIENT_ID',
|
|
101
141
|
encryptedClientSecret: 'BASE64_KMS_ENCRYPTED_CLIENT_SECRET',
|
|
102
142
|
audience: 'https://example.com/',
|
|
@@ -104,17 +144,16 @@ const configuration: TokenConfiguration = {
|
|
|
104
144
|
};
|
|
105
145
|
|
|
106
146
|
const kmsClient = new aws.KMS({ region: 'eu-west-1' });
|
|
107
|
-
const tokenProvider = new
|
|
108
|
-
httpClient: axios.create(),
|
|
147
|
+
const tokenProvider = new KmsTokenProvider({
|
|
109
148
|
kmsClient: kmsClient,
|
|
110
149
|
tokenConfiguration: configuration,
|
|
111
150
|
});
|
|
112
151
|
|
|
113
152
|
// recommended way to retrieve token (utilizes caching and takes care of token expiration)
|
|
114
|
-
|
|
153
|
+
const accessToken = await tokenProvider.getToken();
|
|
115
154
|
|
|
116
|
-
// or bypass caching and get new token
|
|
117
|
-
|
|
155
|
+
// or bypass caching and get a new fresh token
|
|
156
|
+
const freshAccessToken = await tokenProvider.getTokenWithoutCache();
|
|
118
157
|
```
|
|
119
158
|
|
|
120
159
|
### Exceptions
|
|
@@ -6,7 +6,7 @@ class Exception extends Error {
|
|
|
6
6
|
constructor(message, statusCode, details) {
|
|
7
7
|
super(message);
|
|
8
8
|
this.statusCode = statusCode;
|
|
9
|
-
this.details = details ? util_1.serializeObject(details) : details;
|
|
9
|
+
this.details = details ? (0, util_1.serializeObject)(details) : details;
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
exports.Exception = Exception;
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
3
|
if (k2 === undefined) k2 = k;
|
|
4
|
-
Object.
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
5
9
|
}) : (function(o, m, k, k2) {
|
|
6
10
|
if (k2 === undefined) k2 = k;
|
|
7
11
|
o[k2] = m[k];
|
|
@@ -14,7 +18,7 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
|
|
|
14
18
|
var __importStar = (this && this.__importStar) || function (mod) {
|
|
15
19
|
if (mod && mod.__esModule) return mod;
|
|
16
20
|
var result = {};
|
|
17
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
18
22
|
__setModuleDefault(result, mod);
|
|
19
23
|
return result;
|
|
20
24
|
};
|
|
@@ -47,6 +51,7 @@ class HttpClient {
|
|
|
47
51
|
/**
|
|
48
52
|
* Create a new Instance of the HttpClient
|
|
49
53
|
*/
|
|
54
|
+
// eslint-disable-next-line complexity
|
|
50
55
|
constructor(options) {
|
|
51
56
|
var _a, _b, _c, _d, _e;
|
|
52
57
|
// eslint-disable-next-line no-console
|
|
@@ -56,26 +61,27 @@ class HttpClient {
|
|
|
56
61
|
this.correlationIdResolverFunction = options === null || options === void 0 ? void 0 : options.correlationIdResolver;
|
|
57
62
|
this.enableCache = (_c = options === null || options === void 0 ? void 0 : options.enableCache) !== null && _c !== void 0 ? _c : false;
|
|
58
63
|
this.enableRetry = (_d = options === null || options === void 0 ? void 0 : options.enableRetry) !== null && _d !== void 0 ? _d : false;
|
|
59
|
-
this.client =
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
64
|
+
this.client =
|
|
65
|
+
(_e = options === null || options === void 0 ? void 0 : options.client) !== null && _e !== void 0 ? _e : axios_1.default.create({
|
|
66
|
+
adapter: (() => {
|
|
67
|
+
let adapters = axios_1.default.defaults.adapter;
|
|
68
|
+
if (this.enableCache) {
|
|
69
|
+
const cache = (0, axios_cache_adapter_1.setupCache)({
|
|
70
|
+
maxAge: 5 * 60 * 1000,
|
|
71
|
+
readHeaders: false,
|
|
72
|
+
readOnError: true,
|
|
73
|
+
exclude: {
|
|
74
|
+
query: false, // also cache requests with query parameters
|
|
75
|
+
},
|
|
76
|
+
...options === null || options === void 0 ? void 0 : options.cacheOptions,
|
|
77
|
+
key: (req) => HttpClient.generateCacheKey(req),
|
|
78
|
+
});
|
|
79
|
+
// debounce concurrent calls with the same cacheKey so that only one HTTP request is made
|
|
80
|
+
adapters = (0, deduplicateRequestAdapter_1.createDebounceRequestAdapter)(cache.adapter, HttpClient.generateCacheKey);
|
|
81
|
+
}
|
|
82
|
+
return adapters;
|
|
83
|
+
})(),
|
|
84
|
+
});
|
|
79
85
|
if (this.enableRetry) {
|
|
80
86
|
this.client.defaults.raxConfig = {
|
|
81
87
|
...options === null || options === void 0 ? void 0 : options.retryOptions,
|
|
@@ -88,7 +94,7 @@ class HttpClient {
|
|
|
88
94
|
level: 'INFO',
|
|
89
95
|
retryAttempt: (_b = (_a = err.config) === null || _a === void 0 ? void 0 : _a.raxConfig) === null || _b === void 0 ? void 0 : _b.currentRetryAttempt,
|
|
90
96
|
...HttpClient.extractRequestLogData(err.config),
|
|
91
|
-
error: util_1.serializeAxiosError(err),
|
|
97
|
+
error: (0, util_1.serializeAxiosError)(err),
|
|
92
98
|
});
|
|
93
99
|
},
|
|
94
100
|
};
|
|
@@ -109,7 +115,7 @@ class HttpClient {
|
|
|
109
115
|
return config;
|
|
110
116
|
}, (error) => {
|
|
111
117
|
var _a;
|
|
112
|
-
const serializedAxiosError = util_1.serializeAxiosError(error);
|
|
118
|
+
const serializedAxiosError = (0, util_1.serializeAxiosError)(error);
|
|
113
119
|
this.logFunction({
|
|
114
120
|
title: 'HTTP Request Error',
|
|
115
121
|
level: 'WARN',
|
|
@@ -136,7 +142,7 @@ class HttpClient {
|
|
|
136
142
|
if (error instanceof clientException_1.ClientException) {
|
|
137
143
|
throw error;
|
|
138
144
|
}
|
|
139
|
-
const serializedAxiosError = util_1.serializeAxiosError(error);
|
|
145
|
+
const serializedAxiosError = (0, util_1.serializeAxiosError)(error);
|
|
140
146
|
if (error.message === invalidToken) {
|
|
141
147
|
this.logFunction({
|
|
142
148
|
title: 'HTTP call skipped due to a token error',
|
|
@@ -166,18 +172,20 @@ class HttpClient {
|
|
|
166
172
|
method: requestConfig.method,
|
|
167
173
|
url: requestConfig.url,
|
|
168
174
|
query: requestConfig.params,
|
|
169
|
-
request: util_1.safeJsonParse(requestConfig.data, requestConfig.data),
|
|
175
|
+
request: (0, util_1.safeJsonParse)(requestConfig.data, requestConfig.data),
|
|
170
176
|
correlationId: (_a = requestConfig.headers) === null || _a === void 0 ? void 0 : _a[shared_1.orionCorrelationIdRoot],
|
|
171
177
|
};
|
|
172
178
|
}
|
|
173
179
|
// implemented based on https://github.com/RasCarlito/axios-cache-adapter/blob/master/src/cache.js#L77
|
|
174
180
|
static generateCacheKey(req) {
|
|
175
181
|
var _a, _b;
|
|
176
|
-
const prefix = ((_a = req.headers) === null || _a === void 0 ? void 0 : _a.Authorization)
|
|
182
|
+
const prefix = ((_a = req.headers) === null || _a === void 0 ? void 0 : _a.Authorization)
|
|
183
|
+
? (_b = (0, util_1.safeJwtCanonicalIdParse)(req.headers.Authorization.replace('Bearer ', ''))) !== null && _b !== void 0 ? _b : uuid.v4()
|
|
184
|
+
: 'shared';
|
|
177
185
|
const url = `${req.baseURL ? req.baseURL : ''}${req.url}`;
|
|
178
186
|
const query = req.params ? JSON.stringify(req.params) : ''; // possible improvement: optimize cache-hit ratio by sorting the query params
|
|
179
187
|
const key = `${prefix}/${url}${query}`;
|
|
180
|
-
return `${key}${req.data ? md5_1.default(req.data) : ''}`;
|
|
188
|
+
return `${key}${req.data ? (0, md5_1.default)(req.data) : ''}`;
|
|
181
189
|
}
|
|
182
190
|
/**
|
|
183
191
|
* Resolves the token with the token provider and adds it to the headers
|
package/lib/index.d.ts
CHANGED
|
@@ -3,7 +3,9 @@ import Logger, { LoggerConfiguration, SuggestedLogObject } from './logger/logger
|
|
|
3
3
|
import OpenApiWrapper from './openApi/openApiWrapper';
|
|
4
4
|
import { ApiResponse } from './openApi/apiResponseModel';
|
|
5
5
|
import { ApiRequest, GetRequest, PostRequest, PutRequest } from './openApi/apiRequestModel';
|
|
6
|
-
import
|
|
6
|
+
import { TokenProviderHttpClient } from './tokenProvider/tokenProvider';
|
|
7
|
+
import KmsTokenProvider, { KmsTokenProviderOptions, KmsTokenConfiguration } from './tokenProvider/kmsTokenProvider';
|
|
8
|
+
import SecretsManagerTokenProvider, { SecretsManagerTokenProviderOptions, SecretsManagerTokenConfiguration } from './tokenProvider/secretsManagerTokenProvider';
|
|
7
9
|
import { ClientException } from './exceptions/clientException';
|
|
8
10
|
import { ConflictException } from './exceptions/conflictException';
|
|
9
11
|
import { Exception } from './exceptions/exception';
|
|
@@ -13,5 +15,5 @@ import { InvalidDataException } from './exceptions/invalidDataException';
|
|
|
13
15
|
import { NotFoundException } from './exceptions/notFoundException';
|
|
14
16
|
import { ValidationException } from './exceptions/validationException';
|
|
15
17
|
import { serializeObject, serializeAxiosError } from './util';
|
|
16
|
-
export { Logger, OpenApiWrapper, ApiResponse, HttpClient, HttpLogType,
|
|
17
|
-
export type { LoggerConfiguration, SuggestedLogObject, HttpClientOptions, HttpLogOptions, ApiRequest, GetRequest, PostRequest, PutRequest,
|
|
18
|
+
export { Logger, OpenApiWrapper, ApiResponse, HttpClient, HttpLogType, KmsTokenProvider, SecretsManagerTokenProvider, ValidationException, NotFoundException, InvalidDataException, InternalException, ForbiddenException, Exception, ConflictException, ClientException, serializeObject, serializeAxiosError, };
|
|
19
|
+
export type { LoggerConfiguration, SuggestedLogObject, HttpClientOptions, HttpLogOptions, ApiRequest, GetRequest, PostRequest, PutRequest, TokenProviderHttpClient, KmsTokenProviderOptions, KmsTokenConfiguration, SecretsManagerTokenProviderOptions, SecretsManagerTokenConfiguration, };
|
package/lib/index.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
3
|
if (k2 === undefined) k2 = k;
|
|
4
|
-
Object.
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
5
9
|
}) : (function(o, m, k, k2) {
|
|
6
10
|
if (k2 === undefined) k2 = k;
|
|
7
11
|
o[k2] = m[k];
|
|
@@ -14,7 +18,7 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
|
|
|
14
18
|
var __importStar = (this && this.__importStar) || function (mod) {
|
|
15
19
|
if (mod && mod.__esModule) return mod;
|
|
16
20
|
var result = {};
|
|
17
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
18
22
|
__setModuleDefault(result, mod);
|
|
19
23
|
return result;
|
|
20
24
|
};
|
|
@@ -22,7 +26,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
22
26
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
23
27
|
};
|
|
24
28
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
-
exports.serializeAxiosError = exports.serializeObject = exports.ClientException = exports.ConflictException = exports.Exception = exports.ForbiddenException = exports.InternalException = exports.InvalidDataException = exports.NotFoundException = exports.ValidationException = exports.
|
|
29
|
+
exports.serializeAxiosError = exports.serializeObject = exports.ClientException = exports.ConflictException = exports.Exception = exports.ForbiddenException = exports.InternalException = exports.InvalidDataException = exports.NotFoundException = exports.ValidationException = exports.SecretsManagerTokenProvider = exports.KmsTokenProvider = exports.HttpLogType = exports.HttpClient = exports.ApiResponse = exports.OpenApiWrapper = exports.Logger = void 0;
|
|
26
30
|
const httpClient_1 = __importStar(require("./httpClient/httpClient"));
|
|
27
31
|
exports.HttpClient = httpClient_1.default;
|
|
28
32
|
Object.defineProperty(exports, "HttpLogType", { enumerable: true, get: function () { return httpClient_1.HttpLogType; } });
|
|
@@ -32,8 +36,10 @@ const openApiWrapper_1 = __importDefault(require("./openApi/openApiWrapper"));
|
|
|
32
36
|
exports.OpenApiWrapper = openApiWrapper_1.default;
|
|
33
37
|
const apiResponseModel_1 = require("./openApi/apiResponseModel");
|
|
34
38
|
Object.defineProperty(exports, "ApiResponse", { enumerable: true, get: function () { return apiResponseModel_1.ApiResponse; } });
|
|
35
|
-
const
|
|
36
|
-
exports.
|
|
39
|
+
const kmsTokenProvider_1 = __importDefault(require("./tokenProvider/kmsTokenProvider"));
|
|
40
|
+
exports.KmsTokenProvider = kmsTokenProvider_1.default;
|
|
41
|
+
const secretsManagerTokenProvider_1 = __importDefault(require("./tokenProvider/secretsManagerTokenProvider"));
|
|
42
|
+
exports.SecretsManagerTokenProvider = secretsManagerTokenProvider_1.default;
|
|
37
43
|
const clientException_1 = require("./exceptions/clientException");
|
|
38
44
|
Object.defineProperty(exports, "ClientException", { enumerable: true, get: function () { return clientException_1.ClientException; } });
|
|
39
45
|
const conflictException_1 = require("./exceptions/conflictException");
|
package/lib/logger/logger.js
CHANGED
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
/* eslint-disable no-console */
|
|
3
3
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
4
|
if (k2 === undefined) k2 = k;
|
|
5
|
-
Object.
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
6
10
|
}) : (function(o, m, k, k2) {
|
|
7
11
|
if (k2 === undefined) k2 = k;
|
|
8
12
|
o[k2] = m[k];
|
|
@@ -15,7 +19,7 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
|
|
|
15
19
|
var __importStar = (this && this.__importStar) || function (mod) {
|
|
16
20
|
if (mod && mod.__esModule) return mod;
|
|
17
21
|
var result = {};
|
|
18
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
19
23
|
__setModuleDefault(result, mod);
|
|
20
24
|
return result;
|
|
21
25
|
};
|
|
@@ -70,8 +74,8 @@ class Logger {
|
|
|
70
74
|
const truncateToken = (innerPayload) => {
|
|
71
75
|
return innerPayload.replace(/(eyJ[a-zA-Z0-9_-]{5,}\.eyJ[a-zA-Z0-9_-]{5,})\.[a-zA-Z0-9_-]*/gi, (m, p1) => `${p1}.<sig>`);
|
|
72
76
|
};
|
|
73
|
-
const replacer = (key, value) => (is_error_1.default(value) ? Logger.errorToObject(value) : value);
|
|
74
|
-
let stringifiedPayload = truncateToken(fast_safe_stringify_1.default(payload, replacer, this.jsonSpace));
|
|
77
|
+
const replacer = (key, value) => ((0, is_error_1.default)(value) ? Logger.errorToObject(value) : value);
|
|
78
|
+
let stringifiedPayload = truncateToken((0, fast_safe_stringify_1.default)(payload, replacer, this.jsonSpace));
|
|
75
79
|
// https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/cloudwatch_limits_cwl.html 256KB => 32768 characters
|
|
76
80
|
if (stringifiedPayload.length >= 32768) {
|
|
77
81
|
const replacementPayload = {
|
|
@@ -82,7 +86,7 @@ class Logger {
|
|
|
82
86
|
truncatedPayload: stringifiedPayload.substring(0, 10000),
|
|
83
87
|
},
|
|
84
88
|
};
|
|
85
|
-
stringifiedPayload = fast_safe_stringify_1.default(replacementPayload, replacer, this.jsonSpace);
|
|
89
|
+
stringifiedPayload = (0, fast_safe_stringify_1.default)(replacementPayload, replacer, this.jsonSpace);
|
|
86
90
|
}
|
|
87
91
|
this.logFunction(stringifiedPayload);
|
|
88
92
|
}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
3
|
if (k2 === undefined) k2 = k;
|
|
4
|
-
Object.
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
5
9
|
}) : (function(o, m, k, k2) {
|
|
6
10
|
if (k2 === undefined) k2 = k;
|
|
7
11
|
o[k2] = m[k];
|
|
@@ -14,7 +18,7 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
|
|
|
14
18
|
var __importStar = (this && this.__importStar) || function (mod) {
|
|
15
19
|
if (mod && mod.__esModule) return mod;
|
|
16
20
|
var result = {};
|
|
17
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
18
22
|
__setModuleDefault(result, mod);
|
|
19
23
|
return result;
|
|
20
24
|
};
|
|
@@ -43,8 +47,10 @@ class OpenApiWrapper {
|
|
|
43
47
|
const correlationId = this.generateCorrelationId(request.headers);
|
|
44
48
|
requestLogger.startInvocation(null, correlationId);
|
|
45
49
|
// TODO: restrict the alternative way of resolving token and principal only for localhost
|
|
46
|
-
this.userToken =
|
|
47
|
-
|
|
50
|
+
this.userToken =
|
|
51
|
+
(_b = (_a = request.requestContext.authorizer) === null || _a === void 0 ? void 0 : _a.jwt) !== null && _b !== void 0 ? _b : (_c = request.headers.Authorization) === null || _c === void 0 ? void 0 : _c.split(' ')[1];
|
|
52
|
+
this.userPrincipal =
|
|
53
|
+
(_f = (_e = (_d = request.requestContext.authorizer) === null || _d === void 0 ? void 0 : _d.canonicalId) !== null && _e !== void 0 ? _e : (0, util_1.safeJwtCanonicalIdParse)(this.userToken)) !== null && _f !== void 0 ? _f : 'unknown';
|
|
48
54
|
this.requestId = request.requestContext.requestId;
|
|
49
55
|
requestLogger.log({
|
|
50
56
|
title: 'RequestLogger',
|
|
@@ -71,7 +77,7 @@ class OpenApiWrapper {
|
|
|
71
77
|
errorMiddleware: (request, error) => {
|
|
72
78
|
const { correlationId } = this;
|
|
73
79
|
this.clearContext();
|
|
74
|
-
const serializedError = util_1.serializeObject(error);
|
|
80
|
+
const serializedError = (0, util_1.serializeObject)(error);
|
|
75
81
|
if (error instanceof exception_1.Exception) {
|
|
76
82
|
if (error.statusCode === 500) {
|
|
77
83
|
requestLogger.log({ title: 'ErrorLogger', level: 'ERROR', ...serializedError });
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { KMS } from 'aws-sdk';
|
|
2
|
+
import TokenProvider, { Auth0Secret, TokenConfiguration, TokenProviderOptions } from './tokenProvider';
|
|
3
|
+
export default class KmsTokenProvider extends TokenProvider {
|
|
4
|
+
private kmsClient;
|
|
5
|
+
private kmsConfiguration;
|
|
6
|
+
constructor(options: KmsTokenProviderOptions);
|
|
7
|
+
getClientSecret(): Promise<Auth0Secret | undefined>;
|
|
8
|
+
}
|
|
9
|
+
export interface KmsTokenProviderOptions extends TokenProviderOptions {
|
|
10
|
+
/**
|
|
11
|
+
* AWS KMS Client
|
|
12
|
+
*/
|
|
13
|
+
kmsClient: KMS;
|
|
14
|
+
/**
|
|
15
|
+
* Configuration needed for the token
|
|
16
|
+
*/
|
|
17
|
+
tokenConfiguration: KmsTokenConfiguration;
|
|
18
|
+
}
|
|
19
|
+
export interface KmsTokenConfiguration extends TokenConfiguration {
|
|
20
|
+
/**
|
|
21
|
+
* Username or ClientId
|
|
22
|
+
*/
|
|
23
|
+
clientId: string;
|
|
24
|
+
/**
|
|
25
|
+
* KMS Encrypted client secret
|
|
26
|
+
*/
|
|
27
|
+
encryptedClientSecret: string;
|
|
28
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const tokenProvider_1 = __importDefault(require("./tokenProvider"));
|
|
7
|
+
class KmsTokenProvider extends tokenProvider_1.default {
|
|
8
|
+
constructor(options) {
|
|
9
|
+
super(options);
|
|
10
|
+
this.kmsClient = options.kmsClient;
|
|
11
|
+
this.kmsConfiguration = options.tokenConfiguration;
|
|
12
|
+
}
|
|
13
|
+
async getClientSecret() {
|
|
14
|
+
const secret = await this.kmsClient
|
|
15
|
+
.decrypt({
|
|
16
|
+
CiphertextBlob: Buffer.from(this.kmsConfiguration.encryptedClientSecret, 'base64'),
|
|
17
|
+
})
|
|
18
|
+
.promise()
|
|
19
|
+
.then((data) => { var _a; return (_a = data.Plaintext) === null || _a === void 0 ? void 0 : _a.toString(); });
|
|
20
|
+
if (!secret) {
|
|
21
|
+
throw new Error('Request error: failed to decrypt secret using KMS');
|
|
22
|
+
}
|
|
23
|
+
return { Auth0ClientSecret: secret, Auth0ClientID: this.kmsConfiguration.clientId };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
exports.default = KmsTokenProvider;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { SecretsManager } from 'aws-sdk';
|
|
2
|
+
import TokenProvider, { Auth0Secret, TokenConfiguration, TokenProviderOptions } from './tokenProvider';
|
|
3
|
+
export default class SecretsManagerTokenProvider extends TokenProvider {
|
|
4
|
+
private secretsManagerClient;
|
|
5
|
+
private secretsManagerConfiguration;
|
|
6
|
+
constructor(options: SecretsManagerTokenProviderOptions);
|
|
7
|
+
getClientSecret(): Promise<Auth0Secret | undefined>;
|
|
8
|
+
}
|
|
9
|
+
export interface SecretsManagerTokenProviderOptions extends TokenProviderOptions {
|
|
10
|
+
/**
|
|
11
|
+
* AWS Secrets Manager Client
|
|
12
|
+
*/
|
|
13
|
+
secretsManagerClient: SecretsManager;
|
|
14
|
+
/**
|
|
15
|
+
* Configuration needed for the token
|
|
16
|
+
*/
|
|
17
|
+
tokenConfiguration: SecretsManagerTokenConfiguration;
|
|
18
|
+
}
|
|
19
|
+
export interface SecretsManagerTokenConfiguration extends TokenConfiguration {
|
|
20
|
+
/**
|
|
21
|
+
* The ID of a secret stored in AWS Secrets Manager.
|
|
22
|
+
* The expected secret format is:
|
|
23
|
+
* {
|
|
24
|
+
* Auth0ClientID: "string",
|
|
25
|
+
* Auth0ClientSecret: "string"
|
|
26
|
+
* }
|
|
27
|
+
*/
|
|
28
|
+
clientSecretId: string;
|
|
29
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const tokenProvider_1 = __importDefault(require("./tokenProvider"));
|
|
7
|
+
class SecretsManagerTokenProvider extends tokenProvider_1.default {
|
|
8
|
+
constructor(options) {
|
|
9
|
+
super(options);
|
|
10
|
+
this.secretsManagerClient = options.secretsManagerClient;
|
|
11
|
+
this.secretsManagerConfiguration = options.tokenConfiguration;
|
|
12
|
+
}
|
|
13
|
+
async getClientSecret() {
|
|
14
|
+
const secret = await this.secretsManagerClient
|
|
15
|
+
.getSecretValue({ SecretId: this.secretsManagerConfiguration.clientSecretId })
|
|
16
|
+
.promise();
|
|
17
|
+
if (!(secret === null || secret === void 0 ? void 0 : secret.SecretString)) {
|
|
18
|
+
throw new Error('Request error: failed to retrieve secret from Secrets Manager');
|
|
19
|
+
}
|
|
20
|
+
return JSON.parse(secret.SecretString);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
exports.default = SecretsManagerTokenProvider;
|
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
import { KMS } from 'aws-sdk';
|
|
2
1
|
import { AxiosRequestConfig, AxiosResponse } from 'axios';
|
|
3
|
-
export default class TokenProvider {
|
|
2
|
+
export default abstract class TokenProvider {
|
|
4
3
|
private httpClient;
|
|
5
|
-
private kmsClient;
|
|
6
4
|
private configuration;
|
|
7
5
|
private currentTokenPromise?;
|
|
8
6
|
/**
|
|
9
7
|
* Create a new Instance of the TokenProvider
|
|
10
8
|
*/
|
|
11
|
-
constructor(options: TokenProviderOptions);
|
|
9
|
+
protected constructor(options: TokenProviderOptions);
|
|
12
10
|
/**
|
|
13
11
|
* Get access token. Subsequent calls are cached and the token is renewed only if it is expired.
|
|
14
12
|
*/
|
|
@@ -17,30 +15,23 @@ export default class TokenProvider {
|
|
|
17
15
|
* Get access token.
|
|
18
16
|
*/
|
|
19
17
|
getTokenWithoutCache(): Promise<string>;
|
|
18
|
+
abstract getClientSecret(): Promise<Auth0Secret | undefined>;
|
|
19
|
+
}
|
|
20
|
+
export interface Auth0Secret {
|
|
21
|
+
Auth0ClientID: string;
|
|
22
|
+
Auth0ClientSecret: string;
|
|
20
23
|
}
|
|
21
24
|
export interface TokenProviderOptions {
|
|
22
25
|
/**
|
|
23
26
|
* Either an Axios instance or an @atsquad/httpclient instance, by default it creates an axios instance
|
|
24
27
|
*/
|
|
25
28
|
httpClient?: TokenProviderHttpClient;
|
|
26
|
-
/**
|
|
27
|
-
* AWS KMS Client
|
|
28
|
-
*/
|
|
29
|
-
kmsClient: KMS;
|
|
30
29
|
/**
|
|
31
30
|
* Configuration needed for the token
|
|
32
31
|
*/
|
|
33
32
|
tokenConfiguration: TokenConfiguration;
|
|
34
33
|
}
|
|
35
34
|
export interface TokenConfiguration {
|
|
36
|
-
/**
|
|
37
|
-
* Username or ClientId
|
|
38
|
-
*/
|
|
39
|
-
clientId: string;
|
|
40
|
-
/**
|
|
41
|
-
* KMS Encrypted client secret
|
|
42
|
-
*/
|
|
43
|
-
encryptedClientSecret: string;
|
|
44
35
|
audience: string;
|
|
45
36
|
tokenEndpoint: string;
|
|
46
37
|
}
|
|
@@ -12,24 +12,7 @@ class TokenProvider {
|
|
|
12
12
|
constructor(options) {
|
|
13
13
|
var _a;
|
|
14
14
|
this.httpClient = (_a = options.httpClient) !== null && _a !== void 0 ? _a : axios_1.default.create();
|
|
15
|
-
|
|
16
|
-
throw new Error('KMS Client is missing');
|
|
17
|
-
}
|
|
18
|
-
this.kmsClient = options.kmsClient;
|
|
19
|
-
const configuration = options.tokenConfiguration;
|
|
20
|
-
if (!(configuration === null || configuration === void 0 ? void 0 : configuration.clientId)) {
|
|
21
|
-
throw new Error('Configuration error: missing required property "clientId"');
|
|
22
|
-
}
|
|
23
|
-
if (!(configuration === null || configuration === void 0 ? void 0 : configuration.encryptedClientSecret)) {
|
|
24
|
-
throw new Error('Configuration error: missing required property "encryptedClientSecret"');
|
|
25
|
-
}
|
|
26
|
-
if (!(configuration === null || configuration === void 0 ? void 0 : configuration.audience)) {
|
|
27
|
-
throw new Error('Configuration error: missing required property "audience"');
|
|
28
|
-
}
|
|
29
|
-
if (!(configuration === null || configuration === void 0 ? void 0 : configuration.tokenEndpoint)) {
|
|
30
|
-
throw new Error('Configuration error: missing required property "tokenEndpoint"');
|
|
31
|
-
}
|
|
32
|
-
this.configuration = configuration;
|
|
15
|
+
this.configuration = options.tokenConfiguration;
|
|
33
16
|
}
|
|
34
17
|
/**
|
|
35
18
|
* Get access token. Subsequent calls are cached and the token is renewed only if it is expired.
|
|
@@ -43,10 +26,10 @@ class TokenProvider {
|
|
|
43
26
|
try {
|
|
44
27
|
const jwtToken = await this.currentTokenPromise;
|
|
45
28
|
// lower the token expiry time by 10s so that the returned token will be not immediately expired
|
|
46
|
-
if (!jwtToken || ((_a = jsonwebtoken_1.default.decode(jwtToken)) === null || _a === void 0 ? void 0 : _a
|
|
29
|
+
if (!jwtToken || ((_a = jsonwebtoken_1.default.decode(jwtToken)) === null || _a === void 0 ? void 0 : _a.exp) < Date.now() / 1000 - 10) {
|
|
47
30
|
this.currentTokenPromise = this.getTokenWithoutCache();
|
|
48
31
|
}
|
|
49
|
-
return this.currentTokenPromise;
|
|
32
|
+
return await this.currentTokenPromise;
|
|
50
33
|
}
|
|
51
34
|
catch (error) {
|
|
52
35
|
this.currentTokenPromise = this.getTokenWithoutCache();
|
|
@@ -57,14 +40,14 @@ class TokenProvider {
|
|
|
57
40
|
* Get access token.
|
|
58
41
|
*/
|
|
59
42
|
async getTokenWithoutCache() {
|
|
60
|
-
const secret = await this.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
43
|
+
const secret = await this.getClientSecret();
|
|
44
|
+
if (!(secret === null || secret === void 0 ? void 0 : secret.Auth0ClientID) || !secret.Auth0ClientSecret) {
|
|
45
|
+
throw new Error('Request error: failed to retrieve Auth0 Client ID/Secret');
|
|
46
|
+
}
|
|
64
47
|
const headers = { 'Content-Type': 'application/json' };
|
|
65
48
|
const body = {
|
|
66
|
-
client_id:
|
|
67
|
-
client_secret: secret,
|
|
49
|
+
client_id: secret.Auth0ClientID,
|
|
50
|
+
client_secret: secret.Auth0ClientSecret,
|
|
68
51
|
audience: this.configuration.audience,
|
|
69
52
|
grant_type: 'client_credentials',
|
|
70
53
|
};
|
package/lib/util.js
CHANGED
|
@@ -43,7 +43,7 @@ function serializeAxiosError(error) {
|
|
|
43
43
|
const { status, data } = error.response;
|
|
44
44
|
return {
|
|
45
45
|
status: (_a = data.originalStatusCode) !== null && _a !== void 0 ? _a : status,
|
|
46
|
-
details: data.details ? data.details : data,
|
|
46
|
+
details: data.details ? data.details : data, // Prevent wrapping of Exception
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
49
|
exports.serializeAxiosError = serializeAxiosError;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lambda-essentials-ts",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.1.0",
|
|
4
4
|
"description": "A selection of the finest modules supporting authorization, API routing, error handling, logging and sending HTTP requests.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"private": false,
|
|
@@ -26,28 +26,28 @@
|
|
|
26
26
|
},
|
|
27
27
|
"homepage": "https://github.com/Cimpress-MCP/lambda-essentials-ts#readme",
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"aws-sdk": "2.
|
|
30
|
-
"axios": "0.21.
|
|
31
|
-
"axios-cache-adapter": "2.7.3",
|
|
32
|
-
"fast-safe-stringify": "2.0.7",
|
|
33
|
-
"is-error": "2.2.2",
|
|
34
|
-
"jsonwebtoken": "8.5.1",
|
|
35
|
-
"md5": "2.3.0",
|
|
29
|
+
"aws-sdk": "^2.1078.0",
|
|
30
|
+
"axios": "~0.21.3",
|
|
31
|
+
"axios-cache-adapter": "~2.7.3",
|
|
32
|
+
"fast-safe-stringify": "~2.0.7",
|
|
33
|
+
"is-error": "~2.2.2",
|
|
34
|
+
"jsonwebtoken": "~8.5.1",
|
|
35
|
+
"md5": "~2.3.0",
|
|
36
36
|
"openapi-factory": "5.2.17",
|
|
37
|
-
"retry-axios": "2.6.0",
|
|
38
|
-
"uuid": "8.3.2"
|
|
37
|
+
"retry-axios": "~2.6.0",
|
|
38
|
+
"uuid": "~8.3.2"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@types/jest": "26.0.20",
|
|
42
|
-
"eslint": "7.18.0",
|
|
43
|
-
"eslint-config-cimpress-atsquad": "2.1.2",
|
|
44
|
-
"husky": "4.2.5",
|
|
45
|
-
"jest": "26.2.2",
|
|
46
|
-
"lint-staged": "10.2.13",
|
|
47
|
-
"prettier": "2.
|
|
48
|
-
"ts-jest": "26.1.4",
|
|
49
|
-
"ts-node": "
|
|
50
|
-
"typescript": "
|
|
41
|
+
"@types/jest": "^26.0.20",
|
|
42
|
+
"eslint": "^7.18.0",
|
|
43
|
+
"eslint-config-cimpress-atsquad": "^2.1.2",
|
|
44
|
+
"husky": "^4.2.5",
|
|
45
|
+
"jest": "^26.2.2",
|
|
46
|
+
"lint-staged": "^10.2.13",
|
|
47
|
+
"prettier": "^2.7.1",
|
|
48
|
+
"ts-jest": "^26.1.4",
|
|
49
|
+
"ts-node": "^10.9.1",
|
|
50
|
+
"typescript": "^4.8.2"
|
|
51
51
|
},
|
|
52
52
|
"eslintConfig": {
|
|
53
53
|
"extends": "cimpress-atsquad"
|