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
|
@@ -26,9 +26,27 @@
|
|
|
26
26
|
'use strict';
|
|
27
27
|
const oracledb = require('oracledb');
|
|
28
28
|
const msal = require('@azure/msal-node');
|
|
29
|
+
const { TokenCache, hashValue } = require('../cache.js');
|
|
29
30
|
|
|
31
|
+
const OBO_DEFAULT_CLOCK_SKEW_MS = 60 * 1000;
|
|
32
|
+
const TOKEN_CACHE_MAX_ENTRIES = 100;
|
|
33
|
+
// Process-wide, bounded LRU cache of completed OBO exchanges. Connections
|
|
34
|
+
// safely share entries only when their cache key has the same caller-token
|
|
35
|
+
// hash and every Azure credential/request input that can change the result.
|
|
36
|
+
// Values are obfuscated at rest.
|
|
37
|
+
const oboTokenCache = new TokenCache(TOKEN_CACHE_MAX_ENTRIES);
|
|
38
|
+
// Process-wide, bounded cache of service-principal tokens. Its key includes
|
|
39
|
+
// every credential and request input that can change the resulting token, so
|
|
40
|
+
// pools can share only compatible entries. Values are obfuscated at rest.
|
|
41
|
+
const appTokenCache = new TokenCache(TOKEN_CACHE_MAX_ENTRIES);
|
|
30
42
|
|
|
31
|
-
|
|
43
|
+
|
|
44
|
+
// Internal Deep Security API. The driver's ordinary token-authentication hook
|
|
45
|
+
// below returns this result's access-token string.
|
|
46
|
+
async function getTokenResult(params) {
|
|
47
|
+
if (typeof params?.authType !== 'string') {
|
|
48
|
+
throwErr('Azure authentication type is required.');
|
|
49
|
+
}
|
|
32
50
|
switch (params.authType.toLowerCase()) {
|
|
33
51
|
case 'azureserviceprincipal':
|
|
34
52
|
return await servicePrincipalCredentials(params);
|
|
@@ -46,9 +64,7 @@ function throwErr(message) {
|
|
|
46
64
|
|
|
47
65
|
//---------------------------------------------------------------------------
|
|
48
66
|
// Returns the access token for authentication as a service principal. This
|
|
49
|
-
// authentication method requires a client ID
|
|
50
|
-
// certificate. These values may be provided as config parameters read by
|
|
51
|
-
// the Azure SDK.
|
|
67
|
+
// authentication method requires a client ID and client secret.
|
|
52
68
|
// ---------------------------------------------------------------------------
|
|
53
69
|
async function servicePrincipalCredentials(params) {
|
|
54
70
|
const clientId = params.clientId ??
|
|
@@ -57,31 +73,24 @@ async function servicePrincipalCredentials(params) {
|
|
|
57
73
|
throwErr("Token based authentication config parameter authority is missing for azureServicePrincipal in extensionAzure plugins.");
|
|
58
74
|
const clientSecret = params.clientSecret ??
|
|
59
75
|
throwErr("Token based authentication config parameter clientSecret is missing for azureServicePrincipal in extensionAzure plugins.");
|
|
60
|
-
const msalConfig = {
|
|
61
|
-
auth: {
|
|
62
|
-
clientId: clientId,
|
|
63
|
-
authority: authority,
|
|
64
|
-
clientSecret: clientSecret,
|
|
65
|
-
},
|
|
66
|
-
system: {
|
|
67
|
-
// Use it while generating azure tokens behind proxy
|
|
68
|
-
// Otherwise can remove this option
|
|
69
|
-
networkClient: params.proxy,
|
|
70
|
-
},
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
if (!msalConfig.system.networkClient) {
|
|
74
|
-
delete msalConfig.system.networkClient;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
76
|
const scopes = params.scopes ??
|
|
78
77
|
throwErr("Token based authentication config parameter scopes is missing for azureServicePrincipal in extensionAzure plugins.");
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
78
|
+
const normalizedScopes = normalizeScopes(scopes);
|
|
79
|
+
const config = { ...params, clientId, authority, clientSecret };
|
|
80
|
+
const cacheKey = computeAppCacheKey(config, normalizedScopes);
|
|
81
|
+
const result = await appTokenCache.get(cacheKey, async () => {
|
|
82
|
+
const authResponse = await createClient(config).acquireTokenByClientCredential({
|
|
83
|
+
scopes: normalizedScopes,
|
|
84
|
+
});
|
|
85
|
+
if (!authResponse?.accessToken) {
|
|
86
|
+
throwErr('Failed to acquire application access token from Azure.');
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
value: authResponse.accessToken,
|
|
90
|
+
validUntil: calculateExpiry(authResponse, OBO_DEFAULT_CLOCK_SKEW_MS),
|
|
91
|
+
};
|
|
92
|
+
});
|
|
93
|
+
return { accessToken: result.value, isNewToken: !result.isCacheHit };
|
|
85
94
|
}
|
|
86
95
|
|
|
87
96
|
// ---------------------------------------------------------------------------
|
|
@@ -92,9 +101,132 @@ async function servicePrincipalCredentials(params) {
|
|
|
92
101
|
function hookFn(options) {
|
|
93
102
|
if (options.tokenAuthConfigAzure) {
|
|
94
103
|
options.accessToken = async function callbackFn(refresh, config) {
|
|
95
|
-
return await
|
|
104
|
+
return (await getTokenResult(config)).accessToken;
|
|
96
105
|
};
|
|
97
106
|
options.accessTokenConfig = options.tokenAuthConfigAzure;
|
|
98
107
|
}
|
|
99
108
|
}
|
|
100
109
|
oracledb.registerProcessConfigurationHook(hookFn);
|
|
110
|
+
|
|
111
|
+
//---------------------------------------------------------------------------
|
|
112
|
+
// normalizeScopes()
|
|
113
|
+
//---------------------------------------------------------------------------
|
|
114
|
+
function normalizeScopes(scopes) {
|
|
115
|
+
const normalized = Array.isArray(scopes)
|
|
116
|
+
? scopes
|
|
117
|
+
: (typeof scopes === 'string' ? [scopes] : null);
|
|
118
|
+
if (!normalized || normalized.some((scope) =>
|
|
119
|
+
typeof scope !== 'string' || !scope.trim())) {
|
|
120
|
+
throwErr('Azure scopes must be a string or an array of non-empty strings.');
|
|
121
|
+
}
|
|
122
|
+
return normalized.map((scope) => scope.trim()).sort();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
//---------------------------------------------------------------------------
|
|
126
|
+
// buildNetworkClient()
|
|
127
|
+
//---------------------------------------------------------------------------
|
|
128
|
+
function buildNetworkClient(proxyConfig) {
|
|
129
|
+
if (!proxyConfig) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
if (typeof proxyConfig.sendGetRequestAsync === 'function' &&
|
|
133
|
+
typeof proxyConfig.sendPostRequestAsync === 'function') {
|
|
134
|
+
return proxyConfig;
|
|
135
|
+
}
|
|
136
|
+
throwErr('Azure proxy configuration must implement sendGetRequestAsync() and sendPostRequestAsync().');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
//---------------------------------------------------------------------------
|
|
140
|
+
// computeOboCacheKey()
|
|
141
|
+
//---------------------------------------------------------------------------
|
|
142
|
+
function computeOboCacheKey(config, scopes, endUserToken) {
|
|
143
|
+
// Do not put the assertion or client secret itself in a process-wide Map.
|
|
144
|
+
// The secret hash separates entries if a client secret is rotated while the
|
|
145
|
+
// process is running.
|
|
146
|
+
return JSON.stringify([
|
|
147
|
+
config.authType?.toLowerCase(), config.clientId,
|
|
148
|
+
hashValue(config.clientSecret), config.authority, scopes,
|
|
149
|
+
hashValue(endUserToken),
|
|
150
|
+
]);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function computeAppCacheKey(config, scopes) {
|
|
154
|
+
// Hash the secret rather than retaining it in the process-wide key. A
|
|
155
|
+
// changed secret creates a new entry, leaving the old one bounded and
|
|
156
|
+
// eligible for LRU eviction.
|
|
157
|
+
return JSON.stringify([
|
|
158
|
+
config.authType?.toLowerCase(), config.clientId,
|
|
159
|
+
hashValue(config.clientSecret), config.authority, scopes,
|
|
160
|
+
]);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function createClient(config) {
|
|
164
|
+
const networkClient = buildNetworkClient(config.proxy);
|
|
165
|
+
const msalConfig = {
|
|
166
|
+
auth: {
|
|
167
|
+
clientId: config.clientId ?? throwErr("OBO configuration requires clientId."),
|
|
168
|
+
authority: config.authority ?? throwErr("OBO configuration requires authority."),
|
|
169
|
+
clientSecret: config.clientSecret ?? throwErr("OBO configuration requires clientSecret."),
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
if (networkClient) {
|
|
173
|
+
msalConfig.system = { networkClient };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return new msal.ConfidentialClientApplication(msalConfig);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
//---------------------------------------------------------------------------
|
|
180
|
+
// calculateExpiry()
|
|
181
|
+
//---------------------------------------------------------------------------
|
|
182
|
+
function calculateExpiry(result, clockSkewMs) {
|
|
183
|
+
if (result?.expiresOn instanceof Date) {
|
|
184
|
+
return result.expiresOn.getTime() - clockSkewMs;
|
|
185
|
+
}
|
|
186
|
+
throwErr('Azure token response did not include an expiration time.');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
//---------------------------------------------------------------------------
|
|
190
|
+
// getOnBehalfOfToken()
|
|
191
|
+
//---------------------------------------------------------------------------
|
|
192
|
+
async function getOnBehalfOfToken({ endUserToken, oboConfig, cacheOptions }) {
|
|
193
|
+
if (!endUserToken) {
|
|
194
|
+
throwErr('An end user access token must be supplied for OBO exchange.');
|
|
195
|
+
}
|
|
196
|
+
if (!oboConfig) {
|
|
197
|
+
throwErr('OBO configuration is required.');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const scopes = normalizeScopes(oboConfig.scopes);
|
|
201
|
+
const cacheEnabled = cacheOptions?.enabled !== false;
|
|
202
|
+
const clockSkewMs = cacheOptions?.clockSkewMs ?? OBO_DEFAULT_CLOCK_SKEW_MS;
|
|
203
|
+
// This process-wide bounded cache is authoritative. Do not use MSAL's
|
|
204
|
+
// per-client cache because clients are intentionally short-lived so they do
|
|
205
|
+
// not retain credentials in a long-lived object graph.
|
|
206
|
+
|
|
207
|
+
const cacheKey = computeOboCacheKey(oboConfig, scopes, endUserToken);
|
|
208
|
+
const result = await oboTokenCache.get(cacheKey, async () => {
|
|
209
|
+
const authResponse = await createClient(oboConfig).acquireTokenOnBehalfOf({
|
|
210
|
+
oboAssertion: endUserToken,
|
|
211
|
+
scopes,
|
|
212
|
+
skipCache: true
|
|
213
|
+
});
|
|
214
|
+
if (!authResponse?.accessToken) {
|
|
215
|
+
throwErr('Failed to acquire on-behalf-of access token from Azure.');
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
value: authResponse.accessToken,
|
|
219
|
+
validUntil: calculateExpiry(authResponse, clockSkewMs),
|
|
220
|
+
};
|
|
221
|
+
}, { enabled: cacheEnabled });
|
|
222
|
+
return {
|
|
223
|
+
accessToken: result.value,
|
|
224
|
+
expiresOn: new Date(result.validUntil + clockSkewMs),
|
|
225
|
+
isNewToken: !result.isCacheHit,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
module.exports = {
|
|
230
|
+
getTokenResult,
|
|
231
|
+
getOnBehalfOfToken
|
|
232
|
+
};
|
|
@@ -29,18 +29,59 @@ const identitydataplane = require("oci-identitydataplane");
|
|
|
29
29
|
const common = require("oci-common");
|
|
30
30
|
const { generateKeyPair } = require('crypto');
|
|
31
31
|
const fs = require('fs');
|
|
32
|
+
const https = require('https');
|
|
33
|
+
const { URL } = require('url');
|
|
34
|
+
const { TokenCache, hashValue } = require('../cache.js');
|
|
35
|
+
const TOKEN_REQUEST_TIMEOUT_MS = 30000;
|
|
36
|
+
const APP_TOKEN_CACHE_CLOCK_SKEW_MS = 60 * 1000;
|
|
37
|
+
const APP_TOKEN_CACHE_MAX_ENTRIES = 100;
|
|
38
|
+
// Process-wide, bounded LRU cache for OCI client-credentials tokens. Its key
|
|
39
|
+
// includes the authentication inputs that can change a token. Other OCI
|
|
40
|
+
// authentication modes retain their pre-cache behavior.
|
|
41
|
+
const appTokenCache = new TokenCache(APP_TOKEN_CACHE_MAX_ENTRIES);
|
|
32
42
|
|
|
33
|
-
|
|
43
|
+
// Internal Deep Security API. The driver's ordinary token-authentication hook
|
|
44
|
+
// below formats this result as a string or token/private-key pair.
|
|
45
|
+
async function getTokenResult(params) {
|
|
46
|
+
if (typeof params?.authType !== 'string') {
|
|
47
|
+
throwErr('OCI authentication type is required.');
|
|
48
|
+
}
|
|
49
|
+
const authType = params.authType.toLowerCase();
|
|
50
|
+
let result;
|
|
51
|
+
if (authType === 'clientcredentials') {
|
|
52
|
+
const cacheKey = computeAppCacheKey(params);
|
|
53
|
+
result = await appTokenCache.get(cacheKey, () => acquireToken(params));
|
|
54
|
+
} else {
|
|
55
|
+
result = { value: (await acquireToken(params)).value, isCacheHit: false };
|
|
56
|
+
}
|
|
57
|
+
if (typeof result.value === 'string') {
|
|
58
|
+
return { accessToken: result.value, isNewToken: !result.isCacheHit };
|
|
59
|
+
}
|
|
60
|
+
return { ...result.value, isNewToken: !result.isCacheHit };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function acquireToken(params) {
|
|
64
|
+
let result;
|
|
34
65
|
switch (params.authType.toLowerCase()) {
|
|
35
66
|
case 'configfilebasedauthentication':
|
|
36
|
-
|
|
67
|
+
result = await configFileBasedAuthentication(params);
|
|
68
|
+
break;
|
|
37
69
|
case 'simpleauthentication':
|
|
38
|
-
|
|
70
|
+
result = await simpleAuthentication(params);
|
|
71
|
+
break;
|
|
39
72
|
case 'instanceprincipal':
|
|
40
|
-
|
|
73
|
+
result = await instancePrincipalAuthentication(params);
|
|
74
|
+
break;
|
|
75
|
+
case 'clientcredentials':
|
|
76
|
+
result = await clientCredentialsAuthentication(params);
|
|
77
|
+
break;
|
|
78
|
+
case 'resourceprincipal':
|
|
79
|
+
result = await resourcePrincipalAuthentication(params);
|
|
80
|
+
break;
|
|
41
81
|
default:
|
|
42
82
|
throwErr(`Invalid authentication type ${params.authType} in extensionOci plugins.`);
|
|
43
83
|
}
|
|
84
|
+
return { value: result.value, validUntil: calculateValidUntil(result) };
|
|
44
85
|
}
|
|
45
86
|
|
|
46
87
|
//---------------------------------------------------------------------------
|
|
@@ -81,9 +122,16 @@ async function generateAccessToken(provider, scope) {
|
|
|
81
122
|
const generateScopedAccessTokenResponse =
|
|
82
123
|
await client.generateScopedAccessToken(generateScopedAccessTokenRequest);
|
|
83
124
|
|
|
125
|
+
const securityToken = generateScopedAccessTokenResponse.securityToken;
|
|
126
|
+
if (typeof securityToken?.token !== 'string' || !securityToken.token.trim()) {
|
|
127
|
+
throwErr('OCI scoped access token response is missing a token.');
|
|
128
|
+
}
|
|
84
129
|
return {
|
|
85
|
-
|
|
86
|
-
|
|
130
|
+
value: {
|
|
131
|
+
token: securityToken.token,
|
|
132
|
+
privateKey: keyPair.privateKey
|
|
133
|
+
},
|
|
134
|
+
expiresOn: securityToken.expirationTime
|
|
87
135
|
};
|
|
88
136
|
}
|
|
89
137
|
|
|
@@ -153,21 +201,19 @@ async function simpleAuthentication(accessTokenConfig) {
|
|
|
153
201
|
throwErr("Token based authentication config parameter user is missing for simpleauthentication in extensionOci plugins.");
|
|
154
202
|
const fingerprint = accessTokenConfig.fingerprint ??
|
|
155
203
|
throwErr("Token based authentication config parameter fingerprint is missing for simpleauthentication in extensionOci plugins.");
|
|
156
|
-
const passphrase = accessTokenConfig.passphrase
|
|
204
|
+
const passphrase = accessTokenConfig.passphrase ?? null; // optional
|
|
157
205
|
const privateKeyLocation = accessTokenConfig.privateKeyLocation ??
|
|
158
206
|
throwErr("Token based authentication config parameter privateKeyLocation is missing for simpleauthentication in extensionOci plugins.");
|
|
159
207
|
const privateKey = fs.readFileSync(privateKeyLocation, 'utf-8'); // ~/.oci/oci_api_key.pem
|
|
160
208
|
const regionId = accessTokenConfig.regionId ?? // ex : us-ashburn-1
|
|
161
209
|
throwErr("Token based authentication config parameter regionId is missing for simpleauthentication in extensionOci plugins.");
|
|
162
210
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
}
|
|
170
|
-
});
|
|
211
|
+
const region = common.Region.values().find(
|
|
212
|
+
(item) => item.regionId === regionId
|
|
213
|
+
);
|
|
214
|
+
if (!region) {
|
|
215
|
+
throwErr(`Invalid OCI regionId ${regionId}.`);
|
|
216
|
+
}
|
|
171
217
|
const provider = new common.SimpleAuthenticationDetailsProvider(
|
|
172
218
|
tenancy,
|
|
173
219
|
user,
|
|
@@ -194,6 +240,157 @@ async function instancePrincipalAuthentication(accessTokenConfig) {
|
|
|
194
240
|
return await generateAccessToken(provider, accessTokenConfig.scope);
|
|
195
241
|
}
|
|
196
242
|
|
|
243
|
+
//---------------------------------------------------------------------------
|
|
244
|
+
// clientCredentialsAuthentication()
|
|
245
|
+
//
|
|
246
|
+
// Requests an access token from an OCI Identity Domain using the OAuth 2.0
|
|
247
|
+
// client-credentials grant.
|
|
248
|
+
//---------------------------------------------------------------------------
|
|
249
|
+
async function clientCredentialsAuthentication(config) {
|
|
250
|
+
const authority = config.authority ??
|
|
251
|
+
throwErr("Token based authentication config parameter authority is missing for clientcredentials in extensionOci plugins.");
|
|
252
|
+
const clientId = config.clientId ??
|
|
253
|
+
throwErr("Token based authentication config parameter clientId is missing for clientcredentials in extensionOci plugins.");
|
|
254
|
+
const clientSecret = config.clientSecret ??
|
|
255
|
+
throwErr("Token based authentication config parameter clientSecret is missing for clientcredentials in extensionOci plugins.");
|
|
256
|
+
if (config.scopes !== undefined && typeof config.scopes !== 'string') {
|
|
257
|
+
throwErr('OCI OAuth scopes must be a string.');
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const params = new URLSearchParams();
|
|
261
|
+
|
|
262
|
+
params.set("grant_type", "client_credentials");
|
|
263
|
+
if (config.scopes) {
|
|
264
|
+
// OAuth defines a singular form parameter; the driver configuration uses
|
|
265
|
+
// the same plural `scopes` spelling as the Azure token configuration.
|
|
266
|
+
params.set("scope", config.scopes);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const basicAuth = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
|
|
270
|
+
const requestBody = params.toString();
|
|
271
|
+
const response = await postForm(authority, {
|
|
272
|
+
'Authorization': `Basic ${basicAuth}`
|
|
273
|
+
}, requestBody, config.tokenRequestTimeoutMs);
|
|
274
|
+
|
|
275
|
+
let payload = null;
|
|
276
|
+
if (response && response.body) {
|
|
277
|
+
try {
|
|
278
|
+
payload = JSON.parse(response.body);
|
|
279
|
+
} catch {
|
|
280
|
+
payload = null;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const statusCode = Number(response?.statusCode ?? 0);
|
|
285
|
+
if (statusCode < 200 || statusCode >= 300) {
|
|
286
|
+
const statusText = response?.statusMessage || `HTTP ${statusCode}`;
|
|
287
|
+
const errorMessage =
|
|
288
|
+
payload?.error_description ||
|
|
289
|
+
payload?.error ||
|
|
290
|
+
statusText;
|
|
291
|
+
throwErr(`OCI client credentials token request failed: ${errorMessage}`);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const accessToken = payload?.access_token;
|
|
295
|
+
if (!accessToken) {
|
|
296
|
+
throwErr("OCI client credentials token response is missing access_token.");
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return { value: accessToken, expiresIn: payload?.expires_in };
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function calculateValidUntil(result) {
|
|
303
|
+
let expiresAt;
|
|
304
|
+
if (result.expiresOn instanceof Date) {
|
|
305
|
+
expiresAt = result.expiresOn.getTime();
|
|
306
|
+
} else if (typeof result.expiresOn === 'number') {
|
|
307
|
+
expiresAt = result.expiresOn;
|
|
308
|
+
} else if (typeof result.expiresOn === 'string') {
|
|
309
|
+
expiresAt = Date.parse(result.expiresOn);
|
|
310
|
+
} else if (Number.isFinite(Number(result.expiresIn)) &&
|
|
311
|
+
Number(result.expiresIn) > 0) {
|
|
312
|
+
expiresAt = Date.now() + (Number(result.expiresIn) * 1000);
|
|
313
|
+
}
|
|
314
|
+
return Number.isFinite(expiresAt) ?
|
|
315
|
+
expiresAt - APP_TOKEN_CACHE_CLOCK_SKEW_MS : undefined;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function computeAppCacheKey(config) {
|
|
319
|
+
// This function is called only for clientcredentials. Array positions are
|
|
320
|
+
// fixed, and JSON serialization makes the result unambiguous.
|
|
321
|
+
return JSON.stringify([
|
|
322
|
+
config.authType.toLowerCase(), config.authority, config.clientId,
|
|
323
|
+
hashValue(config.clientSecret), config.scopes,
|
|
324
|
+
]);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
//---------------------------------------------------------------------------
|
|
328
|
+
// postForm()
|
|
329
|
+
//
|
|
330
|
+
// Minimal HTTPS helper for posting x-www-form-urlencoded payloads.
|
|
331
|
+
//---------------------------------------------------------------------------
|
|
332
|
+
function postForm(endpoint, extraHeaders, body, timeoutMs = TOKEN_REQUEST_TIMEOUT_MS) {
|
|
333
|
+
return new Promise((resolve, reject) => {
|
|
334
|
+
try {
|
|
335
|
+
const url = new URL(endpoint);
|
|
336
|
+
if (url.protocol !== 'https:') {
|
|
337
|
+
throwErr('OCI token endpoint must use HTTPS.');
|
|
338
|
+
}
|
|
339
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs < 1) {
|
|
340
|
+
throwErr('OCI token request timeout must be a positive integer.');
|
|
341
|
+
}
|
|
342
|
+
const headers = Object.assign({
|
|
343
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
344
|
+
'Content-Length': Buffer.byteLength(body)
|
|
345
|
+
}, extraHeaders);
|
|
346
|
+
|
|
347
|
+
const requestOptions = {
|
|
348
|
+
method: 'POST',
|
|
349
|
+
protocol: url.protocol,
|
|
350
|
+
hostname: url.hostname,
|
|
351
|
+
port: url.port || (url.protocol === 'https:' ? 443 : undefined),
|
|
352
|
+
path: `${url.pathname}${url.search}`,
|
|
353
|
+
headers
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
const req = https.request(requestOptions, (res) => {
|
|
357
|
+
let responseBody = '';
|
|
358
|
+
res.setEncoding('utf8');
|
|
359
|
+
res.on('data', chunk => {
|
|
360
|
+
responseBody += chunk;
|
|
361
|
+
});
|
|
362
|
+
res.on('end', () => {
|
|
363
|
+
resolve({
|
|
364
|
+
statusCode: res.statusCode,
|
|
365
|
+
statusMessage: res.statusMessage,
|
|
366
|
+
body: responseBody
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
req.on('error', reject);
|
|
372
|
+
req.setTimeout(timeoutMs, () => {
|
|
373
|
+
req.destroy(new Error('OCI token request timed out.'));
|
|
374
|
+
});
|
|
375
|
+
req.write(body);
|
|
376
|
+
req.end();
|
|
377
|
+
} catch (err) {
|
|
378
|
+
reject(err);
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// resourcePrincipalAuthentication()
|
|
384
|
+
//
|
|
385
|
+
// Authentication in an OCI resource-principal-enabled service. Credentials
|
|
386
|
+
// are supplied by the OCI runtime, so no API key material is needed.
|
|
387
|
+
//---------------------------------------------------------------------------
|
|
388
|
+
async function resourcePrincipalAuthentication(accessTokenConfig) {
|
|
389
|
+
const provider = common.ResourcePrincipalAuthenticationDetailsProvider.builder();
|
|
390
|
+
|
|
391
|
+
return await generateAccessToken(provider, accessTokenConfig.scope);
|
|
392
|
+
}
|
|
393
|
+
|
|
197
394
|
//---------------------------------------------------------------------------
|
|
198
395
|
// hookFn()
|
|
199
396
|
// hookFn will get registerd to driver while loading plugins.
|
|
@@ -201,9 +398,17 @@ async function instancePrincipalAuthentication(accessTokenConfig) {
|
|
|
201
398
|
function hookFn(options) {
|
|
202
399
|
if (options.tokenAuthConfigOci) {
|
|
203
400
|
options.accessToken = async function callbackFn(refresh, config) {
|
|
204
|
-
|
|
401
|
+
const result = await getTokenResult(config);
|
|
402
|
+
if (result.privateKey !== undefined) {
|
|
403
|
+
return { token: result.token, privateKey: result.privateKey };
|
|
404
|
+
}
|
|
405
|
+
return result.accessToken;
|
|
205
406
|
};
|
|
206
407
|
options.accessTokenConfig = options.tokenAuthConfigOci;
|
|
207
408
|
}
|
|
208
409
|
}
|
|
209
410
|
oracledb.registerProcessConfigurationHook(hookFn);
|
|
411
|
+
|
|
412
|
+
module.exports = {
|
|
413
|
+
getTokenResult
|
|
414
|
+
};
|