oracle-nosqldb
Version:
Node.js driver for Oracle NoSQL Database
253 lines (224 loc) • 8.47 kB
JavaScript
/*-
* Copyright (c) 2018, 2024 Oracle and/or its affiliates. All rights reserved.
*
* Licensed under the Universal Permissive License v 1.0 as shown at
* https://oss.oracle.com/licenses/upl/
*/
'use strict';
const assert = require('assert');
const fs = require('fs');
const isPosInt32 = require('../../utils').isPosInt32;
const isPosInt32OrZero = require('../../utils').isPosInt32OrZero;
const promisified = require('../../utils').promisified;
const clearData = require('../../utils').clearData;
const ErrorCode = require('../../error_code');
const NoSQLArgumentError = require('../../error').NoSQLArgumentError;
const AuthError = require('../../error').NoSQLAuthorizationError;
const KVStoreTokenProvider = require('./token_provider');
class KVStoreFileCredentialsProvider {
constructor(credentialsFile) {
this._fileName = credentialsFile;
if (!this._fileName || typeof this._fileName !== 'string') {
throw new NoSQLArgumentError('Missing or invalid credentials file \
name');
}
}
_loadCredentials(callback) {
fs.readFile(this._fileName, 'utf8', (err, data) => {
if (err) {
return callback(AuthError.creds('Failed to load ' +
`kvstore credentials file ${this._fileName}`, err));
}
try {
const creds = JSON.parse(data, (key, value) => {
return key === 'password' ? Buffer.from(value) : value;
});
callback(null, creds);
} catch(err) {
callback(AuthError.creds(`Failed to parse kvstore \
credentials file ${this._fileName}`, err));
}
});
}
loadCredentials() {
return promisified(this, this._loadCredentials);
}
}
class KVStoreAuthorizationProvider {
constructor(opt, cfg) {
if (!opt || typeof opt !== 'object') {
throw new NoSQLArgumentError('Missing or invalid auth.kvstore',
cfg);
}
//Needed in case this provider is created outside NoSQLClient
//instance. Note that this is currently sufficient, because
//AuthConfig.defaults.kvstore has no nested properties. Otherwise the
//code below will need to change to use Config.inheritOpt().
opt.__proto__ = KVStoreAuthorizationProvider.configDefaults;
//init credentials provider if any
if (opt.credentials != null) {
if (opt.user != null || opt.password != null) {
throw new NoSQLArgumentError('May not specify \
auth.kvstore.credentials together with auth.kvstore.user or \
auth.kvstore.password', cfg);
}
if (typeof opt.credentials === 'string') {
this._credsProvider = new KVStoreFileCredentialsProvider(
opt.credentials);
} else if (typeof opt.credentials === 'function') {
this._credsProvider = { loadCredentials : opt.credentials };
}
else {
if (typeof opt.credentials !== 'object' ||
typeof opt.credentials.loadCredentials !== 'function') {
throw new NoSQLArgumentError(
'Invlaid value of auth.kvstore.credentials', cfg);
}
this._credsProvider = opt.credentials;
}
} else { //user & password supplied directly
if (typeof opt.user !== 'string' || !opt.user.length) {
throw new NoSQLArgumentError(
'Missing or invalid value of auth.kvstore.user');
}
if (!Buffer.isBuffer(opt.password) &&
(typeof opt.password !== 'string' || !opt.password.length)) {
throw new NoSQLArgumentError('Missing or invalid value of \
opt.kvstore.password');
}
this._creds = {
user: opt.user,
password: Buffer.from(opt.password)
};
}
if (!isPosInt32(opt.timeout)) {
throw new NoSQLArgumentError('Invalid auth.kvstore.timeout value',
cfg);
}
this._timeout = opt.timeout;
this._autoRenew = opt.autoRenew;
this._noRenewBeforeMs = opt.noRenewBeforeMs;
assert(isPosInt32OrZero(this._noRenewBeforeMs));
}
_setAuthResult(res) {
assert(res && res.token && res.expireAt);
this._auth = 'Bearer ' + res.token;
this._expireAt = res.expireAt;
}
_scheduleRenew() {
assert(this._auth && this._expireAt);
const currTime = Date.now();
const exp = this._expireAt - currTime;
//If it is 10 seconds before expiration, don't do further renew to
//avoid too many renew requests in the last few seconds.
if (exp <= this._noRenewBeforeMs) {
return;
}
if (this._renewTimer != null) {
clearTimeout(this._renewTimer);
}
this._renewTimer = setTimeout(async () => {
this._setAuthResult(
await this._tokenProvider.renew(this._auth));
this._scheduleRenew();
}, exp / 2);
}
_isValidCreds(creds) {
return creds && typeof creds.user === 'string' && creds.user.length &&
(Buffer.isBuffer(creds.password) ||
typeof creds.password === 'string') &&
creds.password.length;
}
async _retrieveToken() {
let creds;
try {
if (this._credsProvider) {
try {
creds = await this._credsProvider.loadCredentials();
} catch(err) {
throw AuthError.creds('Error retrieving credentials',
err);
}
if (!this._isValidCreds(creds)) {
throw AuthError.creds('Credentials provider returned \
invalid or missing credentials, check user and password');
}
} else {
creds = this._creds;
}
assert(this._tokenProvider != null);
this._setAuthResult(
await this._tokenProvider.login(creds.user, creds.password));
if (this._autoRenew) {
this._scheduleRenew();
}
} finally {
if (this._credsProvider) {
clearData(creds);
}
}
}
get credentialsProvider() {
return this._credsProvider;
}
onInit(cfg) {
this._tokenProvider = new KVStoreTokenProvider(this, cfg);
}
async getAuthorization(req) {
if (!this._auth || (req.lastError && req.lastError.errorCode ===
ErrorCode.RETRY_AUTHENTICATION)) {
await this._retrieveToken();
}
assert(this._auth);
return this._auth;
}
async close() {
if (this._auth) {
if (this._renewTimer != null) {
clearTimeout(this._renewTimer);
}
if (this._tokenProvider != null) {
try {
await this._tokenProvider.logout(this._auth);
//TODO: log the error
} catch(err) {} //eslint-disable-line no-empty
}
}
if (this._creds) {
clearData(this._creds);
}
}
static withCredentials(user, password) {
return new KVStoreAuthorizationProvider({
user,
password
});
}
static withCredentialsProvider(provider) {
if (!provider || (typeof provider !== 'object' &&
typeof provider !== 'function')) {
throw new NoSQLArgumentError(
'Missing or invalid credentials provider');
}
return new KVStoreAuthorizationProvider({
credentials: provider
});
}
static withCredentialsFile(file) {
if (!file || typeof file !== 'string') {
throw new NoSQLArgumentError(
`Missing or invalid credentials file path: ${file}`);
}
return new KVStoreAuthorizationProvider({
credentials: file
});
}
}
KVStoreAuthorizationProvider.configDefaults = Object.freeze({
timeout: 30000,
autoRenew: true,
//The below properties are not exposed to the user but different
//values are used in tests.
noRenewBeforeMs: 10000
});
module.exports = KVStoreAuthorizationProvider;