diff --git a/README.md b/README.md index 5dbbd8ecc..3348fad97 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ OpenSearch Node.js client - [Welcome!](#welcome) - [Example use](#example-use) - [Setup](#setup) - - [Sample code](#sample-code) +- [Sample code](#sample-code) - [Project Resources](#project-resources) - [Code of Conduct](#code-of-conduct) - [License](#license) @@ -44,156 +44,10 @@ Then require the client: const { Client } = require('@opensearch-project/opensearch'); ``` -### Sample code +## Sample code + +Please see the [USER_GUIDE](USER_GUIDE.md) for code snippets. -```javascript -'use strict'; - -var host = 'localhost'; -var protocol = 'https'; -var port = 9200; -var auth = 'admin:admin'; // For testing only. Don't store credentials in code. -var ca_certs_path = '/full/path/to/root-ca.pem'; - -// Optional client certificates if you don't want to use HTTP basic authentication. -// var client_cert_path = '/full/path/to/client.pem' -// var client_key_path = '/full/path/to/client-key.pem' - -// Create a client with SSL/TLS enabled. -var { Client } = require('@opensearch-project/opensearch'); -var fs = require('fs'); -var client = new Client({ - node: protocol + '://' + auth + '@' + host + ':' + port, - ssl: { - ca: fs.readFileSync(ca_certs_path), - // You can turn off certificate verification (rejectUnauthorized: false) if you're using self-signed certificates with a hostname mismatch. - // cert: fs.readFileSync(client_cert_path), - // key: fs.readFileSync(client_key_path) - }, -}); - -async function search() { - - // Create an index with non-default settings. - var index_name = 'books'; - var settings = { - settings: { - index: { - number_of_shards: 4, - number_of_replicas: 3, - }, - }, - }; - - var response = await client.indices.create({ - index: index_name, - body: settings, - }); - - console.log('Creating index:'); - console.log(response.body); - - // Add a document to the index. - var document = { - title: 'The Outsider', - author: 'Stephen King', - year: '2018', - genre: 'Crime fiction', - }; - - var id = '1'; - - var response = await client.index({ - id: id, - index: index_name, - body: document, - refresh: true, - }); - - console.log('Adding document:'); - console.log(response.body); - - // Add documents in bulk - var bulk_documents = [ - { - index: { - _index: 'books-king', - _id: '2' - } - }, - { - title: 'IT', - author: 'Stephen Kings', - year: '1986', - }, - { - create: { - _index: 'test', - _id: '3' - } - }, - { - title: 'The Green Mile', - author: 'Stephen Kings', - year: '1996', - }, - { - create: { - _index: 'test', - _id: '4' - } - }, - { - title: 'Carrie', - author: 'Stephen Kings', - year: '1974', - } - ]; - - var response = await client.bulk({ body: bulk_documents }); - - console.log('Adding documents using the bulk API') - console.log(response.body); - - // Search for a document. - var query = { - query: { - match: { - title: { - query: 'The Outsider', - }, - }, - }, - }; - - var response = await client.search({ - index: index_name, - body: query, - }); - - console.log('Search results:'); - console.log(response.body.hits); - - // Delete a document. - var response = await client.delete({ - index: index_name, - id: id, - }); - - console.log('Deleting document:'); - console.log(response.body); - - // Delete the index. - var response = await client.indices.delete({ - index: index_name, - }); - - console.log('Deleting index:'); - console.log(response.body); -} - -search().catch(console.log); -``` ## Project Resources diff --git a/USER_GUIDE.md b/USER_GUIDE.md new file mode 100644 index 000000000..289c10243 --- /dev/null +++ b/USER_GUIDE.md @@ -0,0 +1,197 @@ +# User Guide + +- [User Guide](#user-guide) + - [Initializing a Client](#initializing-a-client) + - [Authenticate with Amazon OpenSearch Service](#authenticate-with-amazon-opensearch-service) + - [Using AWS V2 SDK](#using-aws-v2-sdk) + - [Using AWS V3 SDK](#using-aws-v3-sdk) + - [Create an Index](#create-an-index) + - [Add a Document to the Index](#add-a-document-to-the-index) + - [Search for the Document](#search-for-the-document) + - [Delete the document](#delete-the-document) + - [Delete the index](#delete-the-index) + +## Initializing a Client +```javascript +'use strict'; + +var host = 'localhost'; +var protocol = 'https'; +var port = 9200; +var auth = 'admin:admin'; // For testing only. Don't store credentials in code. +var ca_certs_path = '/full/path/to/root-ca.pem'; + +// Optional client certificates if you don't want to use HTTP basic authentication. +// var client_cert_path = '/full/path/to/client.pem' +// var client_key_path = '/full/path/to/client-key.pem' + +// Create a client with SSL/TLS enabled. +var { Client } = require('@opensearch-project/opensearch'); +var fs = require('fs'); +var client = new Client({ + node: protocol + '://' + auth + '@' + host + ':' + port, + ssl: { + ca: fs.readFileSync(ca_certs_path), + // You can turn off certificate verification (rejectUnauthorized: false) if you're using self-signed certificates with a hostname mismatch. + // cert: fs.readFileSync(client_cert_path), + // key: fs.readFileSync(client_key_path) + }, +}); +``` + +### Authenticate with Amazon OpenSearch Service + +#### Using AWS V2 SDK + +```javascript +const AWS = require('aws-sdk'); // V2 SDK. +const { Client } = require('@opensearch-project/opensearch'); +const { AwsSigv4Signer } = require('@opensearch-project/opensearch/aws'); + +const client = new Client({ + ...AwsSigv4Signer({ + region: 'us-east-1', + // Must return a Promise that resolve to an AWS.Credentials object. + // This function is used to acquire the credentials when the client start and + // when the credentials are expired. + // The Client will refresh the Credentials only when they are expired. + // With AWS SDK V2, Credentials.refreshPromise is used when available to refresh the credentials. + + // Example with AWS SDK V2: + getCredentials: () => + new Promise((resolve, reject) => { + // Any other method to acquire a new Credentials object can be used. + AWS.config.getCredentials((err, credentials) => { + if (err) { + reject(err); + } else { + resolve(credentials); + } + }); + }), + }), + node: "https://search-xxx.region.es.amazonaws.com", // OpenSearch domain URL +}); +``` + +#### Using AWS V3 SDK + +```javascript +const { defaultProvider } = require("@aws-sdk/credential-provider-node"); // V3 SDK. +const { Client } = require('@opensearch-project/opensearch'); +const { AwsSigv4Signer } = require('@opensearch-project/opensearch/aws'); + +const client = new Client({ + ...AwsSigv4Signer({ + region: 'us-east-1', + // Must return a Promise that resolve to an AWS.Credentials object. + // This function is used to acquire the credentials when the client start and + // when the credentials are expired. + // The Client will refresh the Credentials only when they are expired. + // With AWS SDK V2, Credentials.refreshPromise is used when available to refresh the credentials. + + // Example with AWS SDK V3: + getCredentials: () => { + // Any other method to acquire a new Credentials object can be used. + const credentialsProvider = defaultProvider(); + return credentialsProvider(); + }, + }), + node: "https://search-xxx.region.es.amazonaws.com", // OpenSearch domain URL +}); +``` + +## Create an Index + +```javascript + console.log('Creating index:'); + + var index_name = 'books'; + var settings = { + settings: { + index: { + number_of_shards: 4, + number_of_replicas: 3, + }, + }, + }; + + var response = await client.indices.create({ + index: index_name, + body: settings, + }); + + console.log(response.body); +``` + +## Add a Document to the Index + +```javascript + console.log('Adding document:'); + + var document = { + title: 'The Outsider', + author: 'Stephen King', + year: '2018', + genre: 'Crime fiction', + }; + + var id = '1'; + + var response = await client.index({ + id: id, + index: index_name, + body: document, + refresh: true, + }); + + console.log(response.body); +``` + +## Search for the Document + +```javascript + console.log('Search results:'); + + var query = { + query: { + match: { + title: { + query: 'The Outsider', + }, + }, + }, + }; + + var response = await client.search({ + index: index_name, + body: query, + }); + + console.log(response.body.hits); +``` + +## Delete the document + +```javascript + console.log('Deleting document:'); + + var response = await client.delete({ + index: index_name, + id: id, + }); + + console.log(response.body); +``` + +## Delete the index + +```javascript + console.log('Deleting index:'); + + var response = await client.indices.delete({ + index: index_name, + }); + + console.log(response.body); +``` \ No newline at end of file diff --git a/index.d.ts b/index.d.ts index 8a6112c99..6e9ac2a82 100644 --- a/index.d.ts +++ b/index.d.ts @@ -92,7 +92,7 @@ interface NodeOptions { ssl?: TlsConnectionOptions; headers?: Record; roles?: { - cluster_manager?: boolean + cluster_manager?: boolean; /** * @deprecated use cluster_manager instead */ @@ -138,7 +138,7 @@ interface ClientOptions { password?: string; }; disablePrototypePoisoningProtection?: boolean | 'proto' | 'constructor'; - memoryCircuitBreaker?: MemoryCircuitBreakerOptions + memoryCircuitBreaker?: MemoryCircuitBreakerOptions; } declare class Client { @@ -194,123 +194,451 @@ declare class Client { callback: callbackFn ): TransportRequestCallback; cat: { - aliases, TContext = Context>(params?: RequestParams.CatAliases, options?: TransportRequestOptions): TransportRequestPromise> - aliases, TContext = Context>(callback: callbackFn): TransportRequestCallback - aliases, TContext = Context>(params: RequestParams.CatAliases, callback: callbackFn): TransportRequestCallback - aliases, TContext = Context>(params: RequestParams.CatAliases, options: TransportRequestOptions, callback: callbackFn): TransportRequestCallback - allocation, TContext = Context>(params?: RequestParams.CatAllocation, options?: TransportRequestOptions): TransportRequestPromise> - allocation, TContext = Context>(callback: callbackFn): TransportRequestCallback - allocation, TContext = Context>(params: RequestParams.CatAllocation, callback: callbackFn): TransportRequestCallback - allocation, TContext = Context>(params: RequestParams.CatAllocation, options: TransportRequestOptions, callback: callbackFn): TransportRequestCallback - cluster_manager, TContext = Context>(params?: RequestParams.CatClusterManager, options?: TransportRequestOptions): TransportRequestPromise> - cluster_manager, TContext = Context>(callback: callbackFn): TransportRequestCallback - cluster_manager, TContext = Context>(params: RequestParams.CatClusterManager, callback: callbackFn): TransportRequestCallback - cluster_manager, TContext = Context>(params: RequestParams.CatClusterManager, options: TransportRequestOptions, callback: callbackFn): TransportRequestCallback - count, TContext = Context>(params?: RequestParams.CatCount, options?: TransportRequestOptions): TransportRequestPromise> - count, TContext = Context>(callback: callbackFn): TransportRequestCallback - count, TContext = Context>(params: RequestParams.CatCount, callback: callbackFn): TransportRequestCallback - count, TContext = Context>(params: RequestParams.CatCount, options: TransportRequestOptions, callback: callbackFn): TransportRequestCallback - fielddata, TContext = Context>(params?: RequestParams.CatFielddata, options?: TransportRequestOptions): TransportRequestPromise> - fielddata, TContext = Context>(callback: callbackFn): TransportRequestCallback - fielddata, TContext = Context>(params: RequestParams.CatFielddata, callback: callbackFn): TransportRequestCallback - fielddata, TContext = Context>(params: RequestParams.CatFielddata, options: TransportRequestOptions, callback: callbackFn): TransportRequestCallback - health, TContext = Context>(params?: RequestParams.CatHealth, options?: TransportRequestOptions): TransportRequestPromise> - health, TContext = Context>(callback: callbackFn): TransportRequestCallback - health, TContext = Context>(params: RequestParams.CatHealth, callback: callbackFn): TransportRequestCallback - health, TContext = Context>(params: RequestParams.CatHealth, options: TransportRequestOptions, callback: callbackFn): TransportRequestCallback - help, TContext = Context>(params?: RequestParams.CatHelp, options?: TransportRequestOptions): TransportRequestPromise> - help, TContext = Context>(callback: callbackFn): TransportRequestCallback - help, TContext = Context>(params: RequestParams.CatHelp, callback: callbackFn): TransportRequestCallback - help, TContext = Context>(params: RequestParams.CatHelp, options: TransportRequestOptions, callback: callbackFn): TransportRequestCallback - indices, TContext = Context>(params?: RequestParams.CatIndices, options?: TransportRequestOptions): TransportRequestPromise> - indices, TContext = Context>(callback: callbackFn): TransportRequestCallback - indices, TContext = Context>(params: RequestParams.CatIndices, callback: callbackFn): TransportRequestCallback - indices, TContext = Context>(params: RequestParams.CatIndices, options: TransportRequestOptions, callback: callbackFn): TransportRequestCallback + aliases, TContext = Context>( + params?: RequestParams.CatAliases, + options?: TransportRequestOptions + ): TransportRequestPromise>; + aliases, TContext = Context>( + callback: callbackFn + ): TransportRequestCallback; + aliases, TContext = Context>( + params: RequestParams.CatAliases, + callback: callbackFn + ): TransportRequestCallback; + aliases, TContext = Context>( + params: RequestParams.CatAliases, + options: TransportRequestOptions, + callback: callbackFn + ): TransportRequestCallback; + allocation, TContext = Context>( + params?: RequestParams.CatAllocation, + options?: TransportRequestOptions + ): TransportRequestPromise>; + allocation, TContext = Context>( + callback: callbackFn + ): TransportRequestCallback; + allocation, TContext = Context>( + params: RequestParams.CatAllocation, + callback: callbackFn + ): TransportRequestCallback; + allocation, TContext = Context>( + params: RequestParams.CatAllocation, + options: TransportRequestOptions, + callback: callbackFn + ): TransportRequestCallback; + cluster_manager, TContext = Context>( + params?: RequestParams.CatClusterManager, + options?: TransportRequestOptions + ): TransportRequestPromise>; + cluster_manager, TContext = Context>( + callback: callbackFn + ): TransportRequestCallback; + cluster_manager, TContext = Context>( + params: RequestParams.CatClusterManager, + callback: callbackFn + ): TransportRequestCallback; + cluster_manager, TContext = Context>( + params: RequestParams.CatClusterManager, + options: TransportRequestOptions, + callback: callbackFn + ): TransportRequestCallback; + count, TContext = Context>( + params?: RequestParams.CatCount, + options?: TransportRequestOptions + ): TransportRequestPromise>; + count, TContext = Context>( + callback: callbackFn + ): TransportRequestCallback; + count, TContext = Context>( + params: RequestParams.CatCount, + callback: callbackFn + ): TransportRequestCallback; + count, TContext = Context>( + params: RequestParams.CatCount, + options: TransportRequestOptions, + callback: callbackFn + ): TransportRequestCallback; + fielddata, TContext = Context>( + params?: RequestParams.CatFielddata, + options?: TransportRequestOptions + ): TransportRequestPromise>; + fielddata, TContext = Context>( + callback: callbackFn + ): TransportRequestCallback; + fielddata, TContext = Context>( + params: RequestParams.CatFielddata, + callback: callbackFn + ): TransportRequestCallback; + fielddata, TContext = Context>( + params: RequestParams.CatFielddata, + options: TransportRequestOptions, + callback: callbackFn + ): TransportRequestCallback; + health, TContext = Context>( + params?: RequestParams.CatHealth, + options?: TransportRequestOptions + ): TransportRequestPromise>; + health, TContext = Context>( + callback: callbackFn + ): TransportRequestCallback; + health, TContext = Context>( + params: RequestParams.CatHealth, + callback: callbackFn + ): TransportRequestCallback; + health, TContext = Context>( + params: RequestParams.CatHealth, + options: TransportRequestOptions, + callback: callbackFn + ): TransportRequestCallback; + help, TContext = Context>( + params?: RequestParams.CatHelp, + options?: TransportRequestOptions + ): TransportRequestPromise>; + help, TContext = Context>( + callback: callbackFn + ): TransportRequestCallback; + help, TContext = Context>( + params: RequestParams.CatHelp, + callback: callbackFn + ): TransportRequestCallback; + help, TContext = Context>( + params: RequestParams.CatHelp, + options: TransportRequestOptions, + callback: callbackFn + ): TransportRequestCallback; + indices, TContext = Context>( + params?: RequestParams.CatIndices, + options?: TransportRequestOptions + ): TransportRequestPromise>; + indices, TContext = Context>( + callback: callbackFn + ): TransportRequestCallback; + indices, TContext = Context>( + params: RequestParams.CatIndices, + callback: callbackFn + ): TransportRequestCallback; + indices, TContext = Context>( + params: RequestParams.CatIndices, + options: TransportRequestOptions, + callback: callbackFn + ): TransportRequestCallback; /** - * // TODO: delete cat.master when it is removed from OpenSearch - * @deprecated use cat.cluster_manager instead - */ - master, TContext = Context>(params?: RequestParams.CatMaster, options?: TransportRequestOptions): TransportRequestPromise> + * // TODO: delete cat.master when it is removed from OpenSearch + * @deprecated use cat.cluster_manager instead + */ + master, TContext = Context>( + params?: RequestParams.CatMaster, + options?: TransportRequestOptions + ): TransportRequestPromise>; /** - * // TODO: delete cat.master when it is removed from OpenSearch - * @deprecated use cat.cluster_manager instead - */ - master, TContext = Context>(callback: callbackFn): TransportRequestCallback + * // TODO: delete cat.master when it is removed from OpenSearch + * @deprecated use cat.cluster_manager instead + */ + master, TContext = Context>( + callback: callbackFn + ): TransportRequestCallback; /** - * // TODO: delete cat.master when it is removed from OpenSearch - * @deprecated use cat.cluster_manager instead - */ - master, TContext = Context>(params: RequestParams.CatMaster, callback: callbackFn): TransportRequestCallback + * // TODO: delete cat.master when it is removed from OpenSearch + * @deprecated use cat.cluster_manager instead + */ + master, TContext = Context>( + params: RequestParams.CatMaster, + callback: callbackFn + ): TransportRequestCallback; /** - * // TODO: delete cat.master when it is removed from OpenSearch - * @deprecated use cat.cluster_manager instead - */ - master, TContext = Context>(params: RequestParams.CatMaster, options: TransportRequestOptions, callback: callbackFn): TransportRequestCallback - nodeattrs, TContext = Context>(params?: RequestParams.CatNodeattrs, options?: TransportRequestOptions): TransportRequestPromise> - nodeattrs, TContext = Context>(callback: callbackFn): TransportRequestCallback - nodeattrs, TContext = Context>(params: RequestParams.CatNodeattrs, callback: callbackFn): TransportRequestCallback - nodeattrs, TContext = Context>(params: RequestParams.CatNodeattrs, options: TransportRequestOptions, callback: callbackFn): TransportRequestCallback - nodes, TContext = Context>(params?: RequestParams.CatNodes, options?: TransportRequestOptions): TransportRequestPromise> - nodes, TContext = Context>(callback: callbackFn): TransportRequestCallback - nodes, TContext = Context>(params: RequestParams.CatNodes, callback: callbackFn): TransportRequestCallback - nodes, TContext = Context>(params: RequestParams.CatNodes, options: TransportRequestOptions, callback: callbackFn): TransportRequestCallback - pending_tasks, TContext = Context>(params?: RequestParams.CatPendingTasks, options?: TransportRequestOptions): TransportRequestPromise> - pending_tasks, TContext = Context>(callback: callbackFn): TransportRequestCallback - pending_tasks, TContext = Context>(params: RequestParams.CatPendingTasks, callback: callbackFn): TransportRequestCallback - pending_tasks, TContext = Context>(params: RequestParams.CatPendingTasks, options: TransportRequestOptions, callback: callbackFn): TransportRequestCallback - pendingTasks, TContext = Context>(params?: RequestParams.CatPendingTasks, options?: TransportRequestOptions): TransportRequestPromise> - pendingTasks, TContext = Context>(callback: callbackFn): TransportRequestCallback - pendingTasks, TContext = Context>(params: RequestParams.CatPendingTasks, callback: callbackFn): TransportRequestCallback - pendingTasks, TContext = Context>(params: RequestParams.CatPendingTasks, options: TransportRequestOptions, callback: callbackFn): TransportRequestCallback - plugins, TContext = Context>(params?: RequestParams.CatPlugins, options?: TransportRequestOptions): TransportRequestPromise> - plugins, TContext = Context>(callback: callbackFn): TransportRequestCallback - plugins, TContext = Context>(params: RequestParams.CatPlugins, callback: callbackFn): TransportRequestCallback - plugins, TContext = Context>(params: RequestParams.CatPlugins, options: TransportRequestOptions, callback: callbackFn): TransportRequestCallback - recovery, TContext = Context>(params?: RequestParams.CatRecovery, options?: TransportRequestOptions): TransportRequestPromise> - recovery, TContext = Context>(callback: callbackFn): TransportRequestCallback - recovery, TContext = Context>(params: RequestParams.CatRecovery, callback: callbackFn): TransportRequestCallback - recovery, TContext = Context>(params: RequestParams.CatRecovery, options: TransportRequestOptions, callback: callbackFn): TransportRequestCallback - repositories, TContext = Context>(params?: RequestParams.CatRepositories, options?: TransportRequestOptions): TransportRequestPromise> - repositories, TContext = Context>(callback: callbackFn): TransportRequestCallback - repositories, TContext = Context>(params: RequestParams.CatRepositories, callback: callbackFn): TransportRequestCallback - repositories, TContext = Context>(params: RequestParams.CatRepositories, options: TransportRequestOptions, callback: callbackFn): TransportRequestCallback - segments, TContext = Context>(params?: RequestParams.CatSegments, options?: TransportRequestOptions): TransportRequestPromise> - segments, TContext = Context>(callback: callbackFn): TransportRequestCallback - segments, TContext = Context>(params: RequestParams.CatSegments, callback: callbackFn): TransportRequestCallback - segments, TContext = Context>(params: RequestParams.CatSegments, options: TransportRequestOptions, callback: callbackFn): TransportRequestCallback - shards, TContext = Context>(params?: RequestParams.CatShards, options?: TransportRequestOptions): TransportRequestPromise> - shards, TContext = Context>(callback: callbackFn): TransportRequestCallback - shards, TContext = Context>(params: RequestParams.CatShards, callback: callbackFn): TransportRequestCallback - shards, TContext = Context>(params: RequestParams.CatShards, options: TransportRequestOptions, callback: callbackFn): TransportRequestCallback - snapshots, TContext = Context>(params?: RequestParams.CatSnapshots, options?: TransportRequestOptions): TransportRequestPromise> - snapshots, TContext = Context>(callback: callbackFn): TransportRequestCallback - snapshots, TContext = Context>(params: RequestParams.CatSnapshots, callback: callbackFn): TransportRequestCallback - snapshots, TContext = Context>(params: RequestParams.CatSnapshots, options: TransportRequestOptions, callback: callbackFn): TransportRequestCallback - tasks, TContext = Context>(params?: RequestParams.CatTasks, options?: TransportRequestOptions): TransportRequestPromise> - tasks, TContext = Context>(callback: callbackFn): TransportRequestCallback - tasks, TContext = Context>(params: RequestParams.CatTasks, callback: callbackFn): TransportRequestCallback - tasks, TContext = Context>(params: RequestParams.CatTasks, options: TransportRequestOptions, callback: callbackFn): TransportRequestCallback - templates, TContext = Context>(params?: RequestParams.CatTemplates, options?: TransportRequestOptions): TransportRequestPromise> - templates, TContext = Context>(callback: callbackFn): TransportRequestCallback - templates, TContext = Context>(params: RequestParams.CatTemplates, callback: callbackFn): TransportRequestCallback - templates, TContext = Context>(params: RequestParams.CatTemplates, options: TransportRequestOptions, callback: callbackFn): TransportRequestCallback - thread_pool, TContext = Context>(params?: RequestParams.CatThreadPool, options?: TransportRequestOptions): TransportRequestPromise> - thread_pool, TContext = Context>(callback: callbackFn): TransportRequestCallback - thread_pool, TContext = Context>(params: RequestParams.CatThreadPool, callback: callbackFn): TransportRequestCallback - thread_pool, TContext = Context>(params: RequestParams.CatThreadPool, options: TransportRequestOptions, callback: callbackFn): TransportRequestCallback - threadPool, TContext = Context>(params?: RequestParams.CatThreadPool, options?: TransportRequestOptions): TransportRequestPromise> - threadPool, TContext = Context>(callback: callbackFn): TransportRequestCallback - threadPool, TContext = Context>(params: RequestParams.CatThreadPool, callback: callbackFn): TransportRequestCallback - threadPool, TContext = Context>(params: RequestParams.CatThreadPool, options: TransportRequestOptions, callback: callbackFn): TransportRequestCallback - } - clear_scroll, TRequestBody extends RequestBody = Record, TContext = Context>(params?: RequestParams.ClearScroll, options?: TransportRequestOptions): TransportRequestPromise> - clear_scroll, TRequestBody extends RequestBody = Record, TContext = Context>(callback: callbackFn): TransportRequestCallback - clear_scroll, TRequestBody extends RequestBody = Record, TContext = Context>(params: RequestParams.ClearScroll, callback: callbackFn): TransportRequestCallback - clear_scroll, TRequestBody extends RequestBody = Record, TContext = Context>(params: RequestParams.ClearScroll, options: TransportRequestOptions, callback: callbackFn): TransportRequestCallback - clearScroll, TRequestBody extends RequestBody = Record, TContext = Context>(params?: RequestParams.ClearScroll, options?: TransportRequestOptions): TransportRequestPromise> - clearScroll, TRequestBody extends RequestBody = Record, TContext = Context>(callback: callbackFn): TransportRequestCallback - clearScroll, TRequestBody extends RequestBody = Record, TContext = Context>(params: RequestParams.ClearScroll, callback: callbackFn): TransportRequestCallback - clearScroll, TRequestBody extends RequestBody = Record, TContext = Context>(params: RequestParams.ClearScroll, options: TransportRequestOptions, callback: callbackFn): TransportRequestCallback + * // TODO: delete cat.master when it is removed from OpenSearch + * @deprecated use cat.cluster_manager instead + */ + master, TContext = Context>( + params: RequestParams.CatMaster, + options: TransportRequestOptions, + callback: callbackFn + ): TransportRequestCallback; + nodeattrs, TContext = Context>( + params?: RequestParams.CatNodeattrs, + options?: TransportRequestOptions + ): TransportRequestPromise>; + nodeattrs, TContext = Context>( + callback: callbackFn + ): TransportRequestCallback; + nodeattrs, TContext = Context>( + params: RequestParams.CatNodeattrs, + callback: callbackFn + ): TransportRequestCallback; + nodeattrs, TContext = Context>( + params: RequestParams.CatNodeattrs, + options: TransportRequestOptions, + callback: callbackFn + ): TransportRequestCallback; + nodes, TContext = Context>( + params?: RequestParams.CatNodes, + options?: TransportRequestOptions + ): TransportRequestPromise>; + nodes, TContext = Context>( + callback: callbackFn + ): TransportRequestCallback; + nodes, TContext = Context>( + params: RequestParams.CatNodes, + callback: callbackFn + ): TransportRequestCallback; + nodes, TContext = Context>( + params: RequestParams.CatNodes, + options: TransportRequestOptions, + callback: callbackFn + ): TransportRequestCallback; + pending_tasks, TContext = Context>( + params?: RequestParams.CatPendingTasks, + options?: TransportRequestOptions + ): TransportRequestPromise>; + pending_tasks, TContext = Context>( + callback: callbackFn + ): TransportRequestCallback; + pending_tasks, TContext = Context>( + params: RequestParams.CatPendingTasks, + callback: callbackFn + ): TransportRequestCallback; + pending_tasks, TContext = Context>( + params: RequestParams.CatPendingTasks, + options: TransportRequestOptions, + callback: callbackFn + ): TransportRequestCallback; + pendingTasks, TContext = Context>( + params?: RequestParams.CatPendingTasks, + options?: TransportRequestOptions + ): TransportRequestPromise>; + pendingTasks, TContext = Context>( + callback: callbackFn + ): TransportRequestCallback; + pendingTasks, TContext = Context>( + params: RequestParams.CatPendingTasks, + callback: callbackFn + ): TransportRequestCallback; + pendingTasks, TContext = Context>( + params: RequestParams.CatPendingTasks, + options: TransportRequestOptions, + callback: callbackFn + ): TransportRequestCallback; + plugins, TContext = Context>( + params?: RequestParams.CatPlugins, + options?: TransportRequestOptions + ): TransportRequestPromise>; + plugins, TContext = Context>( + callback: callbackFn + ): TransportRequestCallback; + plugins, TContext = Context>( + params: RequestParams.CatPlugins, + callback: callbackFn + ): TransportRequestCallback; + plugins, TContext = Context>( + params: RequestParams.CatPlugins, + options: TransportRequestOptions, + callback: callbackFn + ): TransportRequestCallback; + recovery, TContext = Context>( + params?: RequestParams.CatRecovery, + options?: TransportRequestOptions + ): TransportRequestPromise>; + recovery, TContext = Context>( + callback: callbackFn + ): TransportRequestCallback; + recovery, TContext = Context>( + params: RequestParams.CatRecovery, + callback: callbackFn + ): TransportRequestCallback; + recovery, TContext = Context>( + params: RequestParams.CatRecovery, + options: TransportRequestOptions, + callback: callbackFn + ): TransportRequestCallback; + repositories, TContext = Context>( + params?: RequestParams.CatRepositories, + options?: TransportRequestOptions + ): TransportRequestPromise>; + repositories, TContext = Context>( + callback: callbackFn + ): TransportRequestCallback; + repositories, TContext = Context>( + params: RequestParams.CatRepositories, + callback: callbackFn + ): TransportRequestCallback; + repositories, TContext = Context>( + params: RequestParams.CatRepositories, + options: TransportRequestOptions, + callback: callbackFn + ): TransportRequestCallback; + segments, TContext = Context>( + params?: RequestParams.CatSegments, + options?: TransportRequestOptions + ): TransportRequestPromise>; + segments, TContext = Context>( + callback: callbackFn + ): TransportRequestCallback; + segments, TContext = Context>( + params: RequestParams.CatSegments, + callback: callbackFn + ): TransportRequestCallback; + segments, TContext = Context>( + params: RequestParams.CatSegments, + options: TransportRequestOptions, + callback: callbackFn + ): TransportRequestCallback; + shards, TContext = Context>( + params?: RequestParams.CatShards, + options?: TransportRequestOptions + ): TransportRequestPromise>; + shards, TContext = Context>( + callback: callbackFn + ): TransportRequestCallback; + shards, TContext = Context>( + params: RequestParams.CatShards, + callback: callbackFn + ): TransportRequestCallback; + shards, TContext = Context>( + params: RequestParams.CatShards, + options: TransportRequestOptions, + callback: callbackFn + ): TransportRequestCallback; + snapshots, TContext = Context>( + params?: RequestParams.CatSnapshots, + options?: TransportRequestOptions + ): TransportRequestPromise>; + snapshots, TContext = Context>( + callback: callbackFn + ): TransportRequestCallback; + snapshots, TContext = Context>( + params: RequestParams.CatSnapshots, + callback: callbackFn + ): TransportRequestCallback; + snapshots, TContext = Context>( + params: RequestParams.CatSnapshots, + options: TransportRequestOptions, + callback: callbackFn + ): TransportRequestCallback; + tasks, TContext = Context>( + params?: RequestParams.CatTasks, + options?: TransportRequestOptions + ): TransportRequestPromise>; + tasks, TContext = Context>( + callback: callbackFn + ): TransportRequestCallback; + tasks, TContext = Context>( + params: RequestParams.CatTasks, + callback: callbackFn + ): TransportRequestCallback; + tasks, TContext = Context>( + params: RequestParams.CatTasks, + options: TransportRequestOptions, + callback: callbackFn + ): TransportRequestCallback; + templates, TContext = Context>( + params?: RequestParams.CatTemplates, + options?: TransportRequestOptions + ): TransportRequestPromise>; + templates, TContext = Context>( + callback: callbackFn + ): TransportRequestCallback; + templates, TContext = Context>( + params: RequestParams.CatTemplates, + callback: callbackFn + ): TransportRequestCallback; + templates, TContext = Context>( + params: RequestParams.CatTemplates, + options: TransportRequestOptions, + callback: callbackFn + ): TransportRequestCallback; + thread_pool, TContext = Context>( + params?: RequestParams.CatThreadPool, + options?: TransportRequestOptions + ): TransportRequestPromise>; + thread_pool, TContext = Context>( + callback: callbackFn + ): TransportRequestCallback; + thread_pool, TContext = Context>( + params: RequestParams.CatThreadPool, + callback: callbackFn + ): TransportRequestCallback; + thread_pool, TContext = Context>( + params: RequestParams.CatThreadPool, + options: TransportRequestOptions, + callback: callbackFn + ): TransportRequestCallback; + threadPool, TContext = Context>( + params?: RequestParams.CatThreadPool, + options?: TransportRequestOptions + ): TransportRequestPromise>; + threadPool, TContext = Context>( + callback: callbackFn + ): TransportRequestCallback; + threadPool, TContext = Context>( + params: RequestParams.CatThreadPool, + callback: callbackFn + ): TransportRequestCallback; + threadPool, TContext = Context>( + params: RequestParams.CatThreadPool, + options: TransportRequestOptions, + callback: callbackFn + ): TransportRequestCallback; + }; + clear_scroll< + TResponse = Record, + TRequestBody extends RequestBody = Record, + TContext = Context + >( + params?: RequestParams.ClearScroll, + options?: TransportRequestOptions + ): TransportRequestPromise>; + clear_scroll< + TResponse = Record, + TRequestBody extends RequestBody = Record, + TContext = Context + >(callback: callbackFn): TransportRequestCallback; + clear_scroll< + TResponse = Record, + TRequestBody extends RequestBody = Record, + TContext = Context + >( + params: RequestParams.ClearScroll, + callback: callbackFn + ): TransportRequestCallback; + clear_scroll< + TResponse = Record, + TRequestBody extends RequestBody = Record, + TContext = Context + >( + params: RequestParams.ClearScroll, + options: TransportRequestOptions, + callback: callbackFn + ): TransportRequestCallback; + clearScroll< + TResponse = Record, + TRequestBody extends RequestBody = Record, + TContext = Context + >( + params?: RequestParams.ClearScroll, + options?: TransportRequestOptions + ): TransportRequestPromise>; + clearScroll< + TResponse = Record, + TRequestBody extends RequestBody = Record, + TContext = Context + >(callback: callbackFn): TransportRequestCallback; + clearScroll< + TResponse = Record, + TRequestBody extends RequestBody = Record, + TContext = Context + >( + params: RequestParams.ClearScroll, + callback: callbackFn + ): TransportRequestCallback; + clearScroll< + TResponse = Record, + TRequestBody extends RequestBody = Record, + TContext = Context + >( + params: RequestParams.ClearScroll, + options: TransportRequestOptions, + callback: callbackFn + ): TransportRequestCallback; cluster: { allocation_explain< TResponse = Record, diff --git a/lib/aws/AwsSigv4Signer.js b/lib/aws/AwsSigv4Signer.js new file mode 100644 index 000000000..7e0c7c614 --- /dev/null +++ b/lib/aws/AwsSigv4Signer.js @@ -0,0 +1,124 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +'use strict'; +const Connection = require('../Connection'); +const Transport = require('../Transport'); +const aws4 = require('aws4'); +const AwsSigv4SignerError = require('./errors'); + +function AwsSigv4Signer(opts) { + const credentialsState = { + credentials: null, + }; + if (opts && (!opts.region || opts.region === null || opts.region === '')) { + throw new AwsSigv4SignerError('Region cannot be empty'); + } + if (opts && typeof opts.getCredentials !== 'function') { + throw new AwsSigv4SignerError('getCredentials function is required'); + } + + function buildSignedRequestObject(request = {}) { + request.service = 'es'; + request.region = opts.region; + request.headers = request.headers || {}; + request.headers['host'] = request.hostname; + return aws4.sign(request, credentialsState.credentials); + } + + class AwsSigv4SignerConnection extends Connection { + buildRequestObject(params) { + const request = super.buildRequestObject(params); + return buildSignedRequestObject(request); + } + } + + class AwsSigv4SignerTransport extends Transport { + request(params, options = {}, callback = undefined) { + // options is optional so if options is a function, it's the callback. + if (typeof options === 'function') { + callback = options; + options = {}; + } + + const currentCredentials = credentialsState.credentials; + let expired = false; + if (!currentCredentials) { + // Credentials haven't been acquired yet. + expired = true; + } + // AWS SDK V2, needsRefresh should be available. + else if (typeof currentCredentials.needsRefresh === 'function') { + expired = currentCredentials.needsRefresh(); + } + // AWS SDK V2, alternative to needsRefresh. + else if (currentCredentials.expired === true) { + expired = true; + } + // AWS SDK V2, alternative to needsRefresh and expired. + else if (currentCredentials.expireTime && currentCredentials.expireTime < new Date()) { + expired = true; + } + // AWS SDK V3, Credentials.expiration is a Date object + else if (currentCredentials.expiration && currentCredentials.expiration < new Date()) { + expired = true; + } + + if (!expired) { + if (typeof callback === 'undefined') { + return super.request(params, options); + } + super.request(params, options, callback); + return; + } + + // In AWS SDK V2 Credentials.refreshPromise should be available. + if (currentCredentials && typeof currentCredentials.refreshPromise === 'function') { + if (typeof callback === 'undefined') { + return currentCredentials.refreshPromise().then(() => { + return super.request(params, options); + }); + } else { + currentCredentials + .refreshPromise() + .then(() => { + super.request(params, options, callback); + }) + .catch(callback); + return; + } + } + + // For AWS SDK V3 or when the client has not acquired credentials yet. + if (typeof callback === 'undefined') { + return opts.getCredentials().then((credentials) => { + credentialsState.credentials = credentials; + return super.request(params, options); + }); + } else { + opts + .getCredentials() + .then((credentials) => { + credentialsState.credentials = credentials; + super.request(params, options, callback); + }) + .catch(callback); + } + } + } + + return { + Transport: AwsSigv4SignerTransport, + Connection: AwsSigv4SignerConnection, + buildSignedRequestObject, + }; +} +module.exports = AwsSigv4Signer; diff --git a/lib/aws/errors.js b/lib/aws/errors.js new file mode 100644 index 000000000..b93e80274 --- /dev/null +++ b/lib/aws/errors.js @@ -0,0 +1,25 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +'use strict'; +const { OpenSearchClientError } = require('../errors'); + +class AwsSigv4SignerError extends OpenSearchClientError { + constructor(message, data) { + super(message, data); + Error.captureStackTrace(this, AwsSigv4SignerError); + this.name = 'AwsSigv4SignerError'; + this.message = message || 'AwsSigv4Signer Error'; + this.data = data; + } +} + +module.exports = AwsSigv4SignerError; diff --git a/lib/aws/index.d.ts b/lib/aws/index.d.ts new file mode 100644 index 000000000..50ba0c4f0 --- /dev/null +++ b/lib/aws/index.d.ts @@ -0,0 +1,40 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/// + +import { Credentials } from '@aws-sdk/types'; +import Connection from '../Connection'; +import Transport from '../Transport'; +import * as http from 'http'; +import { OpenSearchClientError } from '../errors'; + +interface AwsSigv4SignerOptions { + getCredentials: () => Promise; + region: string; +} + +interface AwsSigv4SignerResponse { + Connection: typeof Connection; + Transport: typeof Transport; + buildSignedRequestObject(request: any): http.ClientRequestArgs; +} + +declare function AwsSigv4Signer (opts: AwsSigv4SignerOptions): AwsSigv4SignerResponse; + +declare class AwsSigv4SignerError extends OpenSearchClientError { + name: string; + message: string; + data: any; + constructor(message: string, data: any); +} + +export { AwsSigv4Signer, AwsSigv4SignerOptions, AwsSigv4SignerResponse, AwsSigv4SignerError }; diff --git a/lib/aws/index.js b/lib/aws/index.js new file mode 100644 index 000000000..8586dbb18 --- /dev/null +++ b/lib/aws/index.js @@ -0,0 +1,20 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +'use strict'; + +const AwsSigv4Signer = require('./AwsSigv4Signer'); +const AwsSigv4SignerError = require('./errors'); + +module.exports = { + AwsSigv4Signer, + AwsSigv4SignerError, +}; diff --git a/package.json b/package.json index 20a296676..b113fd1df 100644 --- a/package.json +++ b/package.json @@ -8,10 +8,21 @@ "require": "./index.js", "import": "./index.mjs" }, + "./aws": "./lib/aws/index.js", "./": "./" }, + "typesVersions": { + "*": { + ".": [ + "index.d.ts" + ], + "aws": [ + "./lib/aws/index.d.ts" + ] + } + }, "homepage": "https://www.opensearch.org/", - "version": "2.0.0", + "version": "2.1.0", "versionCanary": "7.10.0-canary.6", "keywords": [ "opensearch", @@ -44,6 +55,7 @@ "company": "Elasticsearch BV" }, "devDependencies": { + "@aws-sdk/types": "^3.160.0", "@sinonjs/fake-timers": "github:sinonjs/fake-timers#0bfffc1", "@types/node": "^15.3.1", "babel-eslint": "^10.1.0", @@ -77,6 +89,7 @@ "xmlbuilder2": "^2.4.1" }, "dependencies": { + "aws4": "^1.11.0", "debug": "^4.3.1", "hpagent": "^0.1.1", "ms": "^2.1.3", diff --git a/test/types/awssigv4signer.test-d.ts b/test/types/awssigv4signer.test-d.ts new file mode 100644 index 000000000..f45e38dbe --- /dev/null +++ b/test/types/awssigv4signer.test-d.ts @@ -0,0 +1,34 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ +import { expectType } from 'tsd'; +const { v4: uuidv4 } = require('uuid'); +import { AwsSigv4SignerResponse, AwsSigv4Signer } from '../../lib/aws'; + +const mockCreds = { + accessKeyId: uuidv4(), + secretAccessKey: uuidv4(), + expired: false, + expireTime: new Date(), + sessionToken: uuidv4(), +}; + +const mockRegion = 'us-east-1'; + +{ + const AwsSigv4SignerOptions = { + getCredentials: () => Promise.resolve(mockCreds), + region: mockRegion, + }; + + const auth = AwsSigv4Signer(AwsSigv4SignerOptions); + + expectType(auth); +} diff --git a/test/types/connection.test-d.ts b/test/types/connection.test-d.ts index 7e7606c69..1abbc0ccb 100644 --- a/test/types/connection.test-d.ts +++ b/test/types/connection.test-d.ts @@ -42,8 +42,8 @@ import { ConnectionOptions } from '../../lib/Connection'; agent: { keepAlive: false }, status: 'alive', roles: {}, - auth: { username: 'username', password: 'password' } - }) + auth: { username: 'username', password: 'password' }, + }); expectType(conn); expectType(conn.url); diff --git a/test/unit/lib/aws/awssigv4signer.test.js b/test/unit/lib/aws/awssigv4signer.test.js new file mode 100644 index 000000000..c895c4915 --- /dev/null +++ b/test/unit/lib/aws/awssigv4signer.test.js @@ -0,0 +1,407 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ +const { test } = require('tap'); +const { URL } = require('url'); +const { v4: uuidv4 } = require('uuid'); +const AwsSigv4Signer = require('../../../../lib/aws/AwsSigv4Signer'); +const AwsSigv4SignerError = require('../../../../lib/aws/errors'); +const { Connection } = require('../../../../index'); +const { Client, buildServer } = require('../../../utils'); + +test('Sign with SigV4', (t) => { + t.plan(2); + + const mockCreds = { + accessKeyId: uuidv4(), + secretAccessKey: uuidv4(), + }; + + const mockRegion = 'us-west-2'; + + const AwsSigv4SignerOptions = { + getCredentials: () => + new Promise((resolve) => { + setTimeout(() => resolve(mockCreds), 100); + }), + region: mockRegion, + }; + + const auth = AwsSigv4Signer(AwsSigv4SignerOptions); + + const connection = new Connection({ + url: new URL('https://localhost:9200'), + }); + + const request = connection.buildRequestObject({ + path: '/hello', + method: 'GET', + headers: { + 'X-Custom-Test': true, + }, + }); + + const signedRequest = auth.buildSignedRequestObject(request); + t.hasProp(signedRequest.headers, 'X-Amz-Date'); + t.hasProp(signedRequest.headers, 'Authorization'); +}); + +test('Sign with SigV4 failure (with empty region)', (t) => { + t.plan(2); + + const mockCreds = { + accessKeyId: uuidv4(), + secretAccessKey: uuidv4(), + }; + + const AwsSigv4SignerOptions = { + getCredentials: () => + new Promise((resolve) => { + setTimeout(() => resolve(mockCreds), 100); + }), + }; + + try { + AwsSigv4Signer(AwsSigv4SignerOptions); + t.fail('Should fail'); + } catch (err) { + t.ok(err instanceof AwsSigv4SignerError); + t.same(err.message, 'Region cannot be empty'); + } +}); + +test('Sign with SigV4 failure (without getCredentials function)', (t) => { + t.plan(2); + + const mockRegion = 'us-west-2'; + + const AwsSigv4SignerOptions = { region: mockRegion }; + + try { + AwsSigv4Signer(AwsSigv4SignerOptions); + t.fail('Should fail'); + } catch (err) { + t.ok(err instanceof AwsSigv4SignerError); + t.same(err.message, 'getCredentials function is required'); + } +}); + +test('Basic aws (promises)', (t) => { + t.plan(4); + + function handler(req, res) { + res.setHeader('Content-Type', 'application/json;utf=8'); + res.end(JSON.stringify({ hello: 'world' })); + } + + buildServer(handler, ({ port }, server) => { + const mockRegion = 'us-east-1'; + + let getCredentialsCalled = 0; + const AwsSigv4SignerOptions = { + region: mockRegion, + getCredentials: () => + new Promise((resolve) => { + setTimeout(() => { + getCredentialsCalled++; + resolve({ + accessKeyId: uuidv4(), + secretAccessKey: uuidv4(), + expired: false, + expireTime: new Date(Date.now() + 1000 * 60 * 60), + }); + }, 100); + }), + }; + const client = new Client({ + ...AwsSigv4Signer(AwsSigv4SignerOptions), + node: `http://localhost:${port}`, + }); + + client + .search({ + index: 'test', + q: 'foo:bar', + }) + .then(({ body }) => { + t.same(body, { hello: 'world' }); + t.same(getCredentialsCalled, 1); + client + .search({ + index: 'test', + q: 'foo:bar', + }) + .then(({ body }) => { + t.same(body, { hello: 'world' }); + t.same(getCredentialsCalled, 1); + + server.stop(); + }) + .catch(t.fail); + }) + .catch(t.fail); + }); +}); + +test('Basic with expired token (promises)', (t) => { + t.plan(4); + + function handler(req, res) { + res.setHeader('Content-Type', 'application/json;utf=8'); + res.end(JSON.stringify({ hello: 'world' })); + } + + buildServer(handler, ({ port }, server) => { + const mockRegion = 'us-east-1'; + + let getCredentialsCalled = 0; + const getCredentials = () => + new Promise((resolve) => { + setTimeout(() => { + getCredentialsCalled++; + resolve({ + accessKeyId: uuidv4(), + secretAccessKey: uuidv4(), + expired: true, + expireTime: new Date(Date.now() - 1000), + }); + }, 100); + }); + + const AwsSigv4SignerOptions = { + getCredentials: getCredentials, + region: mockRegion, + }; + + const auth = AwsSigv4Signer(AwsSigv4SignerOptions); + + const client = new Client({ + ...auth, + node: `http://localhost:${port}`, + }); + + client + .search({ + index: 'test', + q: 'foo:bar', + }) + .then(({ body }) => { + t.same(body, { hello: 'world' }); + t.same(getCredentialsCalled, 1); + client + .search({ + index: 'test', + q: 'foo:bar', + }) + .then(({ body }) => { + t.same(body, { hello: 'world' }); + t.same(getCredentialsCalled, 2); + + server.stop(); + }) + .catch(t.fail); + }) + .catch(t.fail); + }); +}); + +test('Basic with expired token and credentials sdk refresh (promises)', (t) => { + t.plan(6); + + function handler(req, res) { + res.setHeader('Content-Type', 'application/json;utf=8'); + res.end(JSON.stringify({ hello: 'world' })); + } + + buildServer(handler, ({ port }, server) => { + const mockRegion = 'us-east-1'; + + let getCredentialsCalled = 0; + let refreshPromiseCalled = 0; + const getCredentials = () => + new Promise((resolve) => { + setTimeout(() => { + getCredentialsCalled++; + resolve({ + accessKeyId: uuidv4(), + secretAccessKey: uuidv4(), + needsRefresh: () => true, + refreshPromise: () => + new Promise((resolve) => { + refreshPromiseCalled++; + resolve({ + accessKeyId: uuidv4(), + secretAccessKey: uuidv4(), + expired: false, + }); + }), + }); + }, 100); + }); + + const AwsSigv4SignerOptions = { + getCredentials: getCredentials, + region: mockRegion, + }; + + const auth = AwsSigv4Signer(AwsSigv4SignerOptions); + + const client = new Client({ + ...auth, + node: `http://localhost:${port}`, + }); + + client + .search({ + index: 'test', + q: 'foo:bar', + }) + .then(({ body }) => { + t.same(body, { hello: 'world' }); + t.same(getCredentialsCalled, 1); + t.same(refreshPromiseCalled, 0); + client + .search({ + index: 'test', + q: 'foo:bar', + }) + .then(({ body }) => { + t.same(body, { hello: 'world' }); + t.same(getCredentialsCalled, 1); + t.same(refreshPromiseCalled, 1); + + server.stop(); + }) + .catch(t.fail); + }) + .catch(t.fail); + }); +}); + +test('Basic aws (callback)', (t) => { + t.plan(6); + + function handler(req, res) { + res.setHeader('Content-Type', 'application/json;utf=8'); + res.end(JSON.stringify({ hello: 'world' })); + } + + buildServer(handler, ({ port }, server) => { + const mockRegion = 'us-east-1'; + + let getCredentialsCalled = 0; + const AwsSigv4SignerOptions = { + region: mockRegion, + getCredentials: () => + new Promise((resolve) => { + setTimeout(() => { + getCredentialsCalled++; + resolve({ + accessKeyId: uuidv4(), + secretAccessKey: uuidv4(), + expiration: new Date(Date.now() + 1000 * 60 * 60), + }); + }, 100); + }), + }; + const client = new Client({ + ...AwsSigv4Signer(AwsSigv4SignerOptions), + node: `http://localhost:${port}`, + }); + + client.search( + { + index: 'test', + q: 'foo:bar', + }, + (err, { body }) => { + t.error(err); + t.same(body, { hello: 'world' }); + t.same(getCredentialsCalled, 1); + client.search( + { + index: 'test', + q: 'foo:bar', + }, + (err, { body }) => { + t.error(err); + t.same(body, { hello: 'world' }); + t.same(getCredentialsCalled, 1); + server.stop(); + } + ); + } + ); + }); +}); + +test('Basic aws failure to refresh credentials', (t) => { + t.plan(4); + + function handler(req, res) { + res.setHeader('Content-Type', 'application/json;utf=8'); + res.end(JSON.stringify({ hello: 'world' })); + } + + buildServer(handler, ({ port }, server) => { + const mockRegion = 'us-east-1'; + + let getCredentialsCalled = 0; + const AwsSigv4SignerOptions = { + region: mockRegion, + getCredentials: () => + new Promise((resolve, reject) => { + setTimeout(() => { + getCredentialsCalled++; + if (getCredentialsCalled === 1) { + resolve({ + accessKeyId: uuidv4(), + secretAccessKey: uuidv4(), + expireTime: new Date(Date.now() - 1000 * 60 * 60), + }); + } else { + reject(new Error('Failed to refresh credentials')); + } + }, 100); + }), + }; + const client = new Client({ + ...AwsSigv4Signer(AwsSigv4SignerOptions), + node: `http://localhost:${port}`, + }); + + client + .search({ + index: 'test', + q: 'foo:bar', + }) + .then(({ body }) => { + t.same(body, { hello: 'world' }); + t.same(getCredentialsCalled, 1); + client + .search({ + index: 'test', + q: 'foo:bar', + }) + .then(({ body }) => { + t.same(getCredentialsCalled, 2); + t.fail('Should fail'); + }) + .catch((err) => { + t.ok(err); + t.same(getCredentialsCalled, 2); + }) + .finally(() => { + server.stop(); + }); + }) + .catch(t.fail); + }); +}); diff --git a/yarn.lock b/yarn.lock index 0ce6bffd4..ec440d5ee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"@aws-sdk/types@^3.160.0": + version "3.160.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/types/-/types-3.160.0.tgz#94894019ccc7d4fb44c36814051473a2b3988d55" + integrity sha512-sDpDVw/B80USIERcig55dxvPTv+FbYXjUipRIv3wcQ6ePEHIRfqyvdGaTHZGoWYpOW8GFNEbVc2BGFF2ff4ZdA== + "@babel/code-frame@7.12.11": version "7.12.11" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" @@ -757,6 +762,11 @@ auto-bind@4.0.0: resolved "https://registry.yarnpkg.com/auto-bind/-/auto-bind-4.0.0.tgz#e3589fc6c2da8f7ca43ba9f84fa52a744fc997fb" integrity sha512-Hdw8qdNiqdJ8LqT0iK0sVzkFbzg6fhnQqqfWhBDxcHZvU75+B+ayzTy8x+k5Ix0Y92XOhOUlx74ps+bA6BeYMQ== +aws4@^1.11.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" + integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== + babel-eslint@^10.1.0: version "10.1.0" resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.1.0.tgz#6968e568a910b78fb3779cdd8b6ac2f479943232"