Skip to content

Commit aee42a4

Browse files
committed
Add IAM Token based authentication
1 parent 1797d47 commit aee42a4

21 files changed

+3478
-113
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88
stack](https://oracle.github.io/node-oracledb/doc/api.html#properrstack). PR#1467
99
(Slawomir Osoba).
1010

11+
- Added support for [token based
12+
authentication](https://oracle.github.io/node-oracledb/doc/api.html#tokenbasedauth)
13+
when establishing pool based connections and standalone connections.
14+
1115
- Added code to keep the method name in internally bound functions.
1216
PR #1466 (Slawomir Osoba).
1317

binding.gyp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"src/njsAqMessage.c",
99
"src/njsAqQueue.c",
1010
"src/njsBaton.c",
11+
"src/njsTokenCallback.c",
1112
"src/njsConnection.c",
1213
"src/njsDbObject.c",
1314
"src/njsErrors.c",

doc/api.md

Lines changed: 443 additions & 106 deletions
Large diffs are not rendered by default.

examples/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,5 +109,7 @@ File Name | Description
109109
[`sessiontagging1.js`](sessiontagging1.js) | Simple pooled connection tagging for setting session state
110110
[`sessiontagging2.js`](sessiontagging2.js) | More complex example of pooled connection tagging for setting session state
111111
[`soda1.js`](soda1.js) | Basic Simple Oracle Document Access (SODA) example
112+
[`tokenbasedauth.js`](tokenbasedauth.js) | Shows standalone connection using token based authentication
113+
[`tokenbasedauthpool.js`](tokenbasedauthpool.js) | Shows connection pooling using token based authentication
112114
[`version.js`](version.js) | Shows the node-oracledb version attributes
113115
[`webapp.js`](webapp.js) | A simple web application using a connection pool

examples/tokenbasedauth.js

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/* Copyright (c) 2022, Oracle and/or its affiliates. */
2+
3+
/******************************************************************************
4+
*
5+
* You may not use the identified files except in compliance with the Apache
6+
* License, Version 2.0 (the "License.")
7+
*
8+
* You may obtain a copy of the License at
9+
* http://www.apache.org/licenses/LICENSE-2.0.
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
*
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*
18+
* NAME
19+
* tokenbasedauth.js
20+
*
21+
* DESCRIPTION
22+
* This script shows standalone connection using token based authentication
23+
* to Oracle Autonomous Database from Oracle Compute Infrastructure.
24+
*
25+
* For more information refer to
26+
* https://oracle.github.io/node-oracledb/doc/api.html#tokenbasedauth
27+
*
28+
* PREREQUISITES
29+
* - node-oracledb 5.4 or later.
30+
*
31+
* - Oracle Client libraries 19.14 (or later), or 21.5 (or later).
32+
*
33+
* - The Oracle Cloud Infrastructure command line interface (OCI-CLI). The
34+
* command line interface (CLI) is a tool that enables you to work with
35+
* Oracle Cloud Infrastructure objects and services at a command line, see
36+
* https://docs.oracle.com/en-us/iaas/Content/API/Concepts/cliconcepts.htm
37+
*
38+
* - Set these environment variables (see the code explanation):
39+
* NODE_ORACLEDB_ACCESS_TOKEN_LOC
40+
* NODE_ORACLEDB_CONNECTIONSTRING
41+
*
42+
*****************************************************************************/
43+
44+
const fs = require('fs');
45+
const oracledb = require('oracledb');
46+
const { execSync } = require('child_process');
47+
48+
// Execute the OCI-CLI command to generate a token.
49+
// This should create two files "token" and "oci_db_key.pem".
50+
// On Linux the default file location is "~/.oci/db-token". You should set
51+
// NODE_ORACLEDB_ACCESS_TOKEN_LOC to this directory, or to the directory where
52+
// you move the files.
53+
try {
54+
const cmdResult = execSync('oci iam db-token get', { encoding: 'utf-8' });
55+
console.log(cmdResult);
56+
} catch (err) {
57+
console.log(err);
58+
}
59+
60+
// User defined function for reading token and private key values generated by
61+
// the OCI-CLI.
62+
function getToken() {
63+
const tokenPath = process.env.NODE_ORACLEDB_ACCESS_TOKEN_LOC + '/token';
64+
const privateKeyPath = process.env.NODE_ORACLEDB_ACCESS_TOKEN_LOC +
65+
'/oci_db_key.pem';
66+
67+
let token = '';
68+
let privateKey = '';
69+
try {
70+
// Read token file
71+
token = fs.readFileSync(tokenPath, 'utf8');
72+
// Read private key file
73+
const privateKeyFileContents = fs.readFileSync(privateKeyPath, 'utf-8');
74+
privateKeyFileContents.split(/\r?\n/).forEach(line => {
75+
if (line != '-----BEGIN PRIVATE KEY-----' &&
76+
line != '-----END PRIVATE KEY-----')
77+
privateKey = privateKey.concat(line);
78+
});
79+
} catch (err) {
80+
console.error(err);
81+
}
82+
83+
const tokenBasedAuthData = {
84+
token : token,
85+
privateKey : privateKey
86+
};
87+
return tokenBasedAuthData;
88+
}
89+
90+
async function run() {
91+
let connection;
92+
// Get token and private key.
93+
let accessTokenObj = getToken();
94+
95+
// Configuration for token based authentication:
96+
// accessToken: The token values
97+
// externalAuth: Must be set to true for token based authentication.
98+
// connectString: The NODE_ORACLEDB_CONNECTIONSTRING environment variable
99+
// set to the Oracle Net alias or connect descriptor of your
100+
// Oracle Autonomous Database.
101+
const config = {
102+
accessToken : accessTokenObj,
103+
externalAuth : true,
104+
connectString : process.env.NODE_ORACLEDB_CONNECTIONSTRING
105+
};
106+
107+
try {
108+
connection = await oracledb.getConnection(config);
109+
const sql = `SELECT TO_CHAR(current_date, 'DD-Mon-YYYY HH24:MI') AS D
110+
FROM DUAL`;
111+
const result = await connection.execute(sql);
112+
console.log("Result is:\n", result);
113+
} catch (err) {
114+
console.error(err);
115+
} finally {
116+
try {
117+
if (connection)
118+
await connection.close();
119+
} catch (err) {
120+
console.error(err.message);
121+
}
122+
}
123+
}
124+
125+
run();

examples/tokenbasedauthpool.js

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
/* Copyright (c) 2022, Oracle and/or its affiliates. */
2+
3+
/******************************************************************************
4+
*
5+
* You may not use the identified files except in compliance with the Apache
6+
* License, Version 2.0 (the "License.")
7+
*
8+
* You may obtain a copy of the License at
9+
* http://www.apache.org/licenses/LICENSE-2.0.
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
*
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*
18+
* NAME
19+
* tokenbasedauthpool.js
20+
*
21+
* DESCRIPTION
22+
* This script shows connection pooling with token based authentication to
23+
* Oracle Autonomous Database from Oracle Compute Infrastructure. It shows
24+
* how to create a connection pool and update expired tokens.
25+
*
26+
* For more information refer to
27+
* https://oracle.github.io/node-oracledb/doc/api.html#tokenbasedauth
28+
*
29+
* PREREQUISITES
30+
* - node-oracledb 5.4 or later.
31+
*
32+
* - Oracle Client libraries 19.14 (or later), or 21.5 (or later).
33+
*
34+
* - The Oracle Cloud Infrastructure command line interface (OCI-CLI). The
35+
* command line interface (CLI) is a tool that enables you to work with
36+
* Oracle Cloud Infrastructure objects and services at a command line, see
37+
* https://docs.oracle.com/en-us/iaas/Content/API/Concepts/cliconcepts.htm
38+
*
39+
* - Set these environment variables (see the code explanation):
40+
* NODE_ORACLEDB_ACCESS_TOKEN_LOC
41+
* NODE_ORACLEDB_CONNECTIONSTRING
42+
*
43+
*****************************************************************************/
44+
45+
const fs = require('fs');
46+
const oracledb = require('oracledb');
47+
const { execSync } = require('child_process');
48+
49+
// Execute the OCI-CLI command to generate a token.
50+
// This should create two files "token" and "oci_db_key.pem"
51+
// On Linux the default file location is "~/.oci/db-token". You should set
52+
// NODE_ORACLEDB_ACCESS_TOKEN_LOC to this directory, or to the directory where
53+
// you move the files.
54+
try {
55+
const cmdResult = execSync('oci iam db-token get', { encoding: 'utf-8' });
56+
console.log(cmdResult);
57+
} catch (err) {
58+
console.log(err);
59+
}
60+
61+
// User defined function for reading token and private key values generated by
62+
// the OCI-CLI.
63+
function getToken() {
64+
const tokenPath = process.env.NODE_ORACLEDB_ACCESS_TOKEN_LOC + '/token';
65+
const privateKeyPath = process.env.NODE_ORACLEDB_ACCESS_TOKEN_LOC +
66+
'/oci_db_key.pem';
67+
68+
let token = '';
69+
let privateKey = '';
70+
try {
71+
// Read token file
72+
token = fs.readFileSync(tokenPath, 'utf8');
73+
// Read private key file
74+
const privateKeyFileContents = fs.readFileSync(privateKeyPath, 'utf-8');
75+
privateKeyFileContents.split(/\r?\n/).forEach(line => {
76+
if (line != '-----BEGIN PRIVATE KEY-----' &&
77+
line != '-----END PRIVATE KEY-----')
78+
privateKey = privateKey.concat(line);
79+
});
80+
} catch (err) {
81+
console.error(err);
82+
}
83+
84+
const tokenBasedAuthData = {
85+
token : token,
86+
privateKey : privateKey
87+
};
88+
return tokenBasedAuthData;
89+
}
90+
91+
// User defined callback function which is invoked by node-oracledb when a
92+
// token expires and the pool needs to create new connections
93+
function tokenCallback() {
94+
return getToken();
95+
}
96+
97+
async function run() {
98+
// Get token and private key for initial pool creation.
99+
const accessTokenData = getToken();
100+
101+
// Configuration for token based authentication:
102+
// accessToken: The initial token values
103+
// accessTokenCallback: A callback function to provide a refreshed token
104+
// externalAuth: Must be set to true for token based authentication
105+
// homogeneous: Must be set to true for token based authentication
106+
// connectString: The NODE_ORACLEDB_CONNECTIONSTRING environment
107+
// variable set to the Oracle Net alias or connect
108+
// descriptor of your Oracle Autonomous Database
109+
const config = {
110+
accessToken : accessTokenData,
111+
accessTokenCallback : tokenCallback,
112+
externalAuth : true,
113+
homogeneous : true,
114+
connectString : process.env.NODE_ORACLEDB_CONNECTIONSTRING,
115+
};
116+
117+
try {
118+
119+
// Create pool using token based authentication
120+
await oracledb.createPool(config);
121+
122+
// A real app would call createConnection() multiple times over a long
123+
// period of time. During this time the pool may grow. If the initial
124+
// token has expired, node-oracledb will automatically call the
125+
// accessTokenCallback function allowing you to update the token.
126+
await createConnection();
127+
128+
} catch (err) {
129+
console.error(err);
130+
} finally {
131+
await closePoolAndExit();
132+
}
133+
}
134+
135+
async function createConnection() {
136+
let connection;
137+
try {
138+
// Get a connection from the default pool
139+
connection = await oracledb.getConnection();
140+
const sql = `SELECT TO_CHAR(current_date, 'DD-Mon-YYYY HH24:MI') AS D
141+
FROM DUAL`;
142+
const result = await connection.execute(sql);
143+
console.log("Result is:\n", result);
144+
} catch (err) {
145+
console.error(err);
146+
} finally {
147+
if (connection) {
148+
// Put the connection back in the pool
149+
await connection.close();
150+
}
151+
}
152+
}
153+
154+
async function closePoolAndExit() {
155+
console.log('\nTerminating');
156+
try {
157+
// Get the pool from the pool cache and close it
158+
await oracledb.getPool().close(0);
159+
process.exit(0);
160+
} catch (err) {
161+
console.error(err.message);
162+
process.exit(1);
163+
}
164+
}
165+
166+
process
167+
.once('SIGTERM', closePoolAndExit)
168+
.once('SIGINT', closePoolAndExit);
169+
170+
run();

lib/oracledb.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,39 @@ async function createPool(poolAttrs) {
199199
tempUsedPoolAliases[poolAlias] = true;
200200
}
201201

202+
if (adjustedPoolAttrs.accessToken !== undefined) {
203+
// token and privateKey must be set for token based
204+
// authentication
205+
if (adjustedPoolAttrs.accessToken.token === undefined ||
206+
adjustedPoolAttrs.accessToken.token === '' ||
207+
adjustedPoolAttrs.accessToken.privateKey === undefined ||
208+
adjustedPoolAttrs.accessToken.privateKey === '') {
209+
throw new Error(nodbUtil.getErrorMessage('NJS-084'));
210+
}
211+
212+
// cannot set username or password for token based authentication
213+
if (adjustedPoolAttrs.user !== undefined ||
214+
adjustedPoolAttrs.password !== undefined) {
215+
throw new Error(nodbUtil.getErrorMessage('NJS-084'));
216+
}
217+
218+
// homogeneous and externalAuth must be set to true for token based
219+
// authentication
220+
if (adjustedPoolAttrs.homogeneous === false ||
221+
adjustedPoolAttrs.externalAuth === false) {
222+
throw new Error(nodbUtil.getErrorMessage('NJS-085'));
223+
}
224+
225+
adjustedPoolAttrs.token = adjustedPoolAttrs.accessToken.token;
226+
adjustedPoolAttrs.privateKey =
227+
adjustedPoolAttrs.accessToken.privateKey;
228+
}
229+
230+
if (adjustedPoolAttrs.accessToken === undefined &&
231+
adjustedPoolAttrs.accessTokenCallback !== undefined) {
232+
throw new Error(nodbUtil.getErrorMessage('NJS-084'));
233+
}
234+
202235
try {
203236
const pool = await this._createPool(adjustedPoolAttrs);
204237

@@ -271,6 +304,32 @@ async function getConnection(a1) {
271304

272305
// otherwise, create a new standalone connection
273306
} else {
307+
if (connAttrs.accessToken !== undefined) {
308+
// token and privateKey must be set for token based
309+
// authentication
310+
if (connAttrs.accessToken.token === undefined ||
311+
connAttrs.accessToken.token === '' ||
312+
connAttrs.accessToken.privateKey === undefined ||
313+
connAttrs.accessToken.privateKey === '') {
314+
throw new Error(nodbUtil.getErrorMessage('NJS-084'));
315+
}
316+
317+
// cannot set username or password for token based authentication
318+
if (connAttrs.user !== undefined ||
319+
connAttrs.password !== undefined) {
320+
throw new Error(nodbUtil.getErrorMessage('NJS-084'));
321+
}
322+
323+
// externalAuth must be set to true for token based authentication
324+
if (connAttrs.externalAuth === false) {
325+
throw new Error(nodbUtil.getErrorMessage('NJS-086'));
326+
}
327+
328+
connAttrs.token = connAttrs.accessToken.token;
329+
connAttrs.privateKey =
330+
connAttrs.accessToken.privateKey;
331+
}
332+
274333
try {
275334
return await this._getConnection(connAttrs);
276335
} catch (err) {

0 commit comments

Comments
 (0)