From 6aac5a7eb8d1962d0fad763e45ca4862ebced1db Mon Sep 17 00:00:00 2001 From: Alex Shorsher Date: Thu, 16 Dec 2021 21:51:12 -0500 Subject: [PATCH 1/3] add basic auth to ethconnect requests Signed-off-by: Alex Shorsher --- src/event-stream/event-stream.service.ts | 49 ++++++++++++++++++++---- src/main.ts | 6 ++- src/tokens/tokens.service.ts | 49 +++++++++++++++++++++--- 3 files changed, 88 insertions(+), 16 deletions(-) diff --git a/src/event-stream/event-stream.service.ts b/src/event-stream/event-stream.service.ts index d0b96a1..4f69edf 100644 --- a/src/event-stream/event-stream.service.ts +++ b/src/event-stream/event-stream.service.ts @@ -15,6 +15,9 @@ // limitations under the License. import { HttpService } from '@nestjs/axios'; +import { + AxiosRequestConfig, +} from 'axios'; import { Injectable, Logger } from '@nestjs/common'; import { lastValueFrom } from 'rxjs'; import * as WebSocket from 'ws'; @@ -40,6 +43,8 @@ export class EventStreamSocket { constructor( private url: string, private topic: string, + private username: string, + private password: string, private handleEvents: (events: Event[]) => void, private handleReceipt: (receipt: EventStreamReply) => void, ) { @@ -50,7 +55,7 @@ export class EventStreamSocket { this.disconnectDetected = false; this.closeRequested = false; - this.ws = new WebSocket(this.url); + this.ws = new WebSocket(this.url, {auth:`${this.username}:${this.password}`}); this.ws .on('open', () => { if (this.disconnectDetected) { @@ -131,16 +136,33 @@ export class EventStreamService { private readonly logger = new Logger(EventStreamService.name); private baseUrl: string; + private username: string; + private password: string; constructor(private http: HttpService) {} - configure(baseUrl: string) { + configure(baseUrl: string, username: string, password: string) { this.baseUrl = baseUrl; + this.username = username; + this.password = password; + } + + private basicAuth() { + let requestOptions: AxiosRequestConfig = {}; + if (this.username && this.password) { + requestOptions.auth = { + username: this.username, + password: this.password, + } + } + return requestOptions; } async getStreams(): Promise { const response = await lastValueFrom( - this.http.get(`${this.baseUrl}/eventstreams`), + this.http.get(`${this.baseUrl}/eventstreams`, { + ...this.basicAuth() + }), ); return response.data; } @@ -161,25 +183,33 @@ export class EventStreamService { const stream = existingStreams.find(s => s.name === streamDetails.name); if (stream) { const patchedStreamRes = await lastValueFrom( - this.http.patch(`${this.baseUrl}/eventstreams/${stream.id}`, streamDetails), + this.http.patch(`${this.baseUrl}/eventstreams/${stream.id}`, { + ...streamDetails, + }, { + ...this.basicAuth(), + }), ); this.logger.log(`Event stream for ${topic}: ${stream.id}`); return patchedStreamRes.data; } const newStreamRes = await lastValueFrom( - this.http.post(`${this.baseUrl}/eventstreams`, streamDetails), + this.http.post(`${this.baseUrl}/eventstreams`, { + ...streamDetails, + }, { + ...this.basicAuth() + }), ); this.logger.log(`Event stream for ${topic}: ${newStreamRes.data.id}`); return newStreamRes.data; } async deleteStream(id: string) { - await lastValueFrom(this.http.delete(`${this.baseUrl}/eventstreams/${id}`)); + await lastValueFrom(this.http.delete(`${this.baseUrl}/eventstreams/${id}`, {...this.basicAuth()})); } async getSubscriptions(): Promise { const response = await lastValueFrom( - this.http.get(`${this.baseUrl}/subscriptions`), + this.http.get(`${this.baseUrl}/subscriptions`, {...this.basicAuth()}), ); return response.data; } @@ -188,6 +218,7 @@ export class EventStreamService { const response = await lastValueFrom( this.http.get(`${this.baseUrl}/subscriptions/${subId}`, { validateStatus: status => status < 300 || status === 404, + ...this.basicAuth(), }), ); if (response.status === 404) { @@ -208,6 +239,8 @@ export class EventStreamService { name, stream: streamId, fromBlock, + }, { + ...this.basicAuth() }), ); this.logger.log(`Created subscription ${event}: ${response.data.id}`); @@ -236,6 +269,6 @@ export class EventStreamService { handleEvents: (events: Event[]) => void, handleReceipt: (receipt: EventStreamReply) => void, ) { - return new EventStreamSocket(url, topic, handleEvents, handleReceipt); + return new EventStreamSocket(url, topic, this.username, this.password, handleEvents, handleReceipt); } } diff --git a/src/main.ts b/src/main.ts index c2826b0..ab00a70 100644 --- a/src/main.ts +++ b/src/main.ts @@ -76,12 +76,14 @@ async function bootstrap() { const topic = config.get('ETHCONNECT_TOPIC', 'token'); const shortPrefix = config.get('ETHCONNECT_PREFIX', 'fly'); const autoInit = config.get('AUTO_INIT', 'true'); + const username = config.get("ETHCONNECT_USERNAME", ''); + const password = config.get("ETHCONNECT_PASSWORD", ''); const wsUrl = ethConnectUrl.replace('http', 'ws') + '/ws'; - app.get(EventStreamService).configure(ethConnectUrl); + app.get(EventStreamService).configure(ethConnectUrl, username, password); app.get(EventStreamProxyGateway).configure(wsUrl, topic); - app.get(TokensService).configure(ethConnectUrl, instancePath, topic, shortPrefix); + app.get(TokensService).configure(ethConnectUrl, instancePath, topic, shortPrefix, username, password); try { await app.get(TokensService).migrate(); diff --git a/src/tokens/tokens.service.ts b/src/tokens/tokens.service.ts index 75c2ddd..5654d40 100644 --- a/src/tokens/tokens.service.ts +++ b/src/tokens/tokens.service.ts @@ -15,6 +15,9 @@ // limitations under the License. import { HttpService } from '@nestjs/axios'; +import { + AxiosRequestConfig, +} from 'axios'; import { Injectable, Logger, NotFoundException } from '@nestjs/common'; import { lastValueFrom } from 'rxjs'; import { EventStreamService } from '../event-stream/event-stream.service'; @@ -76,6 +79,8 @@ export class TokensService { topic: string; shortPrefix: string; stream: EventStream; + username: string; + password: string; constructor( private http: HttpService, @@ -83,13 +88,15 @@ export class TokensService { private proxy: EventStreamProxyGateway, ) {} - configure(baseUrl: string, instancePath: string, topic: string, shortPrefix: string) { + configure(baseUrl: string, instancePath: string, topic: string, shortPrefix: string, username: string, password: string) { this.baseUrl = baseUrl; this.instancePath = instancePath; this.instanceUrl = baseUrl + instancePath; this.topic = topic; this.shortPrefix = shortPrefix; - this.proxy.addListener(new TokenListener(this.http, this.instanceUrl, this.topic)); + this.username = username; + this.password = password; + this.proxy.addListener(new TokenListener(this.http, this.instanceUrl, this.topic, this.username, this.password)); } /** @@ -153,19 +160,35 @@ export class TokensService { const from = `${this.shortPrefix}-from`; const sync = `${this.shortPrefix}-sync`; const id = `${this.shortPrefix}-id`; - return { + + let requestOptions: AxiosRequestConfig = { params: { [from]: operator, [sync]: 'false', [id]: requestId, }, + ...this.basicAuth(), }; + + return requestOptions; } + private basicAuth() { + let requestOptions: AxiosRequestConfig = {}; + if (this.username && this.password) { + requestOptions.auth = { + username: this.username, + password: this.password, + } + } + return requestOptions; + }; + async getReceipt(id: string): Promise { const response = await lastValueFrom( this.http.get(`${this.baseUrl}/reply/${id}`, { validateStatus: status => status < 300 || status === 404, + ...this.basicAuth(), }), ); if (response.status === 404) { @@ -295,6 +318,7 @@ export class TokensService { account: dto.account, id: packTokenId(dto.poolId, dto.tokenIndex), }, + ...this.basicAuth(), }), ); return { balance: response.data.output }; @@ -306,7 +330,7 @@ class TokenListener implements EventListener { private uriPattern: string | undefined; - constructor(private http: HttpService, private instanceUrl: string, private topic: string) {} + constructor(private http: HttpService, private instanceUrl: string, private topic: string, private username: string, private password: string) {} async onEvent(subName: string, event: Event, process: EventProcessor) { switch (event.signature) { @@ -327,6 +351,17 @@ class TokenListener implements EventListener { } } + private basicAuth() { + let requestOptions: AxiosRequestConfig = {}; + if (this.username && this.password) { + requestOptions.auth = { + username: this.username, + password: this.password, + } + } + return requestOptions; + }; + private transformTokenCreateEvent( subName: string, event: TokenCreateEvent, @@ -449,7 +484,9 @@ class TokenListener implements EventListener { // Fetch and cache the URI pattern (assume it is the same for all tokens) try { const response = await lastValueFrom( - this.http.get(`${this.instanceUrl}/uri?input=0`), + this.http.get(`${this.instanceUrl}/uri?input=0`, { + ...this.basicAuth(), + }), ); this.uriPattern = response.data.output; } catch (err) { @@ -458,4 +495,4 @@ class TokenListener implements EventListener { } return this.uriPattern.replace('{id}', encodeHexIDForURI(id)); } -} +} \ No newline at end of file From 2030687c51889e924ec7b12edc2f5a2318017f2a Mon Sep 17 00:00:00 2001 From: Alex Shorsher Date: Thu, 16 Dec 2021 22:04:28 -0500 Subject: [PATCH 2/3] update e2e tests Signed-off-by: Alex Shorsher --- src/event-stream/event-stream.service.ts | 3 ++- test/app.e2e-spec.ts | 10 +++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/event-stream/event-stream.service.ts b/src/event-stream/event-stream.service.ts index 4f69edf..12b2e8c 100644 --- a/src/event-stream/event-stream.service.ts +++ b/src/event-stream/event-stream.service.ts @@ -55,7 +55,8 @@ export class EventStreamSocket { this.disconnectDetected = false; this.closeRequested = false; - this.ws = new WebSocket(this.url, {auth:`${this.username}:${this.password}`}); + const auth = (this.username && this.password) ? {auth:`${this.username}:${this.password}`} : undefined + this.ws = new WebSocket(this.url, auth); this.ws .on('open', () => { if (this.disconnectDetected) { diff --git a/test/app.e2e-spec.ts b/test/app.e2e-spec.ts index 5d4cba8..6d79282 100644 --- a/test/app.e2e-spec.ts +++ b/test/app.e2e-spec.ts @@ -136,7 +136,7 @@ describe('AppController (e2e)', () => { await app.init(); app.get(EventStreamProxyGateway).configure('url', TOPIC); - app.get(TokensService).configure(BASE_URL, INSTANCE_PATH, TOPIC, PREFIX); + app.get(TokensService).configure(BASE_URL, INSTANCE_PATH, TOPIC, PREFIX, "", ""); (app.getHttpServer() as Server).listen(); server = request(app.getHttpServer()); @@ -523,7 +523,7 @@ describe('AppController (e2e)', () => { }); expect(http.get).toHaveBeenCalledTimes(1); - expect(http.get).toHaveBeenCalledWith(`${BASE_URL}${INSTANCE_PATH}/uri?input=0`); + expect(http.get).toHaveBeenCalledWith(`${BASE_URL}${INSTANCE_PATH}/uri?input=0`, {}); }); it('Websocket: token burn event', async () => { @@ -596,7 +596,7 @@ describe('AppController (e2e)', () => { }); expect(http.get).toHaveBeenCalledTimes(1); - expect(http.get).toHaveBeenCalledWith(`${BASE_URL}${INSTANCE_PATH}/uri?input=0`); + expect(http.get).toHaveBeenCalledWith(`${BASE_URL}${INSTANCE_PATH}/uri?input=0`, {}); }); it('Websocket: token transfer event', async () => { @@ -661,7 +661,7 @@ describe('AppController (e2e)', () => { }); expect(http.get).toHaveBeenCalledTimes(1); - expect(http.get).toHaveBeenCalledWith(`${BASE_URL}${INSTANCE_PATH}/uri?input=0`); + expect(http.get).toHaveBeenCalledWith(`${BASE_URL}${INSTANCE_PATH}/uri?input=0`, {}); }); it('Websocket: token transfer event from wrong pool', () => { @@ -805,7 +805,7 @@ describe('AppController (e2e)', () => { }); expect(http.get).toHaveBeenCalledTimes(1); - expect(http.get).toHaveBeenCalledWith(`${BASE_URL}${INSTANCE_PATH}/uri?input=0`); + expect(http.get).toHaveBeenCalledWith(`${BASE_URL}${INSTANCE_PATH}/uri?input=0`, {}); }); it('Websocket: success receipt', () => { From 8797dc0a6cfbd486cff329424837ded51ea6aba4 Mon Sep 17 00:00:00 2001 From: Alex Shorsher Date: Fri, 17 Dec 2021 14:37:54 -0500 Subject: [PATCH 3/3] move basicAuth to top level utils Signed-off-by: Alex Shorsher --- src/event-stream/event-stream.service.ts | 87 ++++++++++++++---------- src/main.ts | 8 ++- src/tokens/tokens.service.ts | 60 +++++++--------- src/utils.ts | 12 ++++ test/app.e2e-spec.ts | 2 +- 5 files changed, 94 insertions(+), 75 deletions(-) create mode 100644 src/utils.ts diff --git a/src/event-stream/event-stream.service.ts b/src/event-stream/event-stream.service.ts index 12b2e8c..65aa86f 100644 --- a/src/event-stream/event-stream.service.ts +++ b/src/event-stream/event-stream.service.ts @@ -15,12 +15,10 @@ // limitations under the License. import { HttpService } from '@nestjs/axios'; -import { - AxiosRequestConfig, -} from 'axios'; import { Injectable, Logger } from '@nestjs/common'; import { lastValueFrom } from 'rxjs'; import * as WebSocket from 'ws'; +import { basicAuth } from '../utils'; import { Event, EventStream, @@ -55,7 +53,8 @@ export class EventStreamSocket { this.disconnectDetected = false; this.closeRequested = false; - const auth = (this.username && this.password) ? {auth:`${this.username}:${this.password}`} : undefined + const auth = + this.username && this.password ? { auth: `${this.username}:${this.password}` } : undefined; this.ws = new WebSocket(this.url, auth); this.ws .on('open', () => { @@ -148,21 +147,10 @@ export class EventStreamService { this.password = password; } - private basicAuth() { - let requestOptions: AxiosRequestConfig = {}; - if (this.username && this.password) { - requestOptions.auth = { - username: this.username, - password: this.password, - } - } - return requestOptions; - } - async getStreams(): Promise { const response = await lastValueFrom( this.http.get(`${this.baseUrl}/eventstreams`, { - ...this.basicAuth() + ...basicAuth(this.username, this.password), }), ); return response.data; @@ -184,33 +172,47 @@ export class EventStreamService { const stream = existingStreams.find(s => s.name === streamDetails.name); if (stream) { const patchedStreamRes = await lastValueFrom( - this.http.patch(`${this.baseUrl}/eventstreams/${stream.id}`, { - ...streamDetails, - }, { - ...this.basicAuth(), - }), + this.http.patch( + `${this.baseUrl}/eventstreams/${stream.id}`, + { + ...streamDetails, + }, + { + ...basicAuth(this.username, this.password), + }, + ), ); this.logger.log(`Event stream for ${topic}: ${stream.id}`); return patchedStreamRes.data; } const newStreamRes = await lastValueFrom( - this.http.post(`${this.baseUrl}/eventstreams`, { - ...streamDetails, - }, { - ...this.basicAuth() - }), + this.http.post( + `${this.baseUrl}/eventstreams`, + { + ...streamDetails, + }, + { + ...basicAuth(this.username, this.password), + }, + ), ); this.logger.log(`Event stream for ${topic}: ${newStreamRes.data.id}`); return newStreamRes.data; } async deleteStream(id: string) { - await lastValueFrom(this.http.delete(`${this.baseUrl}/eventstreams/${id}`, {...this.basicAuth()})); + await lastValueFrom( + this.http.delete(`${this.baseUrl}/eventstreams/${id}`, { + ...basicAuth(this.username, this.password), + }), + ); } async getSubscriptions(): Promise { const response = await lastValueFrom( - this.http.get(`${this.baseUrl}/subscriptions`, {...this.basicAuth()}), + this.http.get(`${this.baseUrl}/subscriptions`, { + ...basicAuth(this.username, this.password), + }), ); return response.data; } @@ -219,7 +221,7 @@ export class EventStreamService { const response = await lastValueFrom( this.http.get(`${this.baseUrl}/subscriptions/${subId}`, { validateStatus: status => status < 300 || status === 404, - ...this.basicAuth(), + ...basicAuth(this.username, this.password), }), ); if (response.status === 404) { @@ -236,13 +238,17 @@ export class EventStreamService { fromBlock = '0', // subscribe from the start of the chain by default ): Promise { const response = await lastValueFrom( - this.http.post(`${this.baseUrl}/${instancePath}/${event}`, { - name, - stream: streamId, - fromBlock, - }, { - ...this.basicAuth() - }), + this.http.post( + `${this.baseUrl}/${instancePath}/${event}`, + { + name, + stream: streamId, + fromBlock, + }, + { + ...basicAuth(this.username, this.password), + }, + ), ); this.logger.log(`Created subscription ${event}: ${response.data.id}`); return response.data; @@ -270,6 +276,13 @@ export class EventStreamService { handleEvents: (events: Event[]) => void, handleReceipt: (receipt: EventStreamReply) => void, ) { - return new EventStreamSocket(url, topic, this.username, this.password, handleEvents, handleReceipt); + return new EventStreamSocket( + url, + topic, + this.username, + this.password, + handleEvents, + handleReceipt, + ); } } diff --git a/src/main.ts b/src/main.ts index ab00a70..b6ec1bc 100644 --- a/src/main.ts +++ b/src/main.ts @@ -76,14 +76,16 @@ async function bootstrap() { const topic = config.get('ETHCONNECT_TOPIC', 'token'); const shortPrefix = config.get('ETHCONNECT_PREFIX', 'fly'); const autoInit = config.get('AUTO_INIT', 'true'); - const username = config.get("ETHCONNECT_USERNAME", ''); - const password = config.get("ETHCONNECT_PASSWORD", ''); + const username = config.get('ETHCONNECT_USERNAME', ''); + const password = config.get('ETHCONNECT_PASSWORD', ''); const wsUrl = ethConnectUrl.replace('http', 'ws') + '/ws'; app.get(EventStreamService).configure(ethConnectUrl, username, password); app.get(EventStreamProxyGateway).configure(wsUrl, topic); - app.get(TokensService).configure(ethConnectUrl, instancePath, topic, shortPrefix, username, password); + app + .get(TokensService) + .configure(ethConnectUrl, instancePath, topic, shortPrefix, username, password); try { await app.get(TokensService).migrate(); diff --git a/src/tokens/tokens.service.ts b/src/tokens/tokens.service.ts index 5654d40..39b1a31 100644 --- a/src/tokens/tokens.service.ts +++ b/src/tokens/tokens.service.ts @@ -15,9 +15,7 @@ // limitations under the License. import { HttpService } from '@nestjs/axios'; -import { - AxiosRequestConfig, -} from 'axios'; +import { AxiosRequestConfig } from 'axios'; import { Injectable, Logger, NotFoundException } from '@nestjs/common'; import { lastValueFrom } from 'rxjs'; import { EventStreamService } from '../event-stream/event-stream.service'; @@ -25,6 +23,7 @@ import { Event, EventStream, EventStreamReply } from '../event-stream/event-stre import { EventStreamProxyGateway } from '../eventstream-proxy/eventstream-proxy.gateway'; import { EventListener, EventProcessor } from '../eventstream-proxy/eventstream-proxy.interfaces'; import { WebSocketMessage } from '../websocket-events/websocket-events.base'; +import { basicAuth } from '../utils'; import { AsyncResponse, EthConnectAsyncResponse, @@ -88,7 +87,14 @@ export class TokensService { private proxy: EventStreamProxyGateway, ) {} - configure(baseUrl: string, instancePath: string, topic: string, shortPrefix: string, username: string, password: string) { + configure( + baseUrl: string, + instancePath: string, + topic: string, + shortPrefix: string, + username: string, + password: string, + ) { this.baseUrl = baseUrl; this.instancePath = instancePath; this.instanceUrl = baseUrl + instancePath; @@ -96,7 +102,9 @@ export class TokensService { this.shortPrefix = shortPrefix; this.username = username; this.password = password; - this.proxy.addListener(new TokenListener(this.http, this.instanceUrl, this.topic, this.username, this.password)); + this.proxy.addListener( + new TokenListener(this.http, this.instanceUrl, this.topic, this.username, this.password), + ); } /** @@ -161,34 +169,23 @@ export class TokensService { const sync = `${this.shortPrefix}-sync`; const id = `${this.shortPrefix}-id`; - let requestOptions: AxiosRequestConfig = { + const requestOptions: AxiosRequestConfig = { params: { [from]: operator, [sync]: 'false', [id]: requestId, }, - ...this.basicAuth(), + ...basicAuth(this.username, this.password), }; return requestOptions; } - private basicAuth() { - let requestOptions: AxiosRequestConfig = {}; - if (this.username && this.password) { - requestOptions.auth = { - username: this.username, - password: this.password, - } - } - return requestOptions; - }; - async getReceipt(id: string): Promise { const response = await lastValueFrom( this.http.get(`${this.baseUrl}/reply/${id}`, { validateStatus: status => status < 300 || status === 404, - ...this.basicAuth(), + ...basicAuth(this.username, this.password), }), ); if (response.status === 404) { @@ -318,7 +315,7 @@ export class TokensService { account: dto.account, id: packTokenId(dto.poolId, dto.tokenIndex), }, - ...this.basicAuth(), + ...basicAuth(this.username, this.password), }), ); return { balance: response.data.output }; @@ -330,7 +327,13 @@ class TokenListener implements EventListener { private uriPattern: string | undefined; - constructor(private http: HttpService, private instanceUrl: string, private topic: string, private username: string, private password: string) {} + constructor( + private http: HttpService, + private instanceUrl: string, + private topic: string, + private username: string, + private password: string, + ) {} async onEvent(subName: string, event: Event, process: EventProcessor) { switch (event.signature) { @@ -351,17 +354,6 @@ class TokenListener implements EventListener { } } - private basicAuth() { - let requestOptions: AxiosRequestConfig = {}; - if (this.username && this.password) { - requestOptions.auth = { - username: this.username, - password: this.password, - } - } - return requestOptions; - }; - private transformTokenCreateEvent( subName: string, event: TokenCreateEvent, @@ -485,7 +477,7 @@ class TokenListener implements EventListener { try { const response = await lastValueFrom( this.http.get(`${this.instanceUrl}/uri?input=0`, { - ...this.basicAuth(), + ...basicAuth(this.username, this.password), }), ); this.uriPattern = response.data.output; @@ -495,4 +487,4 @@ class TokenListener implements EventListener { } return this.uriPattern.replace('{id}', encodeHexIDForURI(id)); } -} \ No newline at end of file +} diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..06f2876 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,12 @@ +import { AxiosRequestConfig } from 'axios'; + +export const basicAuth = (username: string, password: string) => { + const requestOptions: AxiosRequestConfig = {}; + if (username !== '' && password !== '') { + requestOptions.auth = { + username: username, + password: password, + }; + } + return requestOptions; +}; diff --git a/test/app.e2e-spec.ts b/test/app.e2e-spec.ts index 6d79282..262dea0 100644 --- a/test/app.e2e-spec.ts +++ b/test/app.e2e-spec.ts @@ -136,7 +136,7 @@ describe('AppController (e2e)', () => { await app.init(); app.get(EventStreamProxyGateway).configure('url', TOPIC); - app.get(TokensService).configure(BASE_URL, INSTANCE_PATH, TOPIC, PREFIX, "", ""); + app.get(TokensService).configure(BASE_URL, INSTANCE_PATH, TOPIC, PREFIX, '', ''); (app.getHttpServer() as Server).listen(); server = request(app.getHttpServer());