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.
Files changed (84) hide show
  1. package/README.md +16 -6
  2. package/build/Release/oracledb-26.0.0-darwin-arm64.node +0 -0
  3. package/build/Release/oracledb-26.0.0-darwin-arm64.node-buildinfo.txt +1 -0
  4. package/build/Release/oracledb-26.0.0-js-buildinfo.txt +1 -0
  5. package/build/Release/oracledb-26.0.0-linux-arm64.node +0 -0
  6. package/build/Release/oracledb-26.0.0-linux-arm64.node-buildinfo.txt +1 -0
  7. package/build/Release/oracledb-26.0.0-linux-x64.node +0 -0
  8. package/build/Release/oracledb-26.0.0-linux-x64.node-buildinfo.txt +1 -0
  9. package/build/Release/oracledb-26.0.0-win32-x64.node +0 -0
  10. package/build/Release/oracledb-26.0.0-win32-x64.node-buildinfo.txt +1 -0
  11. package/lib/aqDeqOptions.js +4 -4
  12. package/lib/aqEnqOptions.js +2 -2
  13. package/lib/aqQueue.js +11 -6
  14. package/lib/connection.js +73 -18
  15. package/lib/constants.js +4 -0
  16. package/lib/endUserSecurityContext.js +30 -25
  17. package/lib/errors.js +92 -5
  18. package/lib/impl/connection.js +36 -0
  19. package/lib/impl/datahandlers/buffer.js +91 -18
  20. package/lib/impl/datahandlers/oson.js +3 -2
  21. package/lib/impl/parserHelpers.js +6 -2
  22. package/lib/impl/resultset.js +32 -11
  23. package/lib/oracledb.js +34 -0
  24. package/lib/pool.js +6 -2
  25. package/lib/queryStream.js +13 -5
  26. package/lib/resultset.js +10 -3
  27. package/lib/securityContextProvider.js +206 -0
  28. package/lib/thin/aq.js +2 -3
  29. package/lib/thin/connection.js +380 -24
  30. package/lib/thin/dbObject.js +20 -11
  31. package/lib/thin/pool.js +4 -3
  32. package/lib/thin/protocol/capabilities.js +37 -4
  33. package/lib/thin/protocol/constants.js +49 -1
  34. package/lib/thin/protocol/messages/aqArray.js +2 -2
  35. package/lib/thin/protocol/messages/aqBase.js +2 -2
  36. package/lib/thin/protocol/messages/auth.js +17 -6
  37. package/lib/thin/protocol/messages/base.js +75 -17
  38. package/lib/thin/protocol/messages/dataType.js +46 -0
  39. package/lib/thin/protocol/messages/directPathLoadStream.js +3 -2
  40. package/lib/thin/protocol/messages/execute.js +10 -4
  41. package/lib/thin/protocol/messages/index.js +4 -0
  42. package/lib/thin/protocol/messages/notification.js +281 -0
  43. package/lib/thin/protocol/messages/subscribe.js +181 -0
  44. package/lib/thin/protocol/messages/withData.js +23 -10
  45. package/lib/thin/protocol/packet.js +2 -4
  46. package/lib/thin/protocol/protocol.js +7 -4
  47. package/lib/thin/resultSet.js +40 -20
  48. package/lib/thin/sqlnet/ezConnectResolver.js +9 -2
  49. package/lib/thin/sqlnet/navNodes.js +7 -5
  50. package/lib/thin/sqlnet/networkSession.js +17 -11
  51. package/lib/thin/sqlnet/ntTcp.js +24 -8
  52. package/lib/thin/sqlnet/packet.js +24 -4
  53. package/lib/thin/sqlnet/sessionAtts.js +2 -2
  54. package/lib/thin/statement.js +6 -1
  55. package/lib/thin/statementCache.js +34 -6
  56. package/lib/thin/subscr.js +363 -0
  57. package/lib/transformer.js +60 -17
  58. package/lib/util.js +46 -8
  59. package/lib/version.js +1 -1
  60. package/package.json +1 -1
  61. package/plugins/configProviders/gcpstorage/index.js +121 -0
  62. package/plugins/configProviders/ociobject/index.js +1 -1
  63. package/plugins/token/cache.js +154 -0
  64. package/plugins/token/endUserSecurityProvider/index.js +377 -0
  65. package/plugins/token/extensionAzure/index.js +160 -28
  66. package/plugins/token/extensionOci/index.js +221 -16
  67. package/plugins/vectorsdk/OracleVecDB.js +346 -0
  68. package/plugins/vectorsdk/index.js +49 -0
  69. package/plugins/vectorsdk/utils.js +370 -0
  70. package/plugins/vectorsdk/vectorEmbedding.js +223 -0
  71. package/plugins/vectorsdk/vectorIndex.js +177 -0
  72. package/plugins/vectorsdk/vectorRecords.js +406 -0
  73. package/plugins/vectorsdk/vectorSchema.js +408 -0
  74. package/plugins/vectorsdk/vectorSearch.js +538 -0
  75. package/build/Release/oracledb-7.0.0-darwin-arm64.node +0 -0
  76. package/build/Release/oracledb-7.0.0-darwin-arm64.node-buildinfo.txt +0 -1
  77. package/build/Release/oracledb-7.0.0-js-buildinfo.txt +0 -1
  78. package/build/Release/oracledb-7.0.0-linux-arm64.node +0 -0
  79. package/build/Release/oracledb-7.0.0-linux-arm64.node-buildinfo.txt +0 -1
  80. package/build/Release/oracledb-7.0.0-linux-x64.node +0 -0
  81. package/build/Release/oracledb-7.0.0-linux-x64.node-buildinfo.txt +0 -1
  82. package/build/Release/oracledb-7.0.0-win32-x64.node +0 -0
  83. package/build/Release/oracledb-7.0.0-win32-x64.node-buildinfo.txt +0 -1
  84. 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 = await new oci.common.ResourcePrincipalAuthenticationDetailsProvider.builder();
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);