oracledb 7.0.0 → 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/aqDeqOptions.js +4 -4
- package/lib/aqEnqOptions.js +2 -2
- package/lib/aqQueue.js +11 -6
- package/lib/connection.js +73 -18
- package/lib/constants.js +4 -0
- package/lib/endUserSecurityContext.js +30 -25
- package/lib/errors.js +92 -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/parserHelpers.js +6 -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 +380 -24
- package/lib/thin/dbObject.js +20 -11
- package/lib/thin/pool.js +4 -3
- package/lib/thin/protocol/capabilities.js +37 -4
- package/lib/thin/protocol/constants.js +49 -1
- package/lib/thin/protocol/messages/aqArray.js +2 -2
- package/lib/thin/protocol/messages/aqBase.js +2 -2
- package/lib/thin/protocol/messages/auth.js +17 -6
- package/lib/thin/protocol/messages/base.js +75 -17
- 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 +23 -10
- 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 +9 -2
- package/lib/thin/sqlnet/navNodes.js +7 -5
- package/lib/thin/sqlnet/networkSession.js +17 -11
- package/lib/thin/sqlnet/ntTcp.js +24 -8
- package/lib/thin/sqlnet/packet.js +24 -4
- package/lib/thin/sqlnet/sessionAtts.js +2 -2
- 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 +1 -1
- 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.0-darwin-arm64.node +0 -0
- package/build/Release/oracledb-7.0.0-darwin-arm64.node-buildinfo.txt +0 -1
- package/build/Release/oracledb-7.0.0-js-buildinfo.txt +0 -1
- package/build/Release/oracledb-7.0.0-linux-arm64.node +0 -0
- package/build/Release/oracledb-7.0.0-linux-arm64.node-buildinfo.txt +0 -1
- package/build/Release/oracledb-7.0.0-linux-x64.node +0 -0
- package/build/Release/oracledb-7.0.0-linux-x64.node-buildinfo.txt +0 -1
- package/build/Release/oracledb-7.0.0-win32-x64.node +0 -0
- package/build/Release/oracledb-7.0.0-win32-x64.node-buildinfo.txt +0 -1
- package/eslint.config.js +0 -39
|
@@ -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 };
|
|
@@ -0,0 +1,377 @@
|
|
|
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
|
+
//-----------------------------------------------------------------------------
|
|
26
|
+
'use strict';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* End-user security context token provider. Each configured pool or standalone
|
|
30
|
+
* connection owns its provider; there is no process-global provider setting.
|
|
31
|
+
*/
|
|
32
|
+
const oracledb = require('oracledb');
|
|
33
|
+
const securityContextProvider = oracledb.getSecurityContextProvider();
|
|
34
|
+
|
|
35
|
+
const hasOwn = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);
|
|
36
|
+
// These endUserSecParams fields are consumed by this provider and must not be
|
|
37
|
+
// forwarded to an Azure or OCI token extension as token configuration.
|
|
38
|
+
const PROVIDER_CONTROL_FIELDS = new Set([
|
|
39
|
+
'spiType', 'authFlow', 'endUserToken', 'endUserName',
|
|
40
|
+
'dataRoles', 'attributes', 'key',
|
|
41
|
+
'cacheOptions', 'contextResolution'
|
|
42
|
+
]);
|
|
43
|
+
const DEFAULT_METADATA_FIELDS = [
|
|
44
|
+
'endUserToken', 'endUserName', 'dataRoles', 'attributes', 'key',
|
|
45
|
+
];
|
|
46
|
+
const PROVIDER_MODULE_PATHS = {
|
|
47
|
+
azure: '../extensionAzure/index.js',
|
|
48
|
+
oci: '../extensionOci/index.js',
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
function throwErr(message, code) {
|
|
52
|
+
const err = new Error(message);
|
|
53
|
+
if (code) {
|
|
54
|
+
err.code = code;
|
|
55
|
+
}
|
|
56
|
+
throw err;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function ensureInvocationHook() {
|
|
60
|
+
if (!securityContextProvider.getInvocationHook?.()) {
|
|
61
|
+
securityContextProvider.setInvocationHook(
|
|
62
|
+
securityContextProvider.createInvocationHook()
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function normalizeAuthFlow(value) {
|
|
68
|
+
const flow = String(value ?? 'onBehalfOf').trim().toLowerCase();
|
|
69
|
+
if (flow === 'app' || flow === 'clientcredentials' ||
|
|
70
|
+
flow === 'client_credentials' || flow === 'client-credentials') {
|
|
71
|
+
return 'app';
|
|
72
|
+
}
|
|
73
|
+
if (flow === 'obo' || flow === 'onbehalfof' ||
|
|
74
|
+
flow === 'on_behalf_of' || flow === 'on-behalf-of') {
|
|
75
|
+
return 'obo';
|
|
76
|
+
}
|
|
77
|
+
throwErr("endUserSecParams.authFlow must be 'app' or 'onBehalfOf'.");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function normalizeContextResolution(value) {
|
|
81
|
+
const resolution = String(value ?? 'connection').trim().toLowerCase();
|
|
82
|
+
if (resolution === 'connection' || resolution === 'operation') {
|
|
83
|
+
return resolution;
|
|
84
|
+
}
|
|
85
|
+
throwErr("endUserSecParams.contextResolution must be 'connection' or 'operation'.");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function normalizeProvider(params) {
|
|
89
|
+
const spiType = String(params.spiType ?? 'oci').trim().toLowerCase();
|
|
90
|
+
const modulePath = PROVIDER_MODULE_PATHS[spiType];
|
|
91
|
+
if (!modulePath) {
|
|
92
|
+
throwErr("endUserSecParams.spiType must be 'azure' or 'oci'.");
|
|
93
|
+
}
|
|
94
|
+
// Start with the supplied parameters, then remove fields interpreted by this
|
|
95
|
+
// end-user-security provider. The remaining tokenConfig is passed only to
|
|
96
|
+
// the selected Azure or OCI extension, so request metadata and provider
|
|
97
|
+
// controls cannot accidentally be treated as token-client options.
|
|
98
|
+
const tokenConfig = { ...params };
|
|
99
|
+
for (const key of PROVIDER_CONTROL_FIELDS) {
|
|
100
|
+
delete tokenConfig[key];
|
|
101
|
+
}
|
|
102
|
+
// Azure's application-token helper expects this value. It is harmless for
|
|
103
|
+
// OBO-capable configurations because both flows use the same credentials.
|
|
104
|
+
if (spiType === 'azure' && !tokenConfig.authType) {
|
|
105
|
+
tokenConfig.authType = 'azureserviceprincipal';
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
vendor: spiType,
|
|
109
|
+
modulePath,
|
|
110
|
+
tokenConfig,
|
|
111
|
+
cacheOptions: params.cacheOptions ? { ...params.cacheOptions } : undefined
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Creates a provider from the `endUserSecParams` shape. Configuration fields
|
|
117
|
+
* that describe the identity provider are static. `endUserToken`,
|
|
118
|
+
* `endUserName`, `key`, `dataRoles`, and `attributes` are defaults only: request
|
|
119
|
+
* metadata supplied by runWithContext() wins.
|
|
120
|
+
*/
|
|
121
|
+
function configureUnifiedContextProvider(params) {
|
|
122
|
+
ensureInvocationHook();
|
|
123
|
+
const config = normalizeConfig(params);
|
|
124
|
+
const provider = loadProviderModule(config.provider);
|
|
125
|
+
return createProviderFn(config, provider);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function mergeMetadata(defaults, current) {
|
|
129
|
+
if (!current || typeof current !== 'object') {
|
|
130
|
+
return defaults;
|
|
131
|
+
}
|
|
132
|
+
return { ...defaults, ...current };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function getEndUserToken(metadata) {
|
|
136
|
+
return metadata.endUserToken;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function createProviderFn(config, provider) {
|
|
140
|
+
const unifiedProvider = async function unifiedProvider() {
|
|
141
|
+
const current = securityContextProvider.getCurrentContext();
|
|
142
|
+
if (!current || typeof current !== "object") {
|
|
143
|
+
// Do not acquire an application token for normal work outside a scope.
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
if (hasOwn(current, 'authorization')) {
|
|
147
|
+
throwErr('Security context metadata.authorization is not supported; use endUserToken.');
|
|
148
|
+
}
|
|
149
|
+
const metadata = mergeMetadata(config.defaultMetadata, current);
|
|
150
|
+
const endUserToken = getEndUserToken(metadata);
|
|
151
|
+
// authFlow is fixed when the pool or standalone connection is created.
|
|
152
|
+
// Per-request metadata supplies identity only; it cannot select a token
|
|
153
|
+
// exchange for which the configured provider may lack credentials.
|
|
154
|
+
if (config.authFlow === "obo") {
|
|
155
|
+
if (config.provider.vendor !== 'azure') {
|
|
156
|
+
throwErr('On-behalf-of security contexts are supported only with spiType "azure".');
|
|
157
|
+
}
|
|
158
|
+
if (!endUserToken) {
|
|
159
|
+
throwErr("authFlow=obo requires an end-user token.");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const tokenResult = await acquireOboToken(config, provider, endUserToken);
|
|
163
|
+
return getOrCreateScopedSecurityContext(unifiedProvider, config,
|
|
164
|
+
tokenResult, () => createSecurityContext(finalizeOboMode(metadata,
|
|
165
|
+
tokenResult.accessToken)));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const tokenResult = await acquireAppToken(config, provider);
|
|
169
|
+
const databaseAccessToken = tokenResult.accessToken;
|
|
170
|
+
// No end-user identity means the database resolves the application
|
|
171
|
+
// identity directly from the client-credential database access token.
|
|
172
|
+
if (!endUserToken && !metadata.endUserName) {
|
|
173
|
+
// A key identifies a named database-managed end user. It is not valid
|
|
174
|
+
// for direct application logon, but roles and attributes are valid
|
|
175
|
+
// authorization metadata for either identity form.
|
|
176
|
+
if (metadata.key) {
|
|
177
|
+
throwErr("authFlow 'app' requires endUserName when key is supplied.");
|
|
178
|
+
}
|
|
179
|
+
return getOrCreateScopedSecurityContext(unifiedProvider, config,
|
|
180
|
+
tokenResult, () => createSecurityContext(
|
|
181
|
+
buildContext({
|
|
182
|
+
databaseAccessToken,
|
|
183
|
+
dataRoles: metadata.dataRoles,
|
|
184
|
+
attributes: metadata.attributes
|
|
185
|
+
})));
|
|
186
|
+
}
|
|
187
|
+
// App mode uses the application token to authorize database access and
|
|
188
|
+
// carries a supplied end-user token directly in the EUSC. This is not an
|
|
189
|
+
// OBO exchange; Azure and OCI both support this explicit identity form.
|
|
190
|
+
if (endUserToken) {
|
|
191
|
+
if (metadata.endUserName || metadata.key) {
|
|
192
|
+
throwErr('App mode cannot combine endUserToken with endUserName or key.');
|
|
193
|
+
}
|
|
194
|
+
return getOrCreateScopedSecurityContext(unifiedProvider, config,
|
|
195
|
+
tokenResult, () => createSecurityContext(finalizeAppTokenMode(metadata,
|
|
196
|
+
databaseAccessToken)));
|
|
197
|
+
}
|
|
198
|
+
if (!metadata.endUserName) {
|
|
199
|
+
throwErr("authFlow 'app' requires endUserName when endUserToken is supplied.");
|
|
200
|
+
}
|
|
201
|
+
return getOrCreateScopedSecurityContext(unifiedProvider, config,
|
|
202
|
+
tokenResult, () => createSecurityContext(
|
|
203
|
+
finalizeAppMode(metadata, databaseAccessToken)));
|
|
204
|
+
};
|
|
205
|
+
// The driver reads this private property to choose the provider lifecycle.
|
|
206
|
+
unifiedProvider._contextResolution = config.contextResolution;
|
|
207
|
+
return unifiedProvider;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function createSecurityContext(context) {
|
|
211
|
+
return new oracledb.EndUserSecurityContext(context);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Operation resolution calls the provider for every database operation so the
|
|
215
|
+
// token extension remains authoritative for expiry and refresh. When it
|
|
216
|
+
// confirms the same database token came from its cache, reuse the already
|
|
217
|
+
// encoded EUSC for this runWithContext() scope. The scope cache holds no
|
|
218
|
+
// plaintext token and is released when the async scope completes.
|
|
219
|
+
function getOrCreateScopedSecurityContext(provider, config, tokenResult,
|
|
220
|
+
createContext) {
|
|
221
|
+
if (config.contextResolution !== 'operation') {
|
|
222
|
+
return createContext();
|
|
223
|
+
}
|
|
224
|
+
const cached = securityContextProvider._getScopedValue(provider);
|
|
225
|
+
if (cached && tokenResult.isNewToken === false) {
|
|
226
|
+
return cached;
|
|
227
|
+
}
|
|
228
|
+
const securityContext = createContext();
|
|
229
|
+
securityContextProvider._setScopedValue(provider, securityContext);
|
|
230
|
+
return securityContext;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function finalizeOboMode(metadata, databaseAccessToken) {
|
|
234
|
+
const attributeResolution = resolveAttributesForRequest(metadata);
|
|
235
|
+
const context = {
|
|
236
|
+
databaseAccessToken,
|
|
237
|
+
dataRoles: metadata.dataRoles,
|
|
238
|
+
attributes: attributeResolution.payload
|
|
239
|
+
};
|
|
240
|
+
context.endUserToken = getEndUserToken(metadata);
|
|
241
|
+
return buildContext(context);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function finalizeAppMode(metadata, databaseAccessToken) {
|
|
245
|
+
const context = buildContext({
|
|
246
|
+
databaseAccessToken,
|
|
247
|
+
endUserName: metadata.endUserName,
|
|
248
|
+
dataRoles: metadata.dataRoles,
|
|
249
|
+
key: metadata.key,
|
|
250
|
+
attributes: metadata.attributes
|
|
251
|
+
});
|
|
252
|
+
return context;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function finalizeAppTokenMode(metadata, databaseAccessToken) {
|
|
256
|
+
return buildContext({
|
|
257
|
+
databaseAccessToken,
|
|
258
|
+
endUserToken: metadata.endUserToken,
|
|
259
|
+
dataRoles: metadata.dataRoles,
|
|
260
|
+
attributes: metadata.attributes
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async function acquireAppToken(config, provider) {
|
|
265
|
+
const result = await provider.getTokenResult(config.provider.tokenConfig);
|
|
266
|
+
return {
|
|
267
|
+
accessToken: getAccessToken(result,
|
|
268
|
+
'Token provider failed to return an application access token.'),
|
|
269
|
+
isNewToken: result?.isNewToken !== false,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async function acquireOboToken(config, provider, endUserToken) {
|
|
274
|
+
const result = await provider.getOnBehalfOfToken({
|
|
275
|
+
endUserToken,
|
|
276
|
+
oboConfig: config.provider.tokenConfig,
|
|
277
|
+
cacheOptions: config.provider.cacheOptions
|
|
278
|
+
});
|
|
279
|
+
const accessToken = getAccessToken(result,
|
|
280
|
+
'Token provider failed to return an access token for on-behalf-of exchange.');
|
|
281
|
+
return {
|
|
282
|
+
accessToken,
|
|
283
|
+
// Extensions set false only when returning a valid cached database token.
|
|
284
|
+
// Unknown/custom provider results are conservative and force a new EUSC.
|
|
285
|
+
isNewToken: result?.isNewToken !== false,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function getAccessToken(result, errorMessage) {
|
|
290
|
+
const accessToken = typeof result === 'string'
|
|
291
|
+
? result
|
|
292
|
+
: (result?.accessToken ?? result?.token);
|
|
293
|
+
if (typeof accessToken !== 'string' || !accessToken.trim()) {
|
|
294
|
+
throwErr(errorMessage);
|
|
295
|
+
}
|
|
296
|
+
return accessToken;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function loadProviderModule(descriptor) {
|
|
300
|
+
return require(descriptor.modulePath);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function buildContext(base) {
|
|
304
|
+
const context = { databaseAccessToken: base.databaseAccessToken };
|
|
305
|
+
for (const key of ['endUserToken', 'endUserName', 'dataRoles', 'key', 'attributes']) {
|
|
306
|
+
if (base[key] !== undefined) {
|
|
307
|
+
context[key] = base[key];
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return context;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function normalizeConfig(params) {
|
|
314
|
+
if (!params || typeof params !== 'object' || Array.isArray(params)) {
|
|
315
|
+
throw new TypeError('endUserSecParams must be an object.');
|
|
316
|
+
}
|
|
317
|
+
const defaultMetadata = {};
|
|
318
|
+
for (const key of DEFAULT_METADATA_FIELDS) {
|
|
319
|
+
if (hasOwn(params, key)) {
|
|
320
|
+
defaultMetadata[key] = params[key];
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
const authFlow = normalizeAuthFlow(params.authFlow);
|
|
324
|
+
const provider = normalizeProvider(params);
|
|
325
|
+
if (authFlow === 'obo' && provider.vendor !== 'azure') {
|
|
326
|
+
throwErr('On-behalf-of security contexts are supported only with spiType "azure".');
|
|
327
|
+
}
|
|
328
|
+
return {
|
|
329
|
+
authFlow,
|
|
330
|
+
contextResolution: normalizeContextResolution(params.contextResolution),
|
|
331
|
+
defaultMetadata: snapshotDefaultMetadata(defaultMetadata),
|
|
332
|
+
provider
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Provider defaults are static after pool/connection creation. Snapshot the
|
|
337
|
+
// mutable fields so changes to the caller's configuration object cannot alter
|
|
338
|
+
// a scope after the provider has been configured.
|
|
339
|
+
function snapshotDefaultMetadata(metadata) {
|
|
340
|
+
const snapshot = { ...metadata };
|
|
341
|
+
if (Array.isArray(metadata.dataRoles)) {
|
|
342
|
+
snapshot.dataRoles = [...metadata.dataRoles];
|
|
343
|
+
}
|
|
344
|
+
if (metadata.attributes && typeof metadata.attributes === 'object') {
|
|
345
|
+
snapshot.attributes = JSON.parse(JSON.stringify(metadata.attributes));
|
|
346
|
+
}
|
|
347
|
+
return snapshot;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function resolveAttributesForRequest(metadata) {
|
|
351
|
+
if (!hasOwn(metadata, 'attributes')) {
|
|
352
|
+
return { payload: undefined };
|
|
353
|
+
}
|
|
354
|
+
try {
|
|
355
|
+
JSON.stringify(metadata.attributes);
|
|
356
|
+
} catch (err) {
|
|
357
|
+
const error = new Error('security context attributes must be JSON-serializable.');
|
|
358
|
+
error.cause = err;
|
|
359
|
+
throw error;
|
|
360
|
+
}
|
|
361
|
+
return { payload: metadata.attributes };
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Process configuration hook. `endUserSecParams` is copied to the pool or
|
|
366
|
+
* standalone connection being created. Dynamic request metadata is supplied
|
|
367
|
+
* later with runWithContext().
|
|
368
|
+
*/
|
|
369
|
+
function hookFn(options) {
|
|
370
|
+
if (!options.endUserSecParams) {
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
options._securityContextProvider =
|
|
374
|
+
configureUnifiedContextProvider(options.endUserSecParams);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
oracledb.registerProcessConfigurationHook(hookFn);
|