oracledb 7.0.1 → 26.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -6
- package/build/Release/oracledb-26.0.0-darwin-arm64.node +0 -0
- package/build/Release/oracledb-26.0.0-darwin-arm64.node-buildinfo.txt +1 -0
- package/build/Release/oracledb-26.0.0-js-buildinfo.txt +1 -0
- package/build/Release/oracledb-26.0.0-linux-arm64.node +0 -0
- package/build/Release/oracledb-26.0.0-linux-arm64.node-buildinfo.txt +1 -0
- package/build/Release/oracledb-26.0.0-linux-x64.node +0 -0
- package/build/Release/oracledb-26.0.0-linux-x64.node-buildinfo.txt +1 -0
- package/build/Release/oracledb-26.0.0-win32-x64.node +0 -0
- package/build/Release/oracledb-26.0.0-win32-x64.node-buildinfo.txt +1 -0
- package/lib/connection.js +71 -8
- package/lib/constants.js +4 -0
- package/lib/endUserSecurityContext.js +20 -8
- package/lib/errors.js +70 -5
- package/lib/impl/connection.js +36 -0
- package/lib/impl/datahandlers/buffer.js +91 -18
- package/lib/impl/datahandlers/oson.js +3 -2
- package/lib/impl/resultset.js +32 -11
- package/lib/oracledb.js +34 -0
- package/lib/pool.js +6 -2
- package/lib/queryStream.js +13 -5
- package/lib/resultset.js +10 -3
- package/lib/securityContextProvider.js +206 -0
- package/lib/thin/aq.js +2 -3
- package/lib/thin/connection.js +381 -19
- package/lib/thin/dbObject.js +17 -8
- package/lib/thin/pool.js +3 -2
- package/lib/thin/protocol/capabilities.js +37 -4
- package/lib/thin/protocol/constants.js +49 -1
- package/lib/thin/protocol/messages/auth.js +17 -6
- package/lib/thin/protocol/messages/base.js +74 -16
- package/lib/thin/protocol/messages/dataType.js +46 -0
- package/lib/thin/protocol/messages/directPathLoadStream.js +3 -2
- package/lib/thin/protocol/messages/execute.js +10 -4
- package/lib/thin/protocol/messages/index.js +4 -0
- package/lib/thin/protocol/messages/notification.js +281 -0
- package/lib/thin/protocol/messages/subscribe.js +181 -0
- package/lib/thin/protocol/messages/withData.js +21 -7
- package/lib/thin/protocol/packet.js +2 -4
- package/lib/thin/protocol/protocol.js +7 -4
- package/lib/thin/resultSet.js +40 -20
- package/lib/thin/sqlnet/ezConnectResolver.js +7 -0
- package/lib/thin/sqlnet/navNodes.js +3 -3
- package/lib/thin/sqlnet/ntTcp.js +24 -8
- package/lib/thin/statement.js +6 -1
- package/lib/thin/statementCache.js +34 -6
- package/lib/thin/subscr.js +363 -0
- package/lib/transformer.js +60 -17
- package/lib/util.js +46 -8
- package/lib/version.js +2 -2
- package/package.json +1 -1
- package/plugins/configProviders/gcpstorage/index.js +121 -0
- package/plugins/configProviders/ociobject/index.js +1 -1
- package/plugins/token/cache.js +154 -0
- package/plugins/token/endUserSecurityProvider/index.js +377 -0
- package/plugins/token/extensionAzure/index.js +160 -28
- package/plugins/token/extensionOci/index.js +221 -16
- package/plugins/vectorsdk/OracleVecDB.js +346 -0
- package/plugins/vectorsdk/index.js +49 -0
- package/plugins/vectorsdk/utils.js +370 -0
- package/plugins/vectorsdk/vectorEmbedding.js +223 -0
- package/plugins/vectorsdk/vectorIndex.js +177 -0
- package/plugins/vectorsdk/vectorRecords.js +406 -0
- package/plugins/vectorsdk/vectorSchema.js +408 -0
- package/plugins/vectorsdk/vectorSearch.js +538 -0
- package/build/Release/oracledb-7.0.1-darwin-arm64.node +0 -0
- package/build/Release/oracledb-7.0.1-darwin-arm64.node-buildinfo.txt +0 -1
- package/build/Release/oracledb-7.0.1-js-buildinfo.txt +0 -1
- package/build/Release/oracledb-7.0.1-linux-arm64.node +0 -0
- package/build/Release/oracledb-7.0.1-linux-arm64.node-buildinfo.txt +0 -1
- package/build/Release/oracledb-7.0.1-linux-x64.node +0 -0
- package/build/Release/oracledb-7.0.1-linux-x64.node-buildinfo.txt +0 -1
- package/build/Release/oracledb-7.0.1-win32-x64.node +0 -0
- package/build/Release/oracledb-7.0.1-win32-x64.node-buildinfo.txt +0 -1
package/lib/transformer.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Copyright (c) 2023,
|
|
1
|
+
// Copyright (c) 2023, 2026, Oracle and/or its affiliates.
|
|
2
2
|
|
|
3
3
|
//-----------------------------------------------------------------------------
|
|
4
4
|
//
|
|
@@ -36,6 +36,12 @@ const util = require('util');
|
|
|
36
36
|
const types = require('./types.js');
|
|
37
37
|
const nodbUtil = require('./util.js');
|
|
38
38
|
|
|
39
|
+
const unsupportedJsonFieldNames = new Set([
|
|
40
|
+
'__proto__',
|
|
41
|
+
'constructor',
|
|
42
|
+
'prototype'
|
|
43
|
+
]);
|
|
44
|
+
|
|
39
45
|
//-----------------------------------------------------------------------------
|
|
40
46
|
// checkType()
|
|
41
47
|
//
|
|
@@ -78,12 +84,31 @@ function checkType(info, options) {
|
|
|
78
84
|
//-----------------------------------------------------------------------------
|
|
79
85
|
// transformJsonValue()
|
|
80
86
|
//
|
|
81
|
-
// Returns a normalized JSON value.
|
|
82
|
-
// Arrays are returned as a new array with transformed JSON values.
|
|
83
|
-
// returned as new objects with keys "fields" and "values", both of
|
|
84
|
-
// are arrays (with the value transformed to JSON values).
|
|
87
|
+
// Returns a normalized JSON value. Supported scalar values are returned
|
|
88
|
+
// unchanged. Arrays are returned as a new array with transformed JSON values.
|
|
89
|
+
// Objects are returned as new objects with keys "fields" and "values", both of
|
|
90
|
+
// which are arrays (with the value transformed to JSON values).
|
|
91
|
+
// Only enumerable own object properties are included.
|
|
92
|
+
// If rejectUnsupportedTypes is set, unsupported JSON values, circular
|
|
93
|
+
// references, and field names are rejected.
|
|
85
94
|
//-----------------------------------------------------------------------------
|
|
86
|
-
function transformJsonValue(value) {
|
|
95
|
+
function transformJsonValue(value, options = {}, activeValues) {
|
|
96
|
+
|
|
97
|
+
// bigInt cannot be represented in JSON and is always rejected
|
|
98
|
+
if (typeof value === 'bigint') {
|
|
99
|
+
errors.throwErr(errors.ERR_UNSUPPORTED_JSON_VALUE_TYPE, typeof value);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// reject unsupported values when requested
|
|
103
|
+
if (options.rejectUnsupportedTypes &&
|
|
104
|
+
(typeof value === 'symbol' || typeof value === 'function')) {
|
|
105
|
+
errors.throwErr(errors.ERR_UNSUPPORTED_JSON_VALUE_TYPE, typeof value);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (options.rejectUnsupportedTypes &&
|
|
109
|
+
typeof value === 'number' && !Number.isFinite(value)) {
|
|
110
|
+
errors.throwErr(errors.ERR_NON_FINITE_JSON_NUMBER);
|
|
111
|
+
}
|
|
87
112
|
|
|
88
113
|
// handle simple scalars
|
|
89
114
|
if (value === undefined || value === null ||
|
|
@@ -93,15 +118,6 @@ function transformJsonValue(value) {
|
|
|
93
118
|
value instanceof types.IntervalYM || value instanceof types.IntervalDS)
|
|
94
119
|
return value;
|
|
95
120
|
|
|
96
|
-
// arrays are transformed to a new array with processed values
|
|
97
|
-
if (Array.isArray(value)) {
|
|
98
|
-
const outValue = new Array(value.length);
|
|
99
|
-
for (let i = 0; i < value.length; i++) {
|
|
100
|
-
outValue[i] = transformJsonValue(value[i]);
|
|
101
|
-
}
|
|
102
|
-
return outValue;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
121
|
// database objects are treated as empty objects
|
|
106
122
|
if (value instanceof BaseDbObject)
|
|
107
123
|
return {fields: [], values: []};
|
|
@@ -112,14 +128,41 @@ function transformJsonValue(value) {
|
|
|
112
128
|
return value;
|
|
113
129
|
}
|
|
114
130
|
|
|
131
|
+
if (options.rejectUnsupportedTypes) {
|
|
132
|
+
if (!activeValues)
|
|
133
|
+
activeValues = new Set();
|
|
134
|
+
if (activeValues.has(value))
|
|
135
|
+
errors.throwErr(errors.ERR_INVALID_PARAMETER_VALUE, 1);
|
|
136
|
+
activeValues.add(value);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// arrays are transformed to a new array with processed values
|
|
140
|
+
if (Array.isArray(value)) {
|
|
141
|
+
const outValue = new Array(value.length);
|
|
142
|
+
for (let i = 0; i < value.length; i++) {
|
|
143
|
+
outValue[i] = transformJsonValue(value[i], options, activeValues);
|
|
144
|
+
}
|
|
145
|
+
if (activeValues)
|
|
146
|
+
activeValues.delete(value);
|
|
147
|
+
return outValue;
|
|
148
|
+
}
|
|
149
|
+
|
|
115
150
|
// all other objects are transformed to an object with two arrays (fields
|
|
116
151
|
// and values)
|
|
117
152
|
const outValue = {};
|
|
118
|
-
outValue.fields = Object.
|
|
153
|
+
outValue.fields = Object.keys(value);
|
|
119
154
|
outValue.values = new Array(outValue.fields.length);
|
|
120
155
|
for (let i = 0; i < outValue.fields.length; i++) {
|
|
121
|
-
|
|
156
|
+
if (options.rejectUnsupportedTypes &&
|
|
157
|
+
unsupportedJsonFieldNames.has(outValue.fields[i])) {
|
|
158
|
+
errors.throwErr(errors.ERR_UNSUPPORTED_JSON_FIELD_NAME,
|
|
159
|
+
outValue.fields[i]);
|
|
160
|
+
}
|
|
161
|
+
outValue.values[i] = transformJsonValue(value[outValue.fields[i]], options,
|
|
162
|
+
activeValues);
|
|
122
163
|
}
|
|
164
|
+
if (activeValues)
|
|
165
|
+
activeValues.delete(value);
|
|
123
166
|
return outValue;
|
|
124
167
|
|
|
125
168
|
}
|
package/lib/util.js
CHANGED
|
@@ -33,6 +33,7 @@ const util = require('util');
|
|
|
33
33
|
const types = require('./types.js');
|
|
34
34
|
const constants = require('./constants.js');
|
|
35
35
|
const traceHandler = require('./traceHandler.js');
|
|
36
|
+
const securityContextProvider = require('./securityContextProvider.js');
|
|
36
37
|
const crypto = require("crypto");
|
|
37
38
|
|
|
38
39
|
const pemHeaders = [
|
|
@@ -271,6 +272,8 @@ function wrapFn(func, serialize, preventConcurrentErrorCode) {
|
|
|
271
272
|
let connImpl;
|
|
272
273
|
const traceEnabled = traceHandler.isEnabled();
|
|
273
274
|
const traceContext = {fn: func, args: arguments};
|
|
275
|
+
const hook = securityContextProvider.getInvocationHook();
|
|
276
|
+
let hookState;
|
|
274
277
|
|
|
275
278
|
// if concurrent operations are to be prevented, check for that now
|
|
276
279
|
if (preventConcurrentErrorCode) {
|
|
@@ -295,12 +298,29 @@ function wrapFn(func, serialize, preventConcurrentErrorCode) {
|
|
|
295
298
|
if (traceEnabled) {
|
|
296
299
|
traceEnterFn.call(this, traceContext);
|
|
297
300
|
}
|
|
301
|
+
if (hook?.before) {
|
|
302
|
+
// more attributes like args, sql can be passed to
|
|
303
|
+
// provider later.
|
|
304
|
+
const invocation = {
|
|
305
|
+
target: this,
|
|
306
|
+
connection: connImpl
|
|
307
|
+
};
|
|
308
|
+
hookState = await hook.before(invocation);
|
|
309
|
+
}
|
|
298
310
|
result = await traceContext.fn.apply(this, arguments);
|
|
299
|
-
return result;
|
|
300
311
|
} catch (err) {
|
|
301
312
|
tErr = errors.transformErr(err, wrapper);
|
|
302
|
-
throw tErr;
|
|
303
313
|
} finally {
|
|
314
|
+
if (hook?.after && hookState) {
|
|
315
|
+
try {
|
|
316
|
+
await hook.after(hookState);
|
|
317
|
+
} catch (err) {
|
|
318
|
+
// Preserve the operation's error if both fail.
|
|
319
|
+
if (!tErr) {
|
|
320
|
+
tErr = errors.transformErr(err, wrapper);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
304
324
|
if (connImpl)
|
|
305
325
|
connImpl._releaseLock();
|
|
306
326
|
if (preventConcurrentErrorCode) {
|
|
@@ -311,6 +331,10 @@ function wrapFn(func, serialize, preventConcurrentErrorCode) {
|
|
|
311
331
|
}
|
|
312
332
|
}
|
|
313
333
|
|
|
334
|
+
if (tErr) {
|
|
335
|
+
throw tErr;
|
|
336
|
+
}
|
|
337
|
+
return result;
|
|
314
338
|
};
|
|
315
339
|
if (func.name) {
|
|
316
340
|
Object.defineProperty(wrapper, 'name', { value: func.name });
|
|
@@ -563,19 +587,33 @@ function isVectorValue(value) {
|
|
|
563
587
|
=== Uint8Array.prototype) || value instanceof types.SparseVector);
|
|
564
588
|
}
|
|
565
589
|
|
|
566
|
-
|
|
590
|
+
//---------------------------------------------------------------------------
|
|
567
591
|
// makeDate()
|
|
568
592
|
//
|
|
569
|
-
//
|
|
570
|
-
//
|
|
571
|
-
//
|
|
593
|
+
// Creates a Date from the supplied date/time components. When useLocal is
|
|
594
|
+
// true, the components are interpreted in the Node.js process local timezone.
|
|
595
|
+
// Otherwise, they are interpreted using the supplied UTC offset.
|
|
596
|
+
//---------------------------------------------------------------------------
|
|
572
597
|
function makeDate(useLocal, year, month, day, hour, minute,
|
|
573
598
|
second, fseconds, offset) {
|
|
574
599
|
if (useLocal) {
|
|
575
600
|
return new Date(year, month - 1, day, hour, minute, second, fseconds);
|
|
576
601
|
}
|
|
577
|
-
return new Date(
|
|
578
|
-
fseconds) - offset * 60000);
|
|
602
|
+
return new Date(makeUTCDate(year, month, day, hour, minute, second,
|
|
603
|
+
fseconds).getTime() - offset * 60000);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
//-----------------------------------------------------------------------------
|
|
607
|
+
// makeUTCDate()
|
|
608
|
+
//
|
|
609
|
+
// Creates a UTC date without Date.UTC()'s special handling of years 0 through
|
|
610
|
+
// 99, which is important when calculating historical timezone offsets.
|
|
611
|
+
// -----------------------------------------------------------------------------
|
|
612
|
+
function makeUTCDate(year, month, day, hour, minute, second, fseconds = 0) {
|
|
613
|
+
const date = new Date(0);
|
|
614
|
+
date.setUTCFullYear(year, month - 1, day);
|
|
615
|
+
date.setUTCHours(hour, minute, second, fseconds);
|
|
616
|
+
return date;
|
|
579
617
|
}
|
|
580
618
|
|
|
581
619
|
//---------------------------------------------------------------------------
|
package/lib/version.js
CHANGED
package/package.json
CHANGED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// Copyright (c) 2026, Oracle and/or its affiliates.
|
|
2
|
+
//-----------------------------------------------------------------------------
|
|
3
|
+
//
|
|
4
|
+
// This software is dual-licensed to you under the Universal Permissive License
|
|
5
|
+
// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
|
|
6
|
+
// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
|
|
7
|
+
// either license.
|
|
8
|
+
//
|
|
9
|
+
// If you elect to accept the software under the Apache License, Version 2.0,
|
|
10
|
+
// the following applies:
|
|
11
|
+
//
|
|
12
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
13
|
+
// you may not use this file except in compliance with the License.
|
|
14
|
+
// You may obtain a copy of the License at
|
|
15
|
+
//
|
|
16
|
+
// https://www.apache.org/licenses/LICENSE-2.0
|
|
17
|
+
//
|
|
18
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
19
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
20
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
21
|
+
// See the License for the specific language governing permissions and
|
|
22
|
+
// limitations under the License.
|
|
23
|
+
//
|
|
24
|
+
//-----------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
"use strict";
|
|
27
|
+
|
|
28
|
+
const util = require("node:util");
|
|
29
|
+
const { base } = require("../base.js");
|
|
30
|
+
const oracledb = require("oracledb");
|
|
31
|
+
|
|
32
|
+
let Storage;
|
|
33
|
+
|
|
34
|
+
class GCPStorageProvider extends base {
|
|
35
|
+
constructor(provider_arg, urlExtendedPart) {
|
|
36
|
+
super(urlExtendedPart);
|
|
37
|
+
this.parseProviderArg(provider_arg);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
init() {
|
|
41
|
+
({ Storage } = require("@google-cloud/storage"));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
parseProviderArg(providerArg) {
|
|
45
|
+
for (const entry of providerArg.split(";")) {
|
|
46
|
+
const [key, ...valueParts] = entry.split("=");
|
|
47
|
+
const value = valueParts.join("=");
|
|
48
|
+
|
|
49
|
+
if (!key || !value) {
|
|
50
|
+
throw new Error(
|
|
51
|
+
"GCP Storage Config Provider argument must use bucket=<bucket>;object=<object>;project=<project> is optional."
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
this._addParam(key.trim(), decodeURIComponent(value.trim()));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
for (const key of ["bucket", "object"]) {
|
|
59
|
+
if (!this.paramMap.get(key)) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
`GCP Storage Config Provider missing required ${key} value.`
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async returnConfig() {
|
|
68
|
+
const bucket = this.paramMap.get("bucket");
|
|
69
|
+
const object = this.paramMap.get("object");
|
|
70
|
+
|
|
71
|
+
const storageOptions = {};
|
|
72
|
+
const projectId = this.paramMap.get("project");
|
|
73
|
+
if (projectId) {
|
|
74
|
+
storageOptions.projectId = projectId;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// The Storage client manages the lifecycle of its underlying connections,
|
|
78
|
+
// no explicit close is required after downloading the configuration.
|
|
79
|
+
const storage = new Storage(storageOptions);
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
const [contents] = await storage
|
|
83
|
+
.bucket(bucket)
|
|
84
|
+
.file(object)
|
|
85
|
+
.download();
|
|
86
|
+
return JSON.parse(contents.toString("utf-8"));
|
|
87
|
+
} catch (e) {
|
|
88
|
+
const errmsg = util.format(
|
|
89
|
+
"Failed to retrieve or parse config from GCP Storage: %s\n%s",
|
|
90
|
+
e.message,
|
|
91
|
+
e.stack
|
|
92
|
+
);
|
|
93
|
+
throw new Error(errmsg);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
module.exports = GCPStorageProvider;
|
|
99
|
+
|
|
100
|
+
async function hookFn(args) {
|
|
101
|
+
const configProvider = new GCPStorageProvider(
|
|
102
|
+
args.provider_arg,
|
|
103
|
+
args.urlExtendedPart
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
configProvider.init();
|
|
108
|
+
} catch (err) {
|
|
109
|
+
const errmsg = util.format(
|
|
110
|
+
"GCP Storage Config Provider failed to load required modules: %s\n%s",
|
|
111
|
+
err.message,
|
|
112
|
+
err.stack
|
|
113
|
+
);
|
|
114
|
+
throw new Error(errmsg);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const cfg = await configProvider.returnConfig();
|
|
118
|
+
return [cfg, null];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
oracledb.registerConfigurationProviderHook("gcpstorage", hookFn);
|
|
@@ -87,7 +87,7 @@ class OCIProvider extends base {
|
|
|
87
87
|
if (auth == 'OCI_INSTANCE_PRINCIPAL') {
|
|
88
88
|
provider = await oci.common.InstancePrincipalsAuthenticationDetailsProviderBuilder().build();
|
|
89
89
|
} else if (auth == 'OCI_RESOURCE_PRINCIPAL') {
|
|
90
|
-
provider =
|
|
90
|
+
provider = oci.common.ResourcePrincipalAuthenticationDetailsProvider.builder();
|
|
91
91
|
} else {
|
|
92
92
|
const errmsg = util.format('OCI authentication failed: The authentication parameter value %s may be incorrect', auth);
|
|
93
93
|
throw new Error(errmsg);
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// Copyright (c) 2026, Oracle and/or its affiliates.
|
|
2
|
+
|
|
3
|
+
//-----------------------------------------------------------------------------
|
|
4
|
+
//
|
|
5
|
+
// This software is dual-licensed to you under the Universal Permissive License
|
|
6
|
+
// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
|
|
7
|
+
// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
|
|
8
|
+
// either license.
|
|
9
|
+
//
|
|
10
|
+
// If you elect to accept the software under the Apache License, Version 2.0,
|
|
11
|
+
// the following applies:
|
|
12
|
+
//
|
|
13
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
14
|
+
// you may not use this file except in compliance with the License.
|
|
15
|
+
// You may obtain a copy of the License at
|
|
16
|
+
//
|
|
17
|
+
// https://www.apache.org/licenses/LICENSE-2.0
|
|
18
|
+
//
|
|
19
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
20
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
21
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
22
|
+
// See the License for the specific language governing permissions and
|
|
23
|
+
// limitations under the License.
|
|
24
|
+
//
|
|
25
|
+
// Shared process-wide token-cache support for built-in token extensions.
|
|
26
|
+
//-----------------------------------------------------------------------------
|
|
27
|
+
'use strict';
|
|
28
|
+
|
|
29
|
+
const crypto = require('crypto');
|
|
30
|
+
const { ObfuscatedValue } = require('../../lib/obfuscation.js');
|
|
31
|
+
|
|
32
|
+
//---------------------------------------------------------------------------
|
|
33
|
+
// hashValue()
|
|
34
|
+
//
|
|
35
|
+
// Hash a secret before it becomes part of a process-wide cache key. A changed
|
|
36
|
+
// secret gets a distinct entry without retaining plaintext credentials in a
|
|
37
|
+
// Map key.
|
|
38
|
+
//---------------------------------------------------------------------------
|
|
39
|
+
function hashValue(value) {
|
|
40
|
+
return crypto.createHash('sha256').update(String(value)).digest('hex');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
//---------------------------------------------------------------------------
|
|
44
|
+
// TokenCache
|
|
45
|
+
//
|
|
46
|
+
// A process-wide bounded LRU cache with expiry-aware lookup and single-flight
|
|
47
|
+
// acquisition. Cached token values are obfuscated, including object values
|
|
48
|
+
// such as OCI token/private-key pairs. acquire() may be synchronous or async
|
|
49
|
+
// and returns { value, validUntil }. Entries without a future validUntil are
|
|
50
|
+
// returned but intentionally not cached.
|
|
51
|
+
//---------------------------------------------------------------------------
|
|
52
|
+
class TokenCache {
|
|
53
|
+
constructor(maxEntries) {
|
|
54
|
+
this._maxEntries = maxEntries;
|
|
55
|
+
this._entries = new Map();
|
|
56
|
+
this._inFlight = new Map();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async get(key, acquire, { enabled = true } = {}) {
|
|
60
|
+
if (enabled) {
|
|
61
|
+
const cached = this._entries.get(key);
|
|
62
|
+
if (cached?.validUntil > Date.now()) {
|
|
63
|
+
// Moving the hit to the end maintains insertion-order LRU semantics.
|
|
64
|
+
this._entries.delete(key);
|
|
65
|
+
this._entries.set(key, cached);
|
|
66
|
+
return {
|
|
67
|
+
value: restoreValue(cached.value),
|
|
68
|
+
validUntil: cached.validUntil,
|
|
69
|
+
isCacheHit: true,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
if (cached) {
|
|
73
|
+
clearValue(cached.value);
|
|
74
|
+
this._entries.delete(key);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let request = this._inFlight.get(key);
|
|
79
|
+
if (!request) {
|
|
80
|
+
request = Promise.resolve().then(acquire).then((result) => {
|
|
81
|
+
if (enabled && result?.validUntil > Date.now()) {
|
|
82
|
+
this._evictLeastRecentlyUsed();
|
|
83
|
+
this._entries.set(key, {
|
|
84
|
+
value: protectValue(result.value),
|
|
85
|
+
validUntil: result.validUntil,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return result;
|
|
89
|
+
});
|
|
90
|
+
this._inFlight.set(key, request);
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
const result = await request;
|
|
94
|
+
return {
|
|
95
|
+
value: result.value,
|
|
96
|
+
validUntil: result.validUntil,
|
|
97
|
+
isCacheHit: false,
|
|
98
|
+
};
|
|
99
|
+
} finally {
|
|
100
|
+
if (this._inFlight.get(key) === request) {
|
|
101
|
+
this._inFlight.delete(key);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
_evictLeastRecentlyUsed() {
|
|
107
|
+
while (this._entries.size >= this._maxEntries) {
|
|
108
|
+
const oldestKey = this._entries.keys().next().value;
|
|
109
|
+
const oldestEntry = this._entries.get(oldestKey);
|
|
110
|
+
clearValue(oldestEntry.value);
|
|
111
|
+
this._entries.delete(oldestKey);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function protectValue(value) {
|
|
117
|
+
if (typeof value === 'string') {
|
|
118
|
+
return new ObfuscatedValue(value);
|
|
119
|
+
}
|
|
120
|
+
if (Array.isArray(value)) {
|
|
121
|
+
return value.map(protectValue);
|
|
122
|
+
}
|
|
123
|
+
if (value && typeof value === 'object') {
|
|
124
|
+
return Object.fromEntries(Object.entries(value).map(([key, entry]) =>
|
|
125
|
+
[key, protectValue(entry)]));
|
|
126
|
+
}
|
|
127
|
+
return value;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function restoreValue(value) {
|
|
131
|
+
if (value instanceof ObfuscatedValue) {
|
|
132
|
+
return value.get();
|
|
133
|
+
}
|
|
134
|
+
if (Array.isArray(value)) {
|
|
135
|
+
return value.map(restoreValue);
|
|
136
|
+
}
|
|
137
|
+
if (value && typeof value === 'object') {
|
|
138
|
+
return Object.fromEntries(Object.entries(value).map(([key, entry]) =>
|
|
139
|
+
[key, restoreValue(entry)]));
|
|
140
|
+
}
|
|
141
|
+
return value;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function clearValue(value) {
|
|
145
|
+
if (value instanceof ObfuscatedValue) {
|
|
146
|
+
value.clear();
|
|
147
|
+
} else if (Array.isArray(value)) {
|
|
148
|
+
value.forEach(clearValue);
|
|
149
|
+
} else if (value && typeof value === 'object') {
|
|
150
|
+
Object.values(value).forEach(clearValue);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
module.exports = { TokenCache, hashValue };
|