watson-developer-cloud
Version:
Client library to use the IBM Watson Services and AlchemyAPI
343 lines • 14.8 kB
JavaScript
"use strict";
/**
* Copyright 2014 IBM Corp. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
// new Buffer() is deprecated, replaced with Buffer.from() in node v4.5.0+ -
// `buffer-from` uses the new api when possible but falls back to the old one otherwise
var bufferFrom = require("buffer-from");
var extend = require("extend");
var request = require("request");
var semver = require("semver");
var vcapServices = require("vcap_services");
var v1_1 = require("../iam-token-manager/v1");
var helper_1 = require("./helper");
var requestwrapper_1 = require("./requestwrapper");
function hasCredentials(obj) {
return (obj &&
((obj.username && obj.password) ||
obj.api_key ||
obj.iam_access_token ||
obj.iam_apikey));
}
function hasBasicCredentials(obj) {
return obj && obj.username && obj.password && !usesBasicForIam(obj);
}
function hasIamCredentials(obj) {
return obj && (obj.iam_apikey || obj.iam_access_token);
}
// returns true if the user provides basic auth creds with the intention
// of using IAM auth
function usesBasicForIam(obj) {
return obj.username === 'apikey' && !obj.password.startsWith('icp-');
}
// returns true if the string has a curly bracket or quote as the first or last character
// these are common user-issues that we should handle before they get a network error
function badCharAtAnEnd(value) {
return value.startsWith('{') || value.startsWith('"') || value.endsWith('}') || value.endsWith('"');
}
// checks credentials for common user mistakes of copying {, }, or " characters from the documentation
function checkCredentials(obj) {
var errorMessage = '';
var credsToCheck = ['url', 'username', 'password', 'iam_apikey'];
credsToCheck.forEach(function (cred) {
if (obj[cred] && badCharAtAnEnd(obj[cred])) {
errorMessage += "The " + cred + " shouldn't start or end with curly brackets or quotes. Be sure to remove any {, }, or \"";
}
});
if (errorMessage.length) {
errorMessage += 'Revise these credentials - they should not start or end with curly brackets or quotes.';
return errorMessage;
}
else {
return null;
}
}
var BaseService = /** @class */ (function () {
/**
* Internal base class that other services inherit from
* @param {UserOptions} options
* @param {string} [options.username] - required unless use_unauthenticated is set
* @param {string} [options.password] - required unless use_unauthenticated is set
* @param {boolean} [options.use_unauthenticated] - skip credential requirement
* @param {HeaderOptions} [options.headers]
* @param {boolean} [options.headers.X-Watson-Learning-Opt-Out=false] - opt-out of data collection
* @param {string} [options.url] - override default service base url
* @private
* @abstract
* @constructor
* @throws {Error}
* @returns {BaseService}
*/
function BaseService(userOptions) {
if (!(this instanceof BaseService)) {
// it might be better to just create a new instance and return that..
// but that can't be done here, it has to be done in each individual service.
// So this is still a good failsafe even in that case.
throw new Error('the "new" keyword is required to create Watson service instances');
}
var isNodeFour = semver.satisfies(process.version, '4.x');
if (isNodeFour) {
console.warn('WARNING: Support for Node v4.x is deprecated and will no longer be tested. Support will be officially dropped in the next major version.');
}
var options = extend({}, userOptions);
var _options = this.initCredentials(options);
// If url is not specified, visual recognition requires gateway-a for CF instances
// https://github.ibm.com/Watson/developer-experience/issues/4589
if (_options && this.name === 'watson_vision_combined' && !_options.url && _options.api_key && !_options.iam_apikey) {
_options.url = 'https://gateway-a.watsonplatform.net/visual-recognition/api';
}
if (options.url) {
_options.url = helper_1.stripTrailingSlash(options.url);
}
var serviceClass = this.constructor;
this._options = extend({ qs: {}, url: serviceClass.URL }, this.serviceDefaults, options, _options);
if (hasIamCredentials(_options)) {
this.tokenManager = new v1_1.IamTokenManagerV1({
iamApikey: _options.iam_apikey,
iamAccessToken: _options.iam_access_token,
iamUrl: _options.iam_url
});
}
else if (usesBasicForIam(_options)) {
this.tokenManager = new v1_1.IamTokenManagerV1({
iamApikey: _options.password,
iamUrl: _options.iam_url
});
}
else {
this.tokenManager = null;
}
// rejectUnauthorized should only be false if disable_ssl_verification is true
// used to disable ssl checking for icp
this._options.rejectUnauthorized = !options.disable_ssl_verification;
}
/**
* Retrieve this service's credentials - useful for passing to the authorization service
*
* Only returns a URL when token auth is used.
*
* @returns {Credentials}
*/
BaseService.prototype.getCredentials = function () {
var credentials = {};
if (this._options.username) {
credentials.username = this._options.username;
}
if (this._options.password) {
credentials.password = this._options.password;
}
if (this._options.api_key) {
credentials.api_key = this._options.api_key;
}
if (this._options.url) {
credentials.url = this._options.url;
}
if (this._options.iam_access_token) {
credentials.iam_access_token = this._options.iam_access_token;
}
if (this._options.iam_apikey) {
credentials.iam_apikey = this._options.iam_apikey;
}
if (this._options.iam_url) {
credentials.iam_url = this._options.iam_url;
}
return credentials;
};
/**
* Set an IAM access token to use when authenticating with the service.
* The access token should be valid and not yet expired.
*
* By using this method, you accept responsibility for managing the
* access token yourself. You must set a new access token before this
* one expires. Failing to do so will result in authentication errors
* after this token expires.
*
* @param {string} iam_access_token - A valid, non-expired IAM access token
* @returns {void}
*/
BaseService.prototype.setAccessToken = function (iam_access_token) {
if (this.tokenManager) {
this.tokenManager.setAccessToken(iam_access_token);
}
else {
this.tokenManager = new v1_1.IamTokenManagerV1({
iamAccessToken: iam_access_token
});
}
};
/**
* Guarantee that the next request you make will be IAM authenticated. This
* performs any requests necessary to get a valid IAM token so that if your
* next request involves a streaming operation, it will not be interrupted.
*
* @param {Function} callback - callback function to return flow of execution
*
* @returns {void}
*/
BaseService.prototype.preAuthenticate = function (callback) {
if (Boolean(this.tokenManager)) {
return this.tokenManager.getToken(function (err, token) {
if (err) {
callback(err);
}
callback(null);
});
}
else {
callback(null);
}
};
/**
* Wrapper around `sendRequest` that determines whether or not IAM tokens
* are being used to authenticate the request. If so, the token is
* retrieved by the token manager.
*
* @param {Object} parameters - service request options passed in by user
* @param {Function} callback - callback function to pass the response back to
* @returns {ReadableStream|undefined}
*/
BaseService.prototype.createRequest = function (parameters, callback) {
if (Boolean(this.tokenManager)) {
return this.tokenManager.getToken(function (err, accessToken) {
if (err) {
return callback(err);
}
parameters.defaultOptions.headers.Authorization =
"Bearer " + accessToken;
return requestwrapper_1.sendRequest(parameters, callback);
});
}
else {
return requestwrapper_1.sendRequest(parameters, callback);
}
};
/**
* @private
* @param {UserOptions} options
* @returns {BaseServiceOptions}
*/
BaseService.prototype.initCredentials = function (options) {
var _options = {};
if (options.token) {
options.headers = options.headers || {};
options.headers['X-Watson-Authorization-Token'] = options.token;
_options = extend(_options, options);
return _options;
}
if (options.api_key || options.apikey) {
_options.api_key = options.api_key || options.apikey;
}
_options.jar = request.jar();
// Get credentials from environment properties or Bluemix,
// but prefer credentials provided programatically
_options = extend({}, this.getCredentialsFromBluemix(this.name), this.getCredentialsFromEnvironment(this.name), options, _options);
if (!_options.use_unauthenticated) {
if (!hasCredentials(_options)) {
var errorMessage = 'Insufficient credentials provided in ' +
'constructor argument. Refer to the documentation for the ' +
'required parameters. Common examples are username/password, ' +
'api_key, and iam_access_token.';
throw new Error(errorMessage);
}
if (!hasIamCredentials(_options) && !usesBasicForIam(_options)) {
if (hasBasicCredentials(_options)) {
// Calculate and add Authorization header to base options
var encodedCredentials = bufferFrom(_options.username + ":" + _options.password).toString('base64');
var authHeader = { Authorization: "Basic " + encodedCredentials };
_options.headers = extend(authHeader, _options.headers);
}
else {
_options.qs = extend({ api_key: _options.api_key }, _options.qs);
}
}
}
// check credentials for common user errors
var credentialProblems = checkCredentials(_options);
if (credentialProblems) {
throw new Error(credentialProblems);
}
return _options;
};
/**
* Pulls credentials from env properties
*
* Property checked is uppercase service.name suffixed by _USERNAME and _PASSWORD
*
* For example, if service.name is speech_to_text,
* env properties are SPEECH_TO_TEXT_USERNAME and SPEECH_TO_TEXT_PASSWORD
*
* @private
* @param {string} name - the service snake case name
* @returns {Credentials}
*/
BaseService.prototype.getCredentialsFromEnvironment = function (name) {
if (name === 'watson_vision_combined') {
return this.getCredentialsFromEnvironment('visual_recognition');
}
// Case handling for assistant - should look for assistant env variables before conversation
if (name === 'conversation' && (process.env["ASSISTANT_USERNAME"] || process.env["ASSISTANT_IAM_APIKEY"])) {
return this.getCredentialsFromEnvironment('assistant');
}
var _name = name.toUpperCase();
// https://github.com/watson-developer-cloud/node-sdk/issues/605
var nameWithUnderscore = _name.replace(/-/g, '_');
var username = process.env[_name + "_USERNAME"] || process.env[nameWithUnderscore + "_USERNAME"];
var password = process.env[_name + "_PASSWORD"] || process.env[nameWithUnderscore + "_PASSWORD"];
var apiKey = process.env[_name + "_API_KEY"] || process.env[nameWithUnderscore + "_API_KEY"];
var url = process.env[_name + "_URL"] || process.env[nameWithUnderscore + "_URL"];
var iamAccessToken = process.env[_name + "_IAM_ACCESS_TOKEN"] || process.env[nameWithUnderscore + "_IAM_ACCESS_TOKEN"];
var iamApiKey = process.env[_name + "_IAM_APIKEY"] || process.env[nameWithUnderscore + "_IAM_APIKEY"];
var iamUrl = process.env[_name + "_IAM_URL"] || process.env[nameWithUnderscore + "_IAM_URL"];
return {
username: username,
password: password,
api_key: apiKey,
url: url,
iam_access_token: iamAccessToken,
iam_apikey: iamApiKey,
iam_url: iamUrl
};
};
/**
* Pulls credentials from VCAP_SERVICES env property that bluemix sets
* @param {string} vcap_services_name
* @private
* @returns {Credentials}
*/
BaseService.prototype.getCredentialsFromBluemix = function (vcapServicesName) {
var credentials;
var temp;
if (this.name === 'visual_recognition') {
temp = vcapServices.getCredentials('watson_vision_combined');
}
if (this.name === 'assistant') {
temp = vcapServices.getCredentials('conversation');
}
else {
temp = vcapServices.getCredentials(vcapServicesName);
}
// convert an iam apikey to use the identifier iam_apikey
if (temp.apikey && temp.iam_apikey_name) {
temp.iam_apikey = temp.apikey;
delete temp.apikey;
}
credentials = temp;
return credentials;
};
return BaseService;
}());
exports.BaseService = BaseService;
//# sourceMappingURL=base_service.js.map